pax_global_header00006660000000000000000000000064142002437060014510gustar00rootroot0000000000000052 comment=33aed8c7fb5403f81fec5448acb4a7d8afe2a06a fwupd-1.7.5/000077500000000000000000000000001420024370600126475ustar00rootroot00000000000000fwupd-1.7.5/.circleci/000077500000000000000000000000001420024370600145025ustar00rootroot00000000000000fwupd-1.7.5/.circleci/config.yml000066400000000000000000000134601420024370600164760ustar00rootroot00000000000000version: 2 jobs: build-ubuntu-x86_64: machine: image: circleci/classic:latest 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/fwupd" build-windows: docker: - image: fedora:35 steps: - run: name: "Install deps" command: dnf install -y diffutils glib2-devel git-core gnutls-utils jq meson git gcc gcab ca-certificates mingw32-nsis mingw64-brotli mingw64-gcc mingw64-pkg-config mingw64-glib2 mingw64-gnutls mingw64-libusbx mingw64-sqlite mingw64-libarchive mingw64-json-glib mingw64-curl wine - checkout - run: name: "Build Win32" command: ./contrib/ci/build_windows.sh - persist_to_workspace: root: . paths: - "dist/setup/*.exe" - "dist/VERSION" - "dist/news.txt" - store_artifacts: path: dist/setup build-snap: docker: - image: ubuntu:18.04 steps: - run: name: "Update apt" command: apt update - run: name: "install snapcraft" command: apt install snapcraft git -y - checkout - run: name: "Build Snap" command: snapcraft - run: command: | mkdir -p /tmp/snaps cp *.snap /tmp/snaps - store_artifacts: path: /tmp/snaps - persist_to_workspace: root: . paths: - "*.snap" publish-docs: machine: true 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-edge: docker: - image: cibuilds/snapcraft:stable steps: - attach_workspace: at: . - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release edge publish-stable: docker: - image: cibuilds/snapcraft:stable steps: - attach_workspace: at: . - run: name: "Publish to Store" command: | mkdir .snapcraft echo $SNAPCRAFT_LOGIN_FILE | base64 --decode --ignore-garbage > .snapcraft/snapcraft.cfg snapcraft push *.snap --release stable publish-github-exe-release: docker: - image: circleci/golang:1.9 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 - build-snap - publish-edge: requires: - build-snap filters: branches: only: main 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+$/ - publish-docs: requires: - build-ubuntu-x86_64 filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - build-snap: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - publish-stable: requires: - build-snap filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ fwupd-1.7.5/.clang-format000066400000000000000000000026141420024370600152250ustar00rootroot00000000000000--- 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.7.5/.editorconfig000066400000000000000000000002161420024370600153230ustar00rootroot00000000000000root = 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.7.5/.git-blame-ignore-revs000066400000000000000000000000511420024370600167430ustar00rootroot0000000000000072819f91c19e076ca383e6e9fd7c7a510c62792e fwupd-1.7.5/.gitconfig000066400000000000000000000000611420024370600146160ustar00rootroot00000000000000[blame] ignoreRevsFile = .git-blame-ignore-revs fwupd-1.7.5/.github/000077500000000000000000000000001420024370600142075ustar00rootroot00000000000000fwupd-1.7.5/.github/ISSUE_TEMPLATE/000077500000000000000000000000001420024370600163725ustar00rootroot00000000000000fwupd-1.7.5/.github/ISSUE_TEMPLATE/bug-report-general.md000066400000000000000000000014271420024370600224210ustar00rootroot00000000000000--- 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.7.5/.github/ISSUE_TEMPLATE/bug-report-uefi.md000066400000000000000000000021111420024370600217230ustar00rootroot00000000000000--- 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.7.5/.github/ISSUE_TEMPLATE/bug-report-wd19.md000066400000000000000000000030541420024370600215660ustar00rootroot00000000000000--- 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.7.5/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341420024370600221160ustar00rootroot00000000000000--- 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.7.5/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000006071420024370600205660ustar00rootroot00000000000000--- 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.7.5/.github/pull_request_template.md000066400000000000000000000002731420024370600211520ustar00rootroot00000000000000Type 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.7.5/.github/stale.yml000066400000000000000000000012631420024370600160440ustar00rootroot00000000000000# 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. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false fwupd-1.7.5/.github/workflows/000077500000000000000000000000001420024370600162445ustar00rootroot00000000000000fwupd-1.7.5/.github/workflows/create_containers.yml000066400000000000000000000023421420024370600224600ustar00rootroot00000000000000name: Create containers on: schedule: - cron: '0 0 * * *' jobs: push_to_registry: 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@v2 # TODO(#122): Remove step when https://github.com/actions/virtual-environments/issues/2658 fixed - name: update runc # https://github.com/actions/virtual-environments/issues/2698#issuecomment-779262068 if: startsWith(matrix.os, 'arch') run: | sudo apt-get install --assume-yes libseccomp-dev git clone https://github.com/opencontainers/runc cd runc && git checkout v1.0.0-rc93 && make -j`nproc` && sudo make install cd .. && rm --recursive runc - name: "Generate Dockerfile" env: OS: ${{ matrix.os }} run: ./contrib/ci/generate_docker.py - name: Push to GitHub Packages uses: docker/build-push-action@v1 with: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: docker.pkg.github.com repository: fwupd/fwupd/fwupd-${{matrix.os}} tags: latest fwupd-1.7.5/.github/workflows/freebsd.yml000066400000000000000000000022121420024370600203760ustar00rootroot00000000000000name: FreeBSD daily Builds on: schedule: - cron: "0 0 * * *" jobs: build-freebsd: runs-on: macos-10.15 timeout-minutes: 30 name: build-freebsd-package steps: - name: Checkout uses: actions/checkout@v2 - name: Find tag id: tagger uses: jimschubert/query-tag-action@v1 - name: Build id: test uses: vmactions/freebsd-vm@v0.1.5 with: usesh: true mem: 8192 prepare: | pkg install -y git python3 glib meson pkgconf gobject-introspection \ vala gtk-doc json-glib gpgme gnutls sqlite3 curl gcab libarchive \ libgpg-error gettext-tools gtk-update-icon-cache atk pango \ binutils gcc protobuf-c sync: rsync run: ./contrib/ci/build_freebsd_package.sh --GITHUB_SHA=${GITHUB_SHA} --GITHUB_REPOSITORY_OWNER=${GITHUB_REPOSITORY_OWNER} --GITHUB_REPOSITORY=${GITHUB_REPOSITORY} --GITHUB_TAG=${{steps.tagger.outputs.tag}} - name: Upload fwupd binary artifact uses: actions/upload-artifact@v2 with: name: Binary package path: | fwupd*.pkg fwupd-1.7.5/.github/workflows/greetings.yml000066400000000000000000000010341420024370600207540ustar00rootroot00000000000000name: Greetings on: [pull_request_target] jobs: greeting: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: 'Thanks for your contribution!' pr-message: 'We really appreciate new contributors to the fwupd project. Please review https://github.com/fwupd/fwupd/blob/main/CONTRIBUTING.md for details on how to effectively write patches for pull requests.' fwupd-1.7.5/.github/workflows/main.yml000066400000000000000000000044211420024370600177140ustar00rootroot00000000000000name: Continuous Integration on: push: branches: [ main ] pull_request: branches: [ main ] jobs: pre-commit: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 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-20.04 steps: - uses: actions/checkout@v2 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 - name: Check ABI run: ./contrib/ci/check-abi $(git describe --abbrev=0 --tags) $(git rev-parse HEAD) build: runs-on: ubuntu-latest strategy: matrix: os: [fedora, debian-x86_64, arch, debian-i386, void] steps: - uses: actions/checkout@v2 - 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 fuzzing: 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: 300 dry-run: false - name: Upload Crash uses: actions/upload-artifact@v1 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts fwupd-1.7.5/.gitignore000066400000000000000000000007731420024370600146460ustar00rootroot00000000000000/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-mst/tests/ plugins/synaptics-prometheus/tests/ plugins/tpm-eventlog/tests/ plugins/uefi-dbx/tests/ .buildconfig .ossfuzz fwupd-1.7.5/.gitmodules000066400000000000000000000001561420024370600150260ustar00rootroot00000000000000[submodule "contrib/flatpak"] path = contrib/flatpak url = https://github.com/flathub/org.freedesktop.fwupd fwupd-1.7.5/.lgtm.yml000066400000000000000000000017741420024370600144240ustar00rootroot00000000000000extraction: python: python_setup: version: "3" cpp: prepare: packages: - libarchive-tools - python3-pip - python3-setuptools - python3-wheel - libssl-dev after_prepare: - python3 -m pip install --user "meson >= 0.52.0" - export PATH="$HOME/.local/bin:$PATH" - "cd $LGTM_WORKSPACE" - "mkdir installdir" - "wget https://github.com/tpm2-software/tpm2-tss/releases/download/2.3.0/tpm2-tss-2.3.0.tar.gz" - "tar xf tpm2-tss-2.3.0.tar.gz" - "cd tpm2-tss-2.3.0" - "./configure --prefix=$LGTM_WORKSPACE/installdir/usr --disable-doxygen-doc" - "make install" - "export PKG_CONFIG_PATH=$LGTM_WORKSPACE/installdir/usr/lib/pkgconfig:$PKG_CONFIG_PATH" - "export LD_LIBRARY_PATH=$LGTM_WORKSPACE/installdir/usr/lib:$LD_LIBRARY_PATH" configure: command: - "meson setup _lgtm_build_dir -Defi_binary=false -Dplugin_uefi_capsule_splash=false -Ddocs=none" index: build_command: - "ninja -C _lgtm_build_dir" fwupd-1.7.5/.markdownlint.json000066400000000000000000000001471420024370600163330ustar00rootroot00000000000000{ "default": true, "MD033": false, "MD013": { "tables": false, "line_length": 1000 } } fwupd-1.7.5/.pre-commit-config.yaml000066400000000000000000000044271420024370600171370ustar00rootroot00000000000000default_stages: [commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 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: 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.1.0 hooks: - id: codespell args: ['--config', './contrib/codespell.cfg', --write-changes] - repo: https://github.com/ambv/black rev: 21.6b0 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 mistmatch language: script entry: ./contrib/ci/check-null-false-returns.py - id: check-headers name: check for superfluous includes language: script entry: ./contrib/ci/check-headers.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.27.1 hooks: - id: markdownlint args: ['--fix', '--ignore', '.github'] fwupd-1.7.5/.tx/000077500000000000000000000000001420024370600133605ustar00rootroot00000000000000fwupd-1.7.5/.tx/config000066400000000000000000000002071420024370600145470ustar00rootroot00000000000000[main] host = https://www.transifex.com [fwupd.main] file_filter = po/.po source_file = po/fwupd.pot source_lang = en type = PO fwupd-1.7.5/AUTHORS000066400000000000000000000000451420024370600137160ustar00rootroot00000000000000Richard Hughes fwupd-1.7.5/CODE_OF_CONDUCT.md000066400000000000000000000062231420024370600154510ustar00rootroot00000000000000# 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.7.5/COMMITMENT000066400000000000000000000037761420024370600142630ustar00rootroot00000000000000Common 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.7.5/CONTRIBUTING.md000066400000000000000000000035641420024370600151100ustar00rootroot00000000000000# Contributor Guidelines ## Getting started To set up your local 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. ## 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` `./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.7.5/COPYING000066400000000000000000000636421420024370600137150ustar00rootroot00000000000000 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.7.5/MAINTAINERS000066400000000000000000000001131420024370600143370ustar00rootroot00000000000000Richard Hughes Mario Limonciello fwupd-1.7.5/README.md000066400000000000000000000155601420024370600141350ustar00rootroot00000000000000# fwupd [![Build Status](https://github.com/fwupd/fwupd/actions/workflows/main.yml/badge.svg)](https://github.com/fwupd/fwupd/actions/workflows/main.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/). ## Compiling The most up to date compilation instructions are available in the [Wiki](https://github.com/fwupd/fwupd/wiki/Compilation). **NOTE:** In most cases end users should never 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. ## 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 will 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 [fwupd website](https://fwupd.org/privacy). 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 `daemon.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. ## Local metadata Local metadata can be saved in `/var/lib/fwupd/local.d` or `/usr/share/fwupd/local.d` which are scanned at daemon startup. This can be used to add site-specific BKC tags to existing metadata stores. For instance: 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", ... } ] } ## Other frontends 1. [GNOME Software](https://wiki.gnome.org/Apps/Software) is the graphical frontend available. When compiled with firmware support, it will check for updates periodically and automatically download firmware in the background. After the firmware has been downloaded a popup will be displayed in GNOME Software to perform the update. 2. [KDE Discover](https://userbase.kde.org/Discover) is the software center, generally bundled with KDE Plasma. With the release of [KDE Plasma 5.14](https://www.kde.org/announcements/plasma-5.14.0.php), a new fwupd backend has been implemented in KDE Discover for firmware updates. These firmware updates are shown with other system updates. 3. [Wyse Management Suite](https://www.dell.com/en-us/work/shop/wyse-endpoints-and-software/wyse-management-suite/spd/wyse-wms) A software suite available on Dell IoT gateways and Wyse thin clients with built-in fwupd support. The remote administration interface can be used to download and deploy firmware updates. ## Fuzzing There are several automated fuzzing tests in fwupd. These take some time to run: CC=hfuzz-clang meson --default-library=static \ -Dudevdir=/tmp -Dsystemd_root_prefix=/tmp \ -Dplugin_redfish=false -Dcurl=false \ -Dintrospection=false ../ ninja install ninja fuzz-firmware ninja fuzz-tpm-eventlog fwupd-1.7.5/RELEASE000066400000000000000000000024751420024370600136620ustar00rootroot00000000000000fwupd Release Notes Forking stable branch: When forking main into a stable 1_7_X, be sure to disable the following CI jobs: * publish-docs * publish-stable To make sure it's done right, you can reference commit 433e809318c68c9ab6d4ae50ee9c4312503185d8 Write release entries: git log --format="%s" --cherry-pick --right-only 1.7.4... | grep -i -v trivial | grep -v Merge | sort | uniq Add any user visible changes into ../data/org.freedesktop.fwupd.metainfo.xml appstream-util appdata-to-news ../data/org.freedesktop.fwupd.metainfo.xml > NEWS Update translations: ninja-build fwupd-pot tx push --source tx pull --all --force --minimum-perc=5 ninja-build fix-translations git add ../po/*.po 2. Commit changes to git: # MAKE SURE THIS IS CORRECT export release_ver="1.7.5" 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. Upload tarball: scp meson-dist/fwupd-${release_ver}.tar.* hughsient@people.freedesktop.org:~/public_html/releases 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 fwupd-1.7.5/SECURITY.md000066400000000000000000000026321420024370600144430ustar00rootroot00000000000000# 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 `1.3.x`, `1.2.x` and `1.1.x` branches are fully supported by the upstream authors. Additionally, the `1.0.x` branch is supported for security and bug fixes. 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. | Version | Supported | | ------- | ------------------ | | 1.3.x | :heavy_check_mark: | | 1.2.x | :heavy_check_mark: | | 1.1.x | :heavy_check_mark: | | 1.0.x | :white_check_mark: | | 0.9.x | :x: | | 0.8.x | :x: | ## 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. fwupd-1.7.5/contrib/000077500000000000000000000000001420024370600143075ustar00rootroot00000000000000fwupd-1.7.5/contrib/PKGBUILD000066400000000000000000000022751420024370600154410ustar00rootroot00000000000000# 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' 'gtk-doc' 'git' 'python-cairo' 'noto-fonts' 'noto-fonts-cjk' 'python-gobject' 'vala' 'curl' 'polkit' 'gcab' '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=false \ -Dlzma=true \ -Ddocs=gtkdoc \ -Defi_binary=false \ -Dsupported_build=true ninja -v -C ../build } check() { CACHE_DIRECTORY=/tmp ninja -C build test } package() { DESTDIR="${pkgdir}" ninja -C build install } fwupd-1.7.5/contrib/README.md000066400000000000000000000052071420024370600155720ustar00rootroot00000000000000# 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.7.5/contrib/build-openbmc.sh000077500000000000000000000022511420024370600173660ustar00rootroot00000000000000#!/bin/sh meson ../ \ -Dbash_completion=false \ -Dcompat_cli=false \ -Dconsolekit=false \ -Ddocs=none \ -Delogind=false \ -Dfish_completion=false \ -Dfirmware-packager=false \ -Dgudev=false \ -Dgusb=false \ -Dhsi=false \ -Dintrospection=false \ -Dlibarchive=false \ -Dman=false \ -Dmetainfo=false \ -Doffline=false \ -Dplugin_acpi_phat=false \ -Dplugin_amt=false \ -Dplugin_bcm57xx=false \ -Dplugin_cfu=false \ -Dplugin_dell=false \ -Dplugin_emmc=false \ -Dplugin_ep963x=false \ -Dplugin_fastboot=false \ -Dplugin_logitech_bulkcontroller=false \ -Dplugin_nitrokey=false \ -Dplugin_nvme=false \ -Dplugin_parade_lspcon=false \ -Dplugin_pixart_rf=false \ -Dplugin_powerd=false \ -Dplugin_realtek_mst=false \ -Dplugin_redfish=false \ -Dplugin_synaptics_mst=false \ -Dplugin_synaptics_rmi=false \ -Dplugin_thunderbolt=false \ -Dplugin_tpm=false \ -Dplugin_uefi_capsule=false \ -Dplugin_uf2=false \ -Dplugin_upower=false \ -Dpolkit=false \ -Dsqlite=false \ -Dtests=false \ -Dudevdir=/tmp \ -Dsystemd_root_prefix=/tmp \ $@ fwupd-1.7.5/contrib/ci/000077500000000000000000000000001420024370600147025ustar00rootroot00000000000000fwupd-1.7.5/contrib/ci/Dockerfile-arch.in000066400000000000000000000004101420024370600202070ustar00rootroot00000000000000FROM 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 pacman -Syu --noconfirm archlinux-keyring %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/arch.sh"] fwupd-1.7.5/contrib/ci/Dockerfile-debian.in000066400000000000000000000003331420024370600205200ustar00rootroot00000000000000FROM %%%ARCH_PREFIX%%%debian:testing %%%OS%%% ENV CI_NETWORK true RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/debian.sh"] fwupd-1.7.5/contrib/ci/Dockerfile-fedora.in000066400000000000000000000004121420024370600205340ustar00rootroot00000000000000FROM fedora:35 %%%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.7.5/contrib/ci/Dockerfile-snap.in000066400000000000000000000017651420024370600202510ustar00rootroot00000000000000FROM 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.7.5/contrib/ci/Dockerfile-ubuntu.in000066400000000000000000000003251420024370600206210ustar00rootroot00000000000000FROM ubuntu:devel %%%OS%%% ENV CI_NETWORK true ENV CC clang RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/ubuntu.sh"] fwupd-1.7.5/contrib/ci/Dockerfile-void.in000066400000000000000000000003771420024370600202470ustar00rootroot00000000000000FROM 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.7.5/contrib/ci/README.md000066400000000000000000000102561420024370600161650ustar00rootroot00000000000000# 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.7.5/contrib/ci/abidiff.suppr000066400000000000000000000005241420024370600173620ustar00rootroot00000000000000[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.7.5/contrib/ci/arch.sh000077500000000000000000000027141420024370600161620ustar00rootroot00000000000000#!/bin/bash set -e set -x shopt -s extglob #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #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 -Syu --noconfirm python-flask ../plugins/redfish/tests/redfish.py & # install and run TPM simulator necessary for plugins/uefi-capsule/uefi-self-test pacman -Syu --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 -Syu --noconfirm qt5-base meson qt5-thread-test ../contrib/ci/qt5-thread-test ninja -C qt5-thread-test test #run the CI tests for making sure we can link fwupd/fwupdplugin meson out-of-tree-link ../contrib/ci/out-of-tree-link ninja -C out-of-tree-link test # move the package to working dir mv *.pkg.* ../dist # no testing here because gnome-desktop-testing isn’t available in Arch fwupd-1.7.5/contrib/ci/build_freebsd_package.sh000077500000000000000000000041021420024370600215020ustar00rootroot00000000000000#!/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 quaterly 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.7.5/contrib/ci/build_windows.sh000077500000000000000000000057141420024370600201210ustar00rootroot00000000000000#!/bin/sh set -e #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=false): # 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) #build mkdir -p $build $DESTDIR && cd $build meson .. \ --cross-file=../contrib/mingw64.cross \ --prefix=/ \ --libexecdir="" \ --bindir="" \ -Dbuild=standalone \ -Ddocs=none \ -Dhsi=false \ -Dpolkit=false \ -Dplugin_flashrom=false \ -Dplugin_uefi_capsule=false \ -Dplugin_redfish=false \ -Dplugin_dell=false \ -Dplugin_logitech_bulkcontroller=false \ -Dplugin_nvme=false \ -Dplugin_parade_lspcon=false \ -Dplugin_realtek_mst=false \ -Dplugin_platform_integrity=false \ -Dplugin_bcm57xx=false \ -Dplugin_pixart_rf=false \ -Dplugin_cfu=false \ -Dplugin_cpu=false \ -Dplugin_ep963x=false \ -Dplugin_tpm=false \ -Dsystemd=false \ -Doffline=false \ -Dplugin_emmc=false \ -Dplugin_amt=false \ -Dplugin_mtd=false \ -Dintrospection=false \ -Dplugin_thunderbolt=false \ -Dplugin_synaptics_mst=false \ -Dplugin_synaptics_rmi=false \ -Dplugin_upower=false \ -Dplugin_powerd=false \ -Dman=false \ -Dmetainfo=false \ -Dsoup_session_compat=false \ -Dgcab:introspection=false \ -Dgcab:docs=false \ -Dgcab:nls=false \ -Dgcab:vapi=false \ -Dgcab:tests=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dlibjcat:man=false \ -Dlibjcat:gpg=false \ -Dlibjcat:introspection=false \ -Dgusb:tests=false \ -Dgusb:docs=false \ -Dgusb:introspection=false \ -Dgusb:vapi=false \ -Dbluez=false \ -Dsqlite=false \ -Dgudev=false $@ VERSION=$(meson introspect . --projectinfo | jq -r .version) ninja -v ninja -v install #generate news release cd $root contrib/ci/generate_news.py $VERSION > $DESTDIR/news.txt echo $VERSION > $DESTDIR/VERSION #disable motd for Windows sed -i 's,UpdateMotd=.*,UpdateMotd=false,' $DESTDIR/etc/fwupd/daemon.conf # create a setup binary cd $DESTDIR mkdir -p $DESTDIR/setup makensis -NOCD $build/contrib/setup-win32.nsi #so that it's actually executable cp /usr/x86_64-w64-mingw32/sys-root/mingw/bin/*.dll . #remove static archives find -type f -print0 -name "*.dll.a" | xargs rm -f #remove stuff that we really don't need rm -fr gcab.exe \ xb-tool.exe \ share/man \ include \ fwupd \ lib/*.a \ lib/pkgconfig/ \ var export WINEPATH="/usr/x86_64-w64-mingw32/sys-root/mingw/bin/;$build/libfwupd/;$build/subprojects/libxmlb/src/;$build/subprojects/gcab/libgcab/" #TODO: fixup tests ninja -C $build test || true fwupd-1.7.5/contrib/ci/centos.sh000077500000000000000000000006411420024370600165350ustar00rootroot00000000000000#!/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 .. \ --werror \ -Dplugin_uefi_capsule=false \ -Dplugin_dell=false \ -Dplugin_modem_manager=false \ -Dplugin_synaptics_mst=true \ -Dplugin_flashrom=true \ -Dintrospection=true \ -Ddocs=gtkdoc \ -Dpkcs7=false \ -Dman=true ninja-build -v ninja-build test -v cd .. fwupd-1.7.5/contrib/ci/check-abi000077500000000000000000000071361420024370600164450ustar00rootroot00000000000000#!/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", "-Db_coverage=false", "-Dgtkdoc=false", "-Ddocs=none", "-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.7.5/contrib/ci/check-deprecated.sh000077500000000000000000000006661420024370600204240ustar00rootroot00000000000000#!/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.7.5/contrib/ci/check-headers.py000077500000000000000000000065051420024370600177530ustar00rootroot00000000000000#!/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 toplevel_headers = ["libfwupd/fwupd.h", "libfwupdplugin/fwupdplugin.h"] toplevel_headers_nopath = [os.path.basename(fn) for fn in toplevel_headers] lib_headers = glob.glob("libfwupd*/*.h") lib_headers.remove("libfwupd/fwupd.h") lib_headers.remove("libfwupdplugin/fwupdplugin.h") 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): 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("{} uses private header {}".format(fn, 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( "{} contains {}, should only use top level includes".format( fn, include ) ) 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( "{} contains both {}".format(fn, ", ".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( "{} contains {} but also includes {}".format( fn, toplevel_fn, include ) ) rc = 1 return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.7.5/contrib/ci/check-license.py000077500000000000000000000023171420024370600177570ustar00rootroot00000000000000#!/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]", 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 lic = __get_license(fn) if not lic: print("{} does not specify a license".format(fn)) rc = 1 continue if not "GPL" in lic: print("{} does not contain LGPL or GPL ({})".format(fn, lic)) rc = 1 continue return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.7.5/contrib/ci/check-null-false-returns.py000077500000000000000000000170531420024370600221020ustar00rootroot00000000000000#!/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): 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.7.5/contrib/ci/check-quirks.py000077500000000000000000000032701420024370600176520ustar00rootroot00000000000000#!/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("{} has leading or trailing whitespace: {}".format(fn, line)) rc = 1 continue if not line or line.startswith("#"): continue if line.startswith("["): if not line.endswith("]"): print("{} has invalid section header: {}".format(fn, line)) rc = 1 continue for deprecated in ["DeviceInstanceId", "Guid"]: if line.find(deprecated) != -1: print("{} has deprecated prefix: {}".format(fn, deprecated)) rc = 1 continue else: sections = line.split(" = ") if len(sections) != 2: print("{} has invalid line: {}".format(fn, line)) rc = 1 continue for section in sections: if section.strip() != section: print("{} has invalid spacing: {}".format(fn, line)) rc = 1 break return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.7.5/contrib/ci/check_missing_translations.sh000077500000000000000000000001251420024370600226460ustar00rootroot00000000000000#!/bin/sh set -e cd po intltool-update -m if [ -f missing ]; then exit 1 fi fwupd-1.7.5/contrib/ci/debian.sh000077500000000000000000000053221420024370600164650ustar00rootroot00000000000000#!/bin/bash set -e set -x export QUBES_OPTION= # remove when tpm2-tss is fixed mkdir -p /usr/include/tss #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 -lR !(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 #if some are missing, we're going to use subproject instead and #packaging CI will fail ./contrib/ci/fwupd_setup_helpers.py install-dependencies -o debian --yes || true if ! dpkg-checkbuilddeps; then ./contrib/ci/ubuntu.sh exit 0 fi #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 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.7.5/contrib/ci/debian_s390x.sh000077500000000000000000000014531420024370600174340ustar00rootroot00000000000000#!/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 .. \ --cross-file s390x_cross.txt \ --werror \ -Dplugin_flashrom=false \ -Dplugin_uefi_capsule=false \ -Dplugin_dell=false \ -Dplugin_modem_manager=false \ -Dplugin_msr=false \ -Dplugin_mtd=false \ -Dplugin_redfish=false \ -Dplugin_powerd=false \ -Dintrospection=false \ -Ddocs=none \ -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.7.5/contrib/ci/dependencies.xml000066400000000000000000001213761420024370600200640ustar00rootroot00000000000000 clang-tools-extra cairo-devel cairo-devel cairo-devel libcairo-dev:s390x cairo-gobject-devel cairo-gobject-devel libcairo-gobject2:s390x json-glib json-glib-devel json-glib-devel json-glib-devel (>= 1.1.1) libjson-glib-dev:s390x (>= 1.1.1) libftdi libftdi-devel libftdi-devel pciutils pciutils-devel pciutils-devel noto-fonts google-noto-sans-cjk-ttc-fonts google-noto-sans-cjk-ttc-fonts noto-fonts-cjk (>= 12) (>= 12) freetype freetype libfreetype6-dev:s390x flashrom-devel ia64 ia64 (>= 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 glib-devel (>= 2.45.8) libglib2.0-dev:s390x (>= 2.45.8) glibc-langpack-en gobject-introspection-devel gobject-introspection-devel gnutls-devel gnutls-devel gnutls-devel libgnutls28-dev:s390x libgnutls28-dev gnutls-utils gnutls-utils gnutls-tools gtk-doc gtk-doc gtk-doc libxmlb libxmlb-devel libxmlb-devel (>= 0.1.13) libxmlb-dev:s390x (>= 0.1.13) libjcat-devel libjcat-devel xz-devel libarchive-devel libarchive-devel libarchive-dev:s390x libarchive-devel efivar efivar-devel efivar-devel libefivar-devel amd64 arm64 armhf i386 amd64 arm64 armhf i386 amd64 arm64 armhf i386 amd64 arm64 armhf i386 gcab libgcab1-devel libgcab1-devel libgcab-dev:s390x gcab-devel libgudev1-devel libgudev1-devel libgudev-1.0-dev:s390x libgusb libgusb-devel libgusb-devel libgusb-devel (>= 0.3.5) libgusb-dev:s390x (>= 0.3.3) libicu-dev:s390x libidn2-0-dev:s390x libsmbios libsmbios-devel libsmbios-devel libsmbios-devel i386 amd64 i386 amd64 libsoup-devel curl libcurl-devel libcurl-devel libcurl-devel libcurl4-gnutls-dev:s390x amd64 arm64 armhf i386 amd64 arm64 armhf i386 pango-devel pango-devel pango-devel polkit polkit polkit polkit (>> 0.105-14) (>> 0.105-14) ModemManager-glib-devel ModemManager-glib-devel modemmanager libmm-glib-dev:s390x libqmi-devel libqmi-devel libqmi libqmi-glib-dev:s390x libmbim-devel libmbim-devel libmbim libmbim-glib-dev:s390x polkit-devel polkit-devel polkit-devel libpolkit-gobject-1-dev:s390x python34-devel python-cairo python3-cairo python-gobject qemu-user sqlite-devel sqlite-devel libsqlite3-dev:s390x sqlite-devel elogind-devel (>= 231) (>= 231) systemd-devel systemd-devel libsystemd-dev:s390x umockdev-devel umockdev-devel vala vala vala vala valgrind-devel valgrind-devel ia64 riscv64 x32 mips sparc64 sh4 ppc64 powerpcspe hppa alpha mips64el armhf armel mipsel m68k ia64 riscv64 x32 mips sparc64 sh4 ppc64 powerpcspe hppa alpha mips64el armhf armel mipsel m68k tpm2-tss 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 fwupd-1.7.5/contrib/ci/fedora.sh000077500000000000000000000040211420024370600164760ustar00rootroot00000000000000#!/bin/bash set -e set -x #get any missing deps from the container ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o fedora #generate a tarball git config tar.tar.xz.command "xz -c" mkdir -p build && pushd build rm -rf * if [ "$QUBES" = "true" ]; then QUBES_MACRO=(--define "qubes_packages 1") fi meson .. \ -Ddocs=none \ -Dman=true \ -Dtests=true \ -Dgusb:tests=false \ -Dplugin_dummy=true \ -Dplugin_flashrom=true \ -Dplugin_modem_manager=false \ -Dplugin_thunderbolt=true \ -Dplugin_uefi_capsule=true \ -Dplugin_dell=true \ -Dplugin_synaptics_mst=true $@ 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 sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf # 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.7.5/contrib/ci/flatpak.py000077500000000000000000000057551420024370600167150ustar00rootroot00000000000000#!/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 = "runtime/%s/x86_64/%s" % (data["runtime"], data["runtime-version"]) sdk = "runtime/%s/x86_64/%s" % (data["sdk"], 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 not "name" in module: continue name = module["name"] if not "fwupd" 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.7.5/contrib/ci/fwupd_setup_helpers.py000077500000000000000000000114031420024370600213450ustar00rootroot00000000000000#!/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 # Minimum version of markdown required MINIMUM_MARKDOWN = (3, 3, 3) def get_possible_profiles(): return ["fedora", "centos", "debian", "ubuntu", "arch", "void"] def detect_profile(): try: import distro target = distro.id() if not target in get_possible_profiles(): target = distro.like() except ModuleNotFoundError: target = "" return target def test_markdown(): 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") sys.exit(not new_enough) def parse_dependencies(OS, SUBOS, requested_type): 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 "type" not in child.attrib or "id" not in child.attrib: continue for distro in child: if "id" not in distro.attrib: continue if distro.attrib["id"] != OS: continue packages = distro.findall("package") for package in packages: if SUBOS: if "variant" not in package.attrib: continue if package.attrib["variant"] != SUBOS: continue if package.text: dep = package.text else: dep = child.attrib["id"] if child.attrib["type"] == requested_type and dep: deps.append(dep) return deps def get_build_dependencies(os, variant): return parse_dependencies(os, variant, "build") 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("\tsupported profiles: %s" % 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.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", "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() # these require variants to be set if not args.variant and (args.os == "ubuntu" or args.os == "debian"): args.variant = os.uname().machine if not command: command = args.command # command to run if command == "test-markdown": test_markdown() 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.7.5/contrib/ci/generate_debian.py000077500000000000000000000132321420024370600203540ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys import xml.etree.ElementTree as etree def parse_control_dependencies(requested_type): TARGET = os.getenv("OS") QUBES = os.getenv("QUBES") deps = [] dep = "" if TARGET == "": print("Missing OS environment variable") sys.exit(1) OS = TARGET SUBOS = "" if TARGET: split = TARGET.split("-") if len(split) >= 2: OS = split[0] SUBOS = split[1] else: import lsb_release OS = lsb_release.get_distro_information()["ID"].lower() import platform SUBOS = platform.machine() tree = etree.parse(os.path.join(os.path.dirname(sys.argv[0]), "dependencies.xml")) root = tree.getroot() for child in root: if not "type" in child.attrib or not "id" in child.attrib: continue for distro in child: if not "id" in distro.attrib: continue if distro.attrib["id"] != OS: continue control = distro.find("control") if control is None: continue packages = distro.findall("package") for package in packages: if SUBOS: if not "variant" in package.attrib: continue if package.attrib["variant"] != SUBOS: continue if package.text: dep = package.text else: dep = child.attrib["id"] if child.attrib["type"] == requested_type and dep: version = control.find("version") if version is not None: dep = "%s %s" % (dep, version.text) inclusions = control.findall("inclusive") if inclusions: for i in range(0, len(inclusions)): prefix = "" suffix = " " if i == 0: prefix = " [" if i == len(inclusions) - 1: suffix = "]" dep = "%s%s%s%s" % (dep, prefix, inclusions[i].text, suffix) exclusions = control.findall("exclusive") if exclusions: for i in range(0, len(exclusions)): prefix = "!" suffix = " " if i == 0: prefix = " [!" if i == len(exclusions) - 1: suffix = "]" dep = "%s%s%s%s" % (dep, prefix, exclusions[i].text, suffix) deps.append(dep) return deps, 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("Missing file %s" % control_in) sys.exit(1) with open(control_in, "r") as rfd: lines = rfd.readlines() deps, QUBES = parse_control_dependencies("build") 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 line.startswith("Build-Depends: %%%DYNAMIC%%%"): wfd.write("Build-Depends:\n") for i in range(0, len(deps)): wfd.write("\t%s,\n" % deps[i]) 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("Missing file %s" % 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 += ["%s" % 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("Copyright: %s\n" % copyrights) 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.7.5/contrib/ci/generate_dependencies.py000077700000000000000000000000001420024370600262102fwupd_setup_helpers.pyustar00rootroot00000000000000fwupd-1.7.5/contrib/ci/generate_docker.py000077500000000000000000000061521420024370600204040ustar00rootroot00000000000000#!/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, "build") f = os.path.join(directory, "Dockerfile-%s.in" % OS) if not os.path.exists(f): print("Missing input file %s for %s" % (f, 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("\t%s \\\n" % deps[i]) else: wfd.write("\t%s \n" % deps[i]) 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("RUN dpkg --add-architecture %s\n" % SUBOS) elif line == "%%%OS%%%\n": wfd.write("ENV OS %s\n" % TARGET) else: wfd.write(line) wfd.flush() if len(sys.argv) == 2 and sys.argv[1] == "build": cmd = get_container_cmd() args = [cmd, "build", "-t", "fwupd-%s" % TARGET] if "http_proxy" in os.environ: args += ["--build-arg=http_proxy=%s" % os.environ["http_proxy"]] if "https_proxy" in os.environ: args += ["--build-arg=https_proxy=%s" % os.environ["https_proxy"]] args += ["-f", "./Dockerfile", "."] subprocess.check_call(args) fwupd-1.7.5/contrib/ci/generate_news.py000077500000000000000000000014071420024370600201070ustar00rootroot00000000000000#!/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.7.5/contrib/ci/get_test_firmware.sh000077500000000000000000000004351420024370600207550ustar00rootroot00000000000000#!/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.7.5/contrib/ci/oss-fuzz.py000077500000000000000000000340521420024370600170630ustar00rootroot00000000000000#!/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"] # 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 add_work_includedir(self, value: str) -> None: """add a CFLAG""" self.cflags.append("-I{}/{}".format(self.builddir, value)) def add_src_includedir(self, value: str) -> None: """add a CFLAG""" self.cflags.append("-I{}/{}".format(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("building {} into {}".format(src, 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, "{}".format(dst)) def link(self, objs: List[str], dst: str) -> None: """link multiple obects 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("building {} into {}".format(",".join(objs), 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("failed to find {}".format(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("building {} into {}".format(fn_src, fn_dst)) try: argv = [ "sudo", "build/src/fwupdtool", "firmware-build", fn_src, fn_dst, ] subprocess.run(argv, check=True) except subprocess.CalledProcessError as e: print("tried to run: `{}` and got {}".format(" ".join(argv), 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("writing {}".format(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("#define {} {}\n".format(key, value)) else: f.write('#define {} "{}"\n'.format(key, value)) else: f.write("#define {}\n".format(key)) 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("assembling {}".format(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 "{}*.bin".format(name) self.pattern = pattern or "{}-firmware".format(name) @property def new_gtype(self) -> str: return "fu_{}_new".format(self.pattern).replace("-", "_") @property def header(self) -> str: return "fu-{}.h".format(self.pattern) def _build(bld: Builder) -> None: # 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", ], ) 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_PLUGINDIR": "/tmp", "FWUPD_SYSCONFDIR": "/tmp", "HAVE_REALPATH": None, "PACKAGE_NAME": "fwupd", "PACKAGE_VERSION": "0.0.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): built_objs.append(bld.compile(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("dfuse"), Fuzzer("fmap"), Fuzzer("ihex"), Fuzzer("srec"), Fuzzer("efi-firmware-filesystem", pattern="efi-firmware-filesystem"), Fuzzer("efi-firmware-volume", pattern="efi-firmware-volume"), 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, "{}_fuzzer".format(fzr.name)) bld.mkfuzztargets( os.path.join( bld.srcdir, "fwupd", "libfwupdplugin", "tests", "{}*.builder.xml".format(fzr.name), ) ) bld.makezip( "{}_fuzzer_seed_corpus.zip".format(fzr.name), "fwupd/libfwupdplugin/tests/{}".format(fzr.globstr), ) # plugins for fzr in [ Fuzzer("acpi-phat", pattern="acpi-phat"), Fuzzer("bcm57xx"), Fuzzer("ccgx-dmc", srcdir="ccgx", globstr="ccgx-dmc*.bin"), Fuzzer("ccgx"), Fuzzer("cros-ec"), Fuzzer("ebitdo"), Fuzzer("elanfp"), Fuzzer("elantp"), 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("fwupd/plugins/{}".format(fzr.srcdir)): fuzz_objs.append(bld.compile(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, "{}_fuzzer".format(fzr.name)) bld.mkfuzztargets( os.path.join( bld.srcdir, "fwupd", "plugins", fzr.srcdir, "tests", "{}*.builder.xml".format(fzr.name), ) ) bld.makezip( "{}_fuzzer_seed_corpus.zip".format(fzr.name), "fwupd/plugins/{}/tests/{}".format(fzr.srcdir, 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"], 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.7.5/contrib/ci/out-of-tree-link/000077500000000000000000000000001420024370600200035ustar00rootroot00000000000000fwupd-1.7.5/contrib/ci/out-of-tree-link/.gitignore000066400000000000000000000000061420024370600217670ustar00rootroot00000000000000build fwupd-1.7.5/contrib/ci/out-of-tree-link/fwupd.c000066400000000000000000000003011420024370600212660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #undef NDEBUG #include #include int main(void) { assert(fwupd_error_to_string(FWUPD_ERROR_NOTHING_TO_DO) != NULL); return 0; } fwupd-1.7.5/contrib/ci/out-of-tree-link/fwupdplugin.c000066400000000000000000000003241420024370600225120ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #undef NDEBUG #include #include int main(void) { assert(fu_common_vercmp_full("1.0", "2.1", FWUPD_VERSION_FORMAT_NUMBER) < 0); return 0; } fwupd-1.7.5/contrib/ci/out-of-tree-link/meson.build000066400000000000000000000007501420024370600221470ustar00rootroot00000000000000 project('out-of-tree-link', 'c', license : 'LGPL-2.1+', ) fwupd = dependency('fwupd') fwupdplugin = dependency('fwupdplugin') env = environment() env.set('G_DEBUG', 'fatal-criticals') e = executable( 'fwupd', sources : [ 'fwupd.c' ], dependencies : [ fwupd ], ) test('fwupd', e, timeout: 60, env: env) e = executable( 'fwupdplugin', sources : [ 'fwupdplugin.c' ], dependencies : [ fwupdplugin ], ) test('fwupdplugin', e, timeout: 60, env: env) fwupd-1.7.5/contrib/ci/qt5-thread-test/000077500000000000000000000000001420024370600176355ustar00rootroot00000000000000fwupd-1.7.5/contrib/ci/qt5-thread-test/meson.build000066400000000000000000000010471420024370600220010ustar00rootroot00000000000000project('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.7.5/contrib/ci/qt5-thread-test/qt-thread-test.cpp000066400000000000000000000013101420024370600232020ustar00rootroot00000000000000/* * 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.7.5/contrib/ci/s390x_cross.txt000066400000000000000000000004221420024370600175400ustar00rootroot00000000000000[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.7.5/contrib/ci/snap.sh000077500000000000000000000000661420024370600162040ustar00rootroot00000000000000#!/bin/sh set -e mkdir -p /build cd /build snapcraft fwupd-1.7.5/contrib/ci/snapcraft-wrapper000077500000000000000000000004671420024370600202760ustar00rootroot00000000000000#!/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.7.5/contrib/ci/ubuntu.sh000077500000000000000000000016141420024370600165650ustar00rootroot00000000000000#!/bin/sh set -e set -x #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #check for and install missing dependencies ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o ubuntu #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=docgen -Dgusb:tests=false -Dplugin_platform_integrity=true --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.7.5/contrib/ci/void.sh000077500000000000000000000006101420024370600161770ustar00rootroot00000000000000#!/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 #build rm -rf build meson build \ -Dgusb:tests=false \ -Dgcab:docs=false \ -Dconsolekit=false \ -Dsystemd=false \ -Doffline=false \ -Delogind=true ninja -C build test -v fwupd-1.7.5/contrib/codespell.cfg000066400000000000000000000003171420024370600167430ustar00rootroot00000000000000[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 fwupd-1.7.5/contrib/debian/000077500000000000000000000000001420024370600155315ustar00rootroot00000000000000fwupd-1.7.5/contrib/debian/README.Debian000066400000000000000000000013421420024370600175720ustar00rootroot00000000000000signed 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.7.5/contrib/debian/README.source000066400000000000000000000004201420024370600177040ustar00rootroot00000000000000fwupd 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.7.5/contrib/debian/clean000066400000000000000000000000331420024370600165320ustar00rootroot00000000000000gtk-doc.make m4/gtk-doc.m4 fwupd-1.7.5/contrib/debian/compat000066400000000000000000000000031420024370600167300ustar00rootroot0000000000000012 fwupd-1.7.5/contrib/debian/control.in000066400000000000000000000152561420024370600175520ustar00rootroot00000000000000Source: fwupd Priority: optional Maintainer: Debian EFI Uploaders: Steve McIntyre <93sam@debian.org>, Matthias Klumpp , Mario Limonciello Build-Depends: %%%DYNAMIC%%% Build-Depends-Indep: libglib2.0-doc 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: libfwupdplugin5 Section: libs Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends} Multi-Arch: same Description: Firmware update daemon plugin 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 for the interface between daemon and plugins. 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}, shared-mime-info Recommends: python3, bolt, dbus, secureboot-db, udisks2, fwupd-unsigned, 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, 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}, 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. Package: libfwupdplugin-dev Architecture: linux-any Multi-Arch: same Depends: libfwupdplugin5 (= ${binary:Version}), gir1.2-fwupdplugin-1.0 (= ${binary:Version}), libarchive-dev, libcurl4-gnutls-dev, libfwupd-dev (= ${binary:Version}), libgcab-dev, libglib2.0-dev (>= 2.45.8), libgudev-1.0-dev, libgusb-dev (>= 0.3.5), libjcat-dev, libjson-glib-dev (>= 1.1.1), libxmlb-dev (>= 0.1.13), valgrind [!ia64 !riscv64 !x32 !mips !sparc64 !sh4 !ppc64 !powerpcspe !hppa !alpha !mips64el !armhf !armel !mipsel !m68k], ${misc:Depends} Section: libdevel Description: development files for libfwupdplugin 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 libfwupdplugin Package: gir1.2-fwupdplugin-1.0 Architecture: linux-any Multi-Arch: same Depends: ${misc:Depends}, ${gir:Depends} Section: introspection Description: GObject introspection data for libfwupdplugin This package provides the introspection data for libfwupdplugin. . It can be used by packages using the GIRepository format to generate dynamic bindings. fwupd-1.7.5/contrib/debian/control.qubes.in000066400000000000000000000002451420024370600206600ustar00rootroot00000000000000Package: 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.7.5/contrib/debian/copyright.in000066400000000000000000000201061420024370600200700ustar00rootroot00000000000000Format: 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.7.5/contrib/debian/docs000066400000000000000000000000011420024370600163730ustar00rootroot00000000000000 fwupd-1.7.5/contrib/debian/fwupd-doc.install000066400000000000000000000000161420024370600210060ustar00rootroot00000000000000usr/share/*docfwupd-1.7.5/contrib/debian/fwupd-qubes-vm-whonix.install000066400000000000000000000001351420024370600233140ustar00rootroot00000000000000usr/libexec/qubes-fwupd/fwupd_download_updates.py usr/libexec/qubes-fwupd/fwupd_common_vm.py fwupd-1.7.5/contrib/debian/fwupd-qubes-vm-whonix.postinst000066400000000000000000000005221420024370600235310ustar00rootroot00000000000000#!/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.7.5/contrib/debian/fwupd-qubes-vm-whonix.postrm000066400000000000000000000002531420024370600231730ustar00rootroot00000000000000#!/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.7.5/contrib/debian/fwupd-tests.install000066400000000000000000000010301420024370600214000ustar00rootroot00000000000000#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/share/fwupd/device-tests/*.json usr/libexec/installed-tests/fwupd/fwupd.sh usr/libexec/installed-tests/fwupd/*-self-test usr/lib/*/fwupd-plugins-*/libfu_plugin_test.so usr/lib/*/fwupd-plugins-*/libfu_plugin_invalid.so debian/lintian/fwupd-tests usr/share/lintian/overrides etc/fwupd/remotes.d/fwupd-tests.conf fwupd-1.7.5/contrib/debian/fwupd-tests.postinst000066400000000000000000000011101420024370600216140ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# #only enable on installation not upgrade if [ "$1" = configure ] && [ -z "$2" ]; then if [ -f /etc/fwupd/daemon.conf ]; then if [ "$CI" = "true" ]; then sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf else echo "To enable test suite, modify /etc/fwupd/daemon.conf" fi fi if [ -f /etc/fwupd/remotes.d/fwupd-tests.conf ]; then if [ "$CI" = "true" ]; then sed "s,^Enabled=false,Enabled=true," -i /etc/fwupd/remotes.d/fwupd-tests.conf else echo "To enable test suite, enable fwupd-tests remote" fi fi fi fwupd-1.7.5/contrib/debian/fwupd-tests.postrm000066400000000000000000000004671420024370600212730ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ "$1" = remove -o "$1" = purge ]; then if [ -f /etc/fwupd/daemon.conf ]; then if [ "$CI" = "true" ]; then sed "s,^DisabledPlugins=,DisabledPlugins=test;invalid," -i /etc/fwupd/daemon.conf else echo "To disable test suite, modify /etc/fwupd/daemon.conf" fi fi fi fwupd-1.7.5/contrib/debian/fwupd.dirs000066400000000000000000000000301420024370600175320ustar00rootroot00000000000000var/cache/app-info/xmls fwupd-1.7.5/contrib/debian/fwupd.install000066400000000000000000000011641420024370600202500ustar00rootroot00000000000000usr/bin/* etc/* usr/share/bash-completion usr/share/fish/vendor_completions.d usr/share/fwupd/*.* usr/share/fwupd/metainfo/* usr/share/fwupd/quirks.d/* usr/share/fwupd/remotes.d/* usr/share/dbus-1/* usr/share/icons/* usr/share/polkit-1/* usr/share/locale usr/share/metainfo/* usr/libexec/fwupd/* usr/share/man/man1/* lib/systemd/system/* lib/systemd/system-preset/* lib/systemd/system-shutdown/* lib/udev/rules.d/* data/daemon.conf etc/fwupd debian/fwupd.pkla /var/lib/polkit-1/localauthority/10-vendor.d usr/lib/*/fwupd-plugins-*/*.so debian/lintian/fwupd usr/share/lintian/overrides obj*/data/motd/85-fwupd /etc/update-motd.d fwupd-1.7.5/contrib/debian/fwupd.maintscript000066400000000000000000000004251420024370600211360ustar00rootroot00000000000000 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~ fwupd-1.7.5/contrib/debian/fwupd.pkla000066400000000000000000000002071420024370600175260ustar00rootroot00000000000000[Call internal fwupd actions] Identity=unix-group:admin;unix-group:sudo Action=org.freedesktop.fwupd.update-internal ResultActive=yes fwupd-1.7.5/contrib/debian/fwupd.postinst000066400000000000000000000033111420024370600204610ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/ata.conf 1.5.5~ -- "$@" fi #Perform transition from /etc/fwupd/uefi.conf to /etc/fwupd/uefi_capsule.conf if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then ORIGINAL=/etc/fwupd/uefi.conf NEW=/etc/fwupd/uefi_capsule.conf #If already upgraded this file won't exist #If in the middle of an upgrade: # -> If unmodified then preinst would have renamed to /etc/fwupd/uefi.conf.dpkg-remove # -> If modified, we need to do an in-place upgrade with sed if [ -f $ORIGINAL ]; then sed "s,\[uefi\],\[uefi_capsule\]," -i $ORIGINAL fi dpkg-maintscript-helper mv_conffile $ORIGINAL $NEW 1.5.5~ -- "$@" fi # Clean up from fwupdate->fwupd transition # This can be removed after bullseye and focal are released EFIDIR=$(awk '/^ID=/ {gsub(/"/,""); split($$0,a,"="); print tolower(a[2])}' /etc/os-release) if [ "${DPKG_MAINTSCRIPT_ARCH}" = "amd64" ]; then EFI_NAME=x64 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "i386" ]; then EFI_NAME=ia32 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "arm64" ]; then EFI_NAME=aa64 elif [ "${DPKG_MAINTSCRIPT_ARCH}" = "armhf" ]; then EFI_NAME=arm fi rm -f /boot/efi/EFI/$EFIDIR/fwup$EFI_NAME.efi rm -f /var/lib/fwupdate/done rm -f /var/cache/fwupdate/done for dir in /var/cache/fwupdate /var/lib/fwupdate; do if [ -d $dir ]; then rmdir --ignore-fail-on-non-empty $dir || true fi done fwupd-1.7.5/contrib/debian/fwupd.postrm000066400000000000000000000014651420024370600201320ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ "$1" = purge ]; then rm -rf /var/lib/fwupd/gnupg rm -f /var/cache/app-info/xmls/fwupd.xml fi if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/ata.conf 1.5.5~ -- "$@" fi #Perform transition from /etc/fwupd/uefi.conf to /etc/fwupd/uefi_capsule.conf if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then ORIGINAL=/etc/fwupd/uefi.conf NEW=/etc/fwupd/uefi_capsule.conf dpkg-maintscript-helper mv_conffile $ORIGINAL $NEW 1.5.5~ -- "$@" fi fwupd-1.7.5/contrib/debian/fwupd.preinst000066400000000000000000000016771420024370600202770ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/fwupd.conf 1.0.0~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/fwupd/ata.conf 1.5.5~ -- "$@" fi #Perform transition from /etc/fwupd/uefi.conf to /etc/fwupd/uefi_capsule.conf if dpkg-maintscript-helper supports mv_conffile 2>/dev/null; then ORIGINAL=/etc/fwupd/uefi.conf NEW=/etc/fwupd/uefi_capsule.conf dpkg-maintscript-helper mv_conffile $ORIGINAL $NEW 1.5.5~ -- "$@" fi # 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.7.5/contrib/debian/gbp.conf000066400000000000000000000001611420024370600171460ustar00rootroot00000000000000[DEFAULT] debian-branch = debian upstream-tag = %(version)s [buildpackage] sign-tags = True dist = experimental fwupd-1.7.5/contrib/debian/gir1.2-fwupd-2.0.install000066400000000000000000000000531420024370600215410ustar00rootroot00000000000000usr/lib/*/girepository-1.0/Fwupd-*.typelib fwupd-1.7.5/contrib/debian/gir1.2-fwupdplugin-1.0.install000066400000000000000000000000611420024370600227560ustar00rootroot00000000000000usr/lib/*/girepository-1.0/FwupdPlugin-*.typelib fwupd-1.7.5/contrib/debian/libfwupd-dev.install000066400000000000000000000003101420024370600215030ustar00rootroot00000000000000usr/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.7.5/contrib/debian/libfwupd2.install000066400000000000000000000000301420024370600210100ustar00rootroot00000000000000usr/lib/*/libfwupd.so.* fwupd-1.7.5/contrib/debian/libfwupdplugin-dev.install000066400000000000000000000002501420024370600227250ustar00rootroot00000000000000usr/include/fwupd-1/fwupdplugin.h usr/include/fwupd-1/libfwupdplugin usr/lib/*/libfwupdplugin.so usr/lib/*/pkgconfig/fwupdplugin.pc usr/share/gir-1.0/FwupdPlugin-*.gir fwupd-1.7.5/contrib/debian/libfwupdplugin5.install000066400000000000000000000000361420024370600222400ustar00rootroot00000000000000usr/lib/*/libfwupdplugin.so.* fwupd-1.7.5/contrib/debian/lintian/000077500000000000000000000000001420024370600171675ustar00rootroot00000000000000fwupd-1.7.5/contrib/debian/lintian/fwupd000066400000000000000000000003201420024370600202320ustar00rootroot00000000000000#these are intended to be D-bus activated fwupd binary: systemd-service-file-missing-install-key lib/systemd/system/* #see Debian bug 896012 fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-*/* fwupd-1.7.5/contrib/debian/lintian/fwupd-tests000066400000000000000000000003021420024370600213720ustar00rootroot00000000000000#see Debian bug 896012 fwupd-tests: library-not-linked-against-libc usr/lib/*/fwupd-plugins-*/* fwupd-tests: shared-library-lacks-prerequisites usr/lib/*/fwupd-plugins-*/libfu_plugin_invalid.so fwupd-1.7.5/contrib/debian/not-installed000066400000000000000000000002011420024370600202220ustar00rootroot00000000000000usr/libexec/qubes-fwupd/fwupd_usbvm_validate.py usr/sbin/qubes-fwupdmgr usr/share/qubes-fwupd/src/* usr/share/qubes-fwupd/test/* fwupd-1.7.5/contrib/debian/rules000077500000000000000000000045101420024370600166110ustar00rootroot00000000000000#!/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=true else CONFARGS += -Dplugin_flashrom=false endif ifeq (yes,$(shell pkg-config --exists libsmbios_c && echo yes)) CONFARGS += -Dplugin_dell=true else CONFARGS += -Dplugin_dell=false endif ifeq (yes,$(shell pkg-config --exists efivar && echo yes)) CONFARGS += -Dplugin_uefi_capsule=true -Defi_binary=false else CONFARGS += -Dplugin_uefi_capsule=false endif ifneq ($(filter $(DEB_HOST_ARCH_CPU),i386 amd64),) CONFARGS += -Dplugin_msr=true else CONFARGS += -Dplugin_msr=false endif ifneq ($(QUBES_OPTION),) CONFARGS += -Dqubes=true endif #Needs a MIR ifeq (yes,$(shell dpkg-vendor --derives-from Ubuntu && echo yes)) CONFARGS += -Dplugin_logitech_bulkcontroller=false endif CONFARGS += -Dplugin_dummy=true -Dplugin_powerd=false -Ddocs=gtkdoc -Dsupported_build=true -Dplugin_modem_manager=true %: 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 #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 dh_missing -a --fail-missing #this is placed in fwupd-tests rm -f debian/fwupd/usr/lib/*/fwupd-plugins-*/libfu_plugin_test.so rm -f debian/fwupd/usr/lib/*/fwupd-plugins-*/libfu_plugin_test_ble.so rm -f debian/fwupd/usr/lib/*/fwupd-plugins-*/libfu_plugin_invalid.so rm -f debian/fwupd/etc/fwupd/remotes.d/fwupd-tests.conf 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.7.5/contrib/debian/source/000077500000000000000000000000001420024370600170315ustar00rootroot00000000000000fwupd-1.7.5/contrib/debian/source/format000066400000000000000000000000141420024370600202370ustar00rootroot000000000000003.0 (quilt) fwupd-1.7.5/contrib/debian/source/lintian-overrides000066400000000000000000000001231420024370600224060ustar00rootroot00000000000000#github doesn't have these fwupd source: debian-watch-does-not-check-gpg-signature fwupd-1.7.5/contrib/debian/source/options000066400000000000000000000000641420024370600204470ustar00rootroot00000000000000extend-diff-ignore=".vscode|venv|subprojects|build" fwupd-1.7.5/contrib/debian/tests/000077500000000000000000000000001420024370600166735ustar00rootroot00000000000000fwupd-1.7.5/contrib/debian/tests/ci000077500000000000000000000003041420024370600172110ustar00rootroot00000000000000#!/bin/sh set -e sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf sed "s,^VerboseDomains=.*,VerboseDomains=*," -i /etc/fwupd/daemon.conf gnome-desktop-testing-runner fwupd fwupd-1.7.5/contrib/debian/tests/control000066400000000000000000000002231420024370600202730ustar00rootroot00000000000000Tests: ci Restrictions: needs-root Tests: libfwupd-dev Depends: build-essential, libfwupd-dev, pkg-config Restrictions: allow-stderr, superficial fwupd-1.7.5/contrib/debian/tests/libfwupd-dev000077500000000000000000000013551420024370600212150ustar00rootroot00000000000000#!/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.7.5/contrib/debian/watch000066400000000000000000000003531420024370600165630ustar00rootroot00000000000000# 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.7.5/contrib/firmware_packager/000077500000000000000000000000001420024370600177605ustar00rootroot00000000000000fwupd-1.7.5/contrib/firmware_packager/README.md000066400000000000000000000113701420024370600212410ustar00rootroot00000000000000# 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.7.5/contrib/firmware_packager/__init__.py000066400000000000000000000000001420024370600220570ustar00rootroot00000000000000fwupd-1.7.5/contrib/firmware_packager/add_capsule_header.py000077500000000000000000000047241420024370600241200ustar00rootroot00000000000000#!/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("GUID: %s" % guid_mixed) print("HdrSz: 0x%04x" % hdrsz_old) print("Flags: 0x%04x" % flags) print("PayloadSz: 0x%04x" % imgsz) 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("Wrote capsule %s" % outfile) print("GUID: %s" % guid) print("HdrSz: 0x%04x" % hdrsz) print("Flags: 0x%04x" % flags) print("PayloadSz: 0x%04x" % imgsz) 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.7.5/contrib/firmware_packager/add_dfu_header.py000077500000000000000000000030501420024370600232310ustar00rootroot00000000000000#!/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", "-o{}".format(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("Using temp directory {}".format(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.7.5/contrib/firmware_packager/install_dell_bios_exe.py000077500000000000000000000066651420024370600246750ustar00rootroot00000000000000#!/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 install, check_exists 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("Generated CAB file %s" % 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("Installing to %s" % 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 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) 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) prompt_reboot() fwupd-1.7.5/contrib/firmware_packager/meson.build000066400000000000000000000010241420024370600221170ustar00rootroot00000000000000if 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.7.5/contrib/firmware_packager/simple_client.py000077500000000000000000000116361420024370600231730ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """A simple fwupd frontend""" import sys import os 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("\nUpdating %s" % 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"], help="What to do", ) parser.add_argument("cab", nargs="?", help="CAB file") parser.add_argument("deviceid", nargs="?", help="DeviceID 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 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 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("%s" % glib_err) sys.exit(1) print("\n") 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("%s doesn't exist or isn't a file" % cab) 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) fwupd-1.7.5/contrib/fix_translations.py000077500000000000000000000023231420024370600202530ustar00rootroot00000000000000#!/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.7.5/contrib/flatpak/000077500000000000000000000000001420024370600157315ustar00rootroot00000000000000fwupd-1.7.5/contrib/freebsd/000077500000000000000000000000001420024370600157215ustar00rootroot00000000000000fwupd-1.7.5/contrib/freebsd/Makefile000066400000000000000000000032341420024370600173630ustar00rootroot00000000000000# 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= gtkdoc-scan:textproc/gtk-doc \ 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 \ libgcab-1.0.so:archivers/gcab \ 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 \ 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=false \ -Dplugin_amt=false \ -Dplugin_dell=false \ -Dplugin_emmc=false \ -Dplugin_nvme=false \ -Dplugin_parade_lspcon=false \ -Dplugin_redfish=false \ -Dplugin_synaptics_mst=false \ -Dplugin_synaptics_rmi=false \ -Dplugin_realtek_mst=false \ -Dplugin_thunderbolt=false \ -Dplugin_tpm=false \ -Dplugin_bcm57xx=false \ -Dplugin_ep963x=false \ -Dpolkit=false \ -Dsystemd=false \ -Doffline=false \ -Dtests=false \ -Ddocs=gtkdoc \ -Defi_binary=false .include fwupd-1.7.5/contrib/freebsd/pkg-descr000066400000000000000000000006321420024370600175240ustar00rootroot00000000000000Make 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.7.5/contrib/fwupd.spec.in000066400000000000000000000443321420024370600167230ustar00rootroot00000000000000%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 %global fwupdplugin_version @FWUPD_PLUGINVER@ # 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 enable_dummy 1 %global __meson_wrap_mode nodownload # fwupd.efi is only available on these arches %ifarch x86_64 aarch64 %global have_uefi 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 # libsmbios is only available on x86 %ifarch x86_64 %global have_dell 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: LGPLv2+ 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: libgcab1-devel 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: sqlite-devel BuildRequires: systemd >= %{systemd_version} BuildRequires: systemd-devel BuildRequires: libarchive-devel BuildRequires: gobject-introspection-devel BuildRequires: gcab %ifarch %{valgrind_arches} BuildRequires: valgrind BuildRequires: valgrind-devel %endif BuildRequires: gtk-doc BuildRequires: gnutls-devel BuildRequires: gnutls-utils BuildRequires: meson BuildRequires: json-glib-devel >= %{json_glib_version} BuildRequires: vala BuildRequires: bash-completion BuildRequires: git-core %if 0%{?have_flashrom} BuildRequires: flashrom-devel >= 1.2-2 %endif %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: efivar-devel >= 33 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 %if 0%{?have_dell} BuildRequires: efivar-devel >= 33 BuildRequires: libsmbios-devel >= 2.3.0 %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: bubblewrap Requires: shared-mime-info %if 0%{?rhel} > 7 || 0%{?fedora} > 28 Recommends: python3 %endif Obsoletes: fwupd-sign < 0.1.6 Obsoletes: libebitdo < 0.7.5-3 Obsoletes: libdfu < 1.0.0 Obsoletes: fwupd-labels < 1.1.0-1 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%{?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 %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 %description qubes-vm fwupd wrapper for Qubes OS %endif %prep %autosetup -p1 %build %meson \ %if 0%{?enable_ci} --werror \ %endif -Ddocs=gtkdoc \ %if 0%{?enable_tests} -Dtests=true \ %else -Dtests=false \ %endif %if 0%{?enable_dummy} -Dplugin_dummy=true \ %else -Dplugin_dummy=false \ %endif %if 0%{?have_flashrom} -Dplugin_flashrom=true \ %else -Dplugin_flashrom=false \ %endif %if 0%{?have_msr} -Dplugin_msr=true \ %else -Dplugin_msr=false \ %endif -Dplugin_thunderbolt=true \ %if 0%{?have_uefi} -Dplugin_uefi_capsule=true \ -Dplugin_uefi_pk=true \ -Dplugin_tpm=true \ -Defi_binary=false \ %else -Dplugin_uefi_capsule=false \ -Dplugin_uefi_pk=false \ -Dplugin_tpm=false \ %endif %if 0%{?have_dell} -Dplugin_dell=true \ -Dplugin_synaptics_mst=true \ %else -Dplugin_dell=false \ -Dplugin_synaptics_mst=false \ %endif %if 0%{?have_modem_manager} -Dplugin_modem_manager=true \ %else -Dplugin_modem_manager=false \ %endif %if 0%{?qubes_packages} -Dqubes=true \ %endif -Dman=true \ -Dbluez=true \ -Dplugin_powerd=false \ -Dsupported_build=true %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 # 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 %postun %systemd_postun_with_restart fwupd.service %files -f %{name}.lang %doc README.md AUTHORS %license COPYING %config(noreplace)%{_sysconfdir}/fwupd/daemon.conf %if 0%{?have_uefi} %config(noreplace)%{_sysconfdir}/fwupd/uefi_capsule.conf %endif %config(noreplace)%{_sysconfdir}/fwupd/redfish.conf %config(noreplace)%{_sysconfdir}/fwupd/thunderbolt.conf %dir %{_libexecdir}/fwupd %{_libexecdir}/fwupd/fwupd %ifarch i686 x86_64 %{_libexecdir}/fwupd/fwupd-detect-cet %endif %{_libexecdir}/fwupd/fwupdoffline %if 0%{?have_uefi} %{_bindir}/fwupdate %endif %{_bindir}/dfu-tool %if 0%{?have_uefi} %{_bindir}/dbxtool %endif %{_bindir}/fwupdmgr %{_bindir}/fwupdtool %{_bindir}/fwupdagent %dir %{_sysconfdir}/fwupd %dir %{_sysconfdir}/fwupd/remotes.d %if 0%{?have_dell} %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/dell-esrt.conf %endif %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs-testing.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/vendor.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 /usr/lib/modules-load.d/fwupd-redfish.conf %{_datadir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/bash-completion/completions/fwupdmgr %{_datadir}/bash-completion/completions/fwupdtool %{_datadir}/bash-completion/completions/fwupdagent %{_datadir}/fish/vendor_completions.d/fwupdmgr.fish %{_datadir}/fwupd/metainfo/org.freedesktop.fwupd*.metainfo.xml %if 0%{?have_dell} %{_datadir}/fwupd/remotes.d/dell-esrt/metadata.xml %endif %{_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/fwupdagent.1* %{_mandir}/man1/dfu-tool.1* %if 0%{?have_uefi} %{_mandir}/man1/dbxtool.* %endif %{_mandir}/man1/fwupdmgr.1* %if 0%{?have_uefi} %{_mandir}/man1/fwupdate.1* %endif %{_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 %{_presetdir}/fwupd-refresh.preset %{_unitdir}/system-update.target.wants/ %dir %{_localstatedir}/lib/fwupd %dir %{_localstatedir}/cache/fwupd %dir %{_datadir}/fwupd/quirks.d %{_datadir}/fwupd/quirks.d/*.quirk %{_datadir}/doc/fwupd/builder/README.md %if 0%{?have_uefi} %{_sysconfdir}/grub.d/35_fwupd %endif %{_libdir}/libfwupd.so.2* %{_libdir}/libfwupdplugin.so.%{fwupdplugin_version}* %{_libdir}/girepository-1.0/Fwupd-2.0.typelib %{_libdir}/girepository-1.0/FwupdPlugin-1.0.typelib /usr/lib/udev/rules.d/*.rules /usr/lib/systemd/system-shutdown/fwupd.shutdown %dir %{_libdir}/fwupd-plugins-%{fwupdplugin_version} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_acpi_dmar.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_acpi_facp.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_acpi_phat.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_amt.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_analogix.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ata.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_bcm57xx.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ccgx.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_colorhug.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so %if 0%{?have_dell} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_dell.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_dell_esrt.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_dell_dock.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_dfu.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_dfu_csr.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ebitdo.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_elantp.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_elanfp.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_emmc.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ep963x.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fastboot.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_fresco_pd.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_hailuck.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_iommu.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_jabra.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_lenovo_thinklmi.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_linux_lockdown.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_linux_sleep.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_linux_swap.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_linux_tainted.so %if 0%{?have_msr} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_msr.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_mtd.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_nitrokey.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_nordic_hid.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_nvme.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_optionrom.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_parade_lspcon.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_pci_bcr.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_pci_mei.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_pixart_rf.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_realtek_mst.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_redfish.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_rts54hid.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_rts54hub.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_steelseries.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_superio.so %if 0%{?have_dell} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_mst.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_cape.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_cxaudio.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_prometheus.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_synaptics_rmi.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_system76_launch.so %if 0%{?enable_dummy} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_test.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_test_ble.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_invalid.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_thelio_io.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_thunderbolt.so %if 0%{?have_uefi} %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_tpm.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_bios.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_capsule.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_dbx.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_pk.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uefi_recovery.so %endif %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_usi_dock.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logind.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_bulkcontroller.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_logitech_hidpp.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_uf2.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_upower.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_vli.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_wacom_raw.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_wacom_usb.so %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_goodixmoc.so %ghost %{_localstatedir}/lib/fwupd/gnupg %if 0%{?have_modem_manager} %files plugin-modem-manager %{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_modem_manager.so %endif %if 0%{?have_flashrom} %files plugin-flashrom %{_libdir}/fwupd-plugins-%{fwupdplugin_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}/gir-1.0/FwupdPlugin-1.0.gir %{_datadir}/gtk-doc/html/fwupd %{_datadir}/vala/vapi %{_includedir}/fwupd-1 %{_libdir}/libfwupd*.so %{_libdir}/pkgconfig/fwupd.pc %{_libdir}/pkgconfig/fwupdplugin.pc %files tests %if 0%{?enable_tests} %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/*.sh %if 0%{?have_uefi} %{_datadir}/installed-tests/fwupd/efi %endif %{_datadir}/fwupd/device-tests/*.json %{_libexecdir}/installed-tests/fwupd/* %dir %{_sysconfdir}/fwupd/remotes.d %config(noreplace)%{_sysconfdir}/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 %{_libexecdir}/qubes-fwupd/fwupd_usbvm_validate.py %files qubes-dom0 %{_datadir}/qubes-fwupd/src/fwupd_receive_updates.py /usr/sbin/qubes-fwupdmgr %{_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 * #LONGDATE# Richard Hughes #VERSION#-0.#BUILD##ALPHATAG# - Update from git fwupd-1.7.5/contrib/generate-version-script.py000077500000000000000000000107621420024370600214510ustar00rootroot00000000000000#!/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("usage: %s \n" % sys.argv[0]) 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 += " %s;\n" % symbol 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.7.5/contrib/meson.build000066400000000000000000000007451420024370600164570ustar00rootroot00000000000000subdir('firmware_packager') if get_option('qubes') subdir('qubes') endif con2 = configuration_data() con2.set('FWUPD_VERSION', fwupd_version) con2.set('FWUPD_PLUGINVER', libfwupdplugin_lt_current) configure_file( input : 'fwupd.spec.in', output : 'fwupd.spec.in', configuration : con2, ) if host_machine.system() == 'windows' # replace @FWUPD_VERSION@ configure_file( input : 'setup-win32.nsi.in', output : 'setup-win32.nsi', configuration : con2, ) endif fwupd-1.7.5/contrib/mingw64.cross000066400000000000000000000005261420024370600166600ustar00rootroot00000000000000[binaries] c = '/usr/bin/x86_64-w64-mingw32-gcc' cpp = '/usr/bin/x86_64-w64-mingw32-g++' ar = '/usr/bin/x86_64-w64-mingw32-ar' strip = '/usr/bin/x86_64-w64-mingw32-strip' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' exe_wrapper = '/usr/bin/wine' [host_machine] system = 'windows' cpu_family = 'x86_64' cpu = 'i686' endian = 'little' fwupd-1.7.5/contrib/prepare-system000077500000000000000000000023211420024370600172130ustar00rootroot00000000000000#!/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.7.5/contrib/qubes/000077500000000000000000000000001420024370600154265ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/README.md000066400000000000000000000163321420024370600167120ustar00rootroot00000000000000# 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) ## OS 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, sys-whonix, and sys-usb (if exists) 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, sys-whonix, and sys-usb) * 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.7.5/contrib/qubes/doc/000077500000000000000000000000001420024370600161735ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/doc/heads_update.md000066400000000000000000000026711420024370600211510ustar00rootroot00000000000000# 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_managment_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.7.5/contrib/qubes/doc/img/000077500000000000000000000000001420024370600167475ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/doc/img/heads_detected.jpg000066400000000000000000003060111420024370600223770ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_firmware_managment_menu.jpg000066400000000000000000003376341420024370600255240ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_flash_rom.jpg000066400000000000000000002361331420024370600225770ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_options.jpg000066400000000000000000003764151420024370600223300ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_selecting_rom.jpg000066400000000000000000002402101420024370600234460ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_success.jpg000066400000000000000000002172211420024370600222720ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/heads_update_process.jpg000066400000000000000000003504261420024370600236470ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/qubes_manager.png000066400000000000000000006330331420024370600222760ustar00rootroot00000000000000PNG  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.7.5/contrib/qubes/doc/img/uefi_ME.jpg000066400000000000000000002464221420024370600207740ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/uefi_capsule_found.jpg000066400000000000000000003465741420024370600233330ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/img/uefi_success.jpg000066400000000000000000003215211420024370600221350ustar00rootroot00000000000000 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.7.5/contrib/qubes/doc/uefi_capsule_update.md000066400000000000000000000017371420024370600225330ustar00rootroot00000000000000# 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.7.5/contrib/qubes/doc/whonix.md000066400000000000000000000007441420024370600200360ustar00rootroot00000000000000# 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.7.5/contrib/qubes/meson.build000066400000000000000000000021751420024370600175750ustar00rootroot00000000000000install_data([ 'src/__init__.py', 'src/fwupd_receive_updates.py', 'src/qubes_fwupd_heads.py', 'src/qubes_fwupd_update.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', 'src/vms/fwupd_usbvm_validate.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_data( 'src/qubes_fwupdmgr.py', install_dir : 'sbin', rename : 'qubes-fwupdmgr', install_mode : 'rwxrwxr-x', ) fwupd-1.7.5/contrib/qubes/src/000077500000000000000000000000001420024370600162155ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/src/__init__.py000066400000000000000000000000001420024370600203140ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/src/fwupd_receive_updates.py000066400000000000000000000243351420024370600231520ustar00rootroot00000000000000#!/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 glob import grp import hashlib import os import re import shutil import subprocess FWUPD_DOM0_DIR = "/root/.cache/fwupd" 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.gz") FWUPD_DOM0_METADATA_JCAT = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz.jcat") 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_VM_METADATA_FILE = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz") FWUPD_VM_METADATA_JCAT = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz.jcat") FWUPD_PKI = "/etc/pki/fwupd" FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" FWUPD_METADATA_FLAG_REGEX = re.compile(r"^metaflag") FWUPD_METADATA_FILES_REGEX = re.compile( r"^firmware[a-z0-9\[\]\@\<\>\.\"\-]{0,128}.xml.gz.?[aj]?[sc]?[ca]?t?$" ) HEADS_UPDATES_DIR = "/boot/updates" WARNING_COLOR = "\033[93m" 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 _check_domain(self, updatevm): """Checks if domain given as `updatevm` is allowed to send update files. Keyword argument: updatevm - domain to be checked """ cmd = ["qubes-prefs", "--force-root", "updatevm"] p = subprocess.check_output(cmd) source = p.decode("ascii").rstrip() if source != updatevm and "sys-whonix" != updatevm: raise Exception(f"Domain {updatevm} not allowed to send dom0 updates") def _verify_received(self, files_path, regex_pattern, updatevm): """Checks if sent files match regex filename pattern. Keyword arguments: files_path -- absolute path to inspected directory regex_pattern -- pattern of the expected files updatevm - domain to be checked """ for untrusted_f in os.listdir(files_path): if not regex_pattern.match(untrusted_f): raise Exception(f"Domain {updatevm} sent unexpected file") f = untrusted_f assert "/" not in f assert "\0" not in f assert "\x1b" not in f path_f = os.path.join(files_path, f) if os.path.islink(path_f) or not os.path.isfile(path_f): raise Exception(f"Domain {updatevm} sent not regular file") 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.mkdir(file_path) os.chown(file_path, -1, qubes_gid) os.chmod(file_path, 0o0775) 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 _extract_archive(self, archive_path, output_path): """Extracts archive file to the specified directory. Keyword arguments: archive_path -- absolute path to archive file output_path -- absolute path to the output directory """ cmd_extract = ["gcab", "-x", f"--directory={output_path}", f"{archive_path}"] shutil.copy(archive_path, FWUPD_DOM0_UPDATES_DIR) p = subprocess.Popen(cmd_extract, stdout=subprocess.PIPE) p.communicate()[0].decode("ascii") if p.returncode != 0: raise Exception(f"gcab: Error while extracting {archive_path}.") 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_cache() raise Exception("jcat-tool: Verification failed") 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 """ fwupd_firmware_file_regex = re.compile(filename) dom0_firmware_untrusted_path = os.path.join(FWUPD_DOM0_UNTRUSTED_DIR, filename) updatevm_firmware_file_path = os.path.join(FWUPD_VM_UPDATES_DIR, filename) self._check_domain(updatevm) if os.path.exists(FWUPD_DOM0_UNTRUSTED_DIR): shutil.rmtree(FWUPD_DOM0_UNTRUSTED_DIR) self._create_dirs(FWUPD_DOM0_UPDATES_DIR, FWUPD_DOM0_UNTRUSTED_DIR) cmd_copy = "qvm-run --pass-io %s %s > %s" % ( updatevm, "'cat %s'" % updatevm_firmware_file_path, dom0_firmware_untrusted_path, ) p = subprocess.Popen(cmd_copy, shell=True) p.wait() if p.returncode != 0: raise Exception("qvm-run: Copying firmware file failed!!") self._verify_received( FWUPD_DOM0_UNTRUSTED_DIR, fwupd_firmware_file_regex, updatevm ) self._check_shasum(dom0_firmware_untrusted_path, sha) untrusted_dir_name = filename.replace(".cab", "") self._extract_archive(dom0_firmware_untrusted_path, FWUPD_DOM0_UNTRUSTED_DIR) signature_name = os.path.join(FWUPD_DOM0_UNTRUSTED_DIR, "firmware*.jcat") file_path = glob.glob(signature_name) if not file_path: raise FileNotFoundError("jcat file not found!") self._jcat_verification(file_path[0], FWUPD_DOM0_UNTRUSTED_DIR) os.umask(self.old_umask) if untrusted_dir_name == "untrusted": untrusted_dir_name = "trusted" verified_file = os.path.join(FWUPD_DOM0_UPDATES_DIR, filename) self.arch_name = "trusted.cab" self.arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, self.arch_name) shutil.move(verified_file, self.arch_path) else: self.arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, filename) dir_name = os.path.join(FWUPD_DOM0_UPDATES_DIR, untrusted_dir_name) os.remove(dom0_firmware_untrusted_path) shutil.move(FWUPD_DOM0_UNTRUSTED_DIR, dir_name) def handle_metadata_update(self, updatevm, metadata_url=None): """Copies metadata files from the updateVM. Keyword argument: updatevm -- update VM name """ if metadata_url: metadata_name = metadata_url.replace(FWUPD_DOWNLOAD_PREFIX, "") self.metadata_file = os.path.join(FWUPD_DOM0_METADATA_DIR, metadata_name) self.metadata_file_jcat = self.metadata_file + ".jcat" else: self.metadata_file = FWUPD_DOM0_METADATA_FILE self.metadata_file_jcat = FWUPD_DOM0_METADATA_JCAT self.metadata_file_updatevm = self.metadata_file.replace( FWUPD_DOM0_METADATA_DIR, FWUPD_VM_METADATA_DIR ) self.metadata_file_jcat_updatevm = self.metadata_file_jcat.replace( FWUPD_DOM0_METADATA_DIR, FWUPD_VM_METADATA_DIR ) self._check_domain(updatevm) self._create_dirs(FWUPD_DOM0_METADATA_DIR) cmd_file = "'cat %s'" % self.metadata_file_updatevm cmd_jcat = "'cat %s'" % self.metadata_file_jcat_updatevm cmd_copy_metadata_file = "qvm-run --pass-io %s %s > %s" % ( updatevm, cmd_file, self.metadata_file, ) cmd_copy_metadata_jcat = "qvm-run --pass-io %s %s > %s" % ( updatevm, cmd_jcat, self.metadata_file_jcat, ) p = subprocess.Popen(cmd_copy_metadata_file, shell=True) p.wait() if p.returncode != 0: raise Exception("qvm-run: Copying metadata file failed!!") p = subprocess.Popen(cmd_copy_metadata_jcat, shell=True) p.wait() if p.returncode != 0: raise Exception('qvm-run": Copying metadata jcat failed!!') self._verify_received( FWUPD_DOM0_METADATA_DIR, FWUPD_METADATA_FILES_REGEX, updatevm ) self._jcat_verification(self.metadata_file_jcat, FWUPD_DOM0_METADATA_DIR) os.umask(self.old_umask) def clean_cache(self, usbvm=False): """Removes updates data Keyword arguments: usbvm -- usbvm support flag """ 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) if usbvm: print("Cleaning usbvm cache directories") self._clean_usbvm() fwupd-1.7.5/contrib/qubes/src/qubes_fwupd_heads.py000066400000000000000000000104741420024370600222650ustar00rootroot00000000000000#!/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 os import shutil import xml.etree.ElementTree as ET from distutils.version import LooseVersion as l_ver FWUPDTOOL = "/bin/fwupdtool" BOOT = "/boot" HEADS_UPDATES_DIR = os.path.join(BOOT, "updates") EXIT_CODES = {"ERROR": 1, "SUCCESS": 0, "NOTHING_TO_DO": 2} 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 line.startswith("BiosVersion: CBET4000 "): self.heads_version = line.replace( "BiosVersion: CBET4000 ", "" ).replace(" heads", "") else: print("Device is not running under the heads firmware!!") print("Exiting...") return EXIT_CODES["NOTHING_TO_DO"] def _parse_metadata(self, metadata_file): """ Parse metadata info. """ cmd_metadata = ["zcat", metadata_file] 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 l_ver(release_ver) > l_ver( self.heads_version ): if not self.heads_update_version or l_ver(release_ver) > l_ver( 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) update_path = arch_path.replace(".cab", "/firmware.rom") heads_update_path = os.path.join(heads_boot_path, "firmware.rom") if not os.path.exists(HEADS_UPDATES_DIR): os.mkdir(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) 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.7.5/contrib/qubes/src/qubes_fwupd_update.py000066400000000000000000000127601420024370600224630ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kaminski # # SPDX-License-Identifier: LGPL-2.1+ # import grp import os import re import subprocess FWUPD_DOM0_DIR = "/root/.cache/fwupd" 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-") WARNING_COLOR = "\033[93m" class FwupdUpdate: 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.mkdir(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 _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 usbvm is running""" cmd_xl_list = ["xl", "list"] p = subprocess.Popen(cmd_xl_list, stdout=subprocess.PIPE) output = p.communicate()[0].decode() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware downgrade failed") return self.updatevm in output def _encrypt_update_url(self, url): self.enc_url = url self.arch_name = url.replace(FWUPD_DOWNLOAD_PREFIX, "") self.arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, self.arch_name) if "&" in url: self.enc_url = self.enc_url.replace("&", "--and--") self.arch_name = "untrusted.cab" if "|" in url: self.enc_url = self.enc_url.replace("|", "--or--") self.arch_name = "untrusted.cab" if "#" in url: self.enc_url = self.enc_url.replace("#", "--hash--") self.arch_name = "untrusted.cab" if "%20" in url: self.arch_name = "untrusted.cab" 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): self._create_dirs(FWUPD_DOM0_DIR) if metadata_url: cmd_metadata = [ "qvm-run", "--pass-io", self.updatevm, ( "script --quiet --return --command " f'"{FWUPD_VM_DOWNLOAD} --metadata' f' --url={metadata_url}"' ), ] else: cmd_metadata = [ "qvm-run", "--pass-io", self.updatevm, ( "script --quiet --return --command " f'"{FWUPD_VM_DOWNLOAD} --metadata"' ), ] p = subprocess.Popen(cmd_metadata) p.wait() if p.returncode != 0: 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): self._create_dirs(FWUPD_DOM0_DIR) self._encrypt_update_url(url) if not os.path.exists(self.arch_path): cmd_firmware_download = [ "qvm-run", "--pass-io", self.updatevm, ( "script --quiet --return --command " f'"{FWUPD_VM_DOWNLOAD} --url={self.enc_url}' f' --sha={sha}"' ), ] p = subprocess.Popen(cmd_firmware_download) p.wait() if p.returncode != 0: raise Exception("Firmware download failed.") else: self.cached = True print("Firmware already downloaded. Using cached files.") fwupd-1.7.5/contrib/qubes/src/qubes_fwupdmgr.py000077500000000000000000001210611420024370600216250ustar00rootroot00000000000000#!/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 sys import xml.etree.ElementTree as ET from pathlib import Path from distutils.version import LooseVersion as l_ver 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 from fwupd_receive_updates import FwupdReceiveUpdates except ModuleNotFoundError: raise ModuleNotFoundError( "qubes-fwupd modules not found. " "You may need to reinstall package." ) FWUPD_DOM0_DIR = "/root/.cache/fwupd" FWUPD_DOM0_METADATA_DIR = os.path.join(FWUPD_DOM0_DIR, "metadata") FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOM0_METADATA_SIGNATURE = os.path.join( FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz.asc" ) FWUPD_DOM0_METADATA_FILE = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz") FWUPD_DOM0_METADATA_JCAT = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz.jcat") FWUPD_VM_LOG = os.path.join(FWUPD_DOM0_DIR, "usbvm-devices.log") FWUPD_VM_VALIDATE = "/usr/libexec/qubes-fwupd/fwupd_usbvm_validate.py" 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_VM_METADATA_SIGNATURE = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz.asc") FWUPD_VM_METADATA_FILE = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz") FWUPD_VM_METADATA_JCAT = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz.jcat") FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" FWUPDMGR = "/bin/fwupdmgr" FWUPDAGENT = "/bin/fwupdagent" USBVM_N = "sys-usb" BIOS_UPDATE_FLAG = os.path.join(FWUPD_DOM0_DIR, "bios_update") LVFS_TESTING_DOM0_FLAG = os.path.join(FWUPD_DOM0_DIR, "lvfs_testing") LVFS_TESTING_USBVM_FLAG = os.path.join(FWUPD_VM_DIR, "lvfs_testing") METADATA_REFRESH_REGEX = re.compile(r"^Successfully refreshed metadata manually$") SPECIAL_CHAR_REGEX = re.compile(r"%20|&|\||#") 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", "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"}], } EXIT_CODES = {"ERROR": 1, "SUCCESS": 0, "NOTHING_TO_DO": 2} 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 """ 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 _validate_usbvm_dirs(self): """Validates if sys-ubs updates and metadata directories exist.""" cmd_validate_dirs = [ "qvm-run", "--pass-io", USBVM_N, f'script --quiet --return --command "{FWUPD_VM_VALIDATE} dirs"', ] p = subprocess.Popen(cmd_validate_dirs) p.wait() if p.returncode != 0: raise Exception("Validation of usbvm directories failed.") def _validate_usbvm_archive(self, arch_name, sha): """Validates checksum and gpg signature of the archive file.""" arch_path = os.path.join(FWUPD_VM_UPDATES_DIR, arch_name) arch_validate = f"{FWUPD_VM_VALIDATE} updates {arch_path} {sha}" cmd_validate_arch = [ "qvm-run", "--pass-io", USBVM_N, f'script --quiet --return --command "{arch_validate}"', ] p = subprocess.Popen(cmd_validate_arch) p.wait() if p.returncode != 0: raise Exception("Validation of the archive file failed.") def _copy_usbvm_metadata(self): """Copies metadata files to usbvm.""" self.metadata_file_usbvm = self.metadata_file.replace( FWUPD_DOM0_METADATA_DIR, FWUPD_VM_METADATA_DIR ) self.metadata_file_jcat_usbvm = self.metadata_file_usbvm + ".jcat" cat_file = f"cat > {self.metadata_file_usbvm}" cmd_copy_file = ( f"cat {self.metadata_file} | " f'qvm-run --nogui --pass-io {USBVM_N} "{cat_file}"' ) cat_jcat = f"cat > {self.metadata_file_jcat_usbvm}" cmd_copy_jcat = ( f"cat {self.metadata_file_jcat} | " f'qvm-run --nogui --pass-io {USBVM_N} "{cat_jcat}"' ) p = subprocess.Popen(cmd_copy_file, shell=True) p.wait() if p.returncode != 0: raise Exception("Copying metadata file failed.") p = subprocess.Popen(cmd_copy_jcat, shell=True) p.wait() if p.returncode != 0: raise Exception("Copying metadata jcat failed.") def _validate_usbvm_metadata(self, metadata_url=None): """Checks GPG signature of metadata files in usbvm.""" usbvm_cmd = f'"{FWUPD_VM_VALIDATE} metadata"' if metadata_url: usbvm_cmd = f'"{FWUPD_VM_VALIDATE} metadata --url={metadata_url}"' cmd_validate_metadata = [ "qvm-run", "--pass-io", USBVM_N, "script --quiet --return --command" f" {usbvm_cmd}", ] p = subprocess.Popen(cmd_validate_metadata) p.wait() if p.returncode != 0: raise Exception("Metadata validation failed") def _refresh_usbvm_metadata(self): """Refreshes metadata in usbvm.""" sig_metadata_file = self.metadata_file_jcat_usbvm cmd_refresh_metadata = [ "qvm-run", "--pass-io", USBVM_N, ( "script --quiet --return --command " f'"{FWUPDMGR} refresh {self.metadata_file_usbvm} ' f'{sig_metadata_file} {self.lvfs}"' ), ] p = subprocess.Popen(cmd_refresh_metadata) p.wait() if p.returncode != 0: raise Exception("Metadata refresh in usbvm failed") def _copy_firmware_updates(self, arch_name): """Copies updates files to usbvm. Keywords arguments: arch_name - name of the archive file """ arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, arch_name) output_path = os.path.join(FWUPD_VM_UPDATES_DIR, arch_name) cat_file = f"cat > {output_path}" cmd_copy_file = ( f"cat {arch_path} | " f'qvm-run --nogui --pass-io {USBVM_N} "{cat_file}"' ) p = subprocess.Popen(cmd_copy_file, shell=True) p.wait() if p.returncode != 0: raise Exception("Copying metadata file failed.") def _install_usbvm_firmware_update(self, arch_name): """Installs firmware update for specified device in dom0. Keywords arguments: arch_name - name of the archive file """ arch_path = os.path.join(FWUPD_VM_UPDATES_DIR, arch_name) CMD_update = [ "qvm-run", "--pass-io", USBVM_N, f"script --quiet --return --command" f' "{FWUPDMGR} install {arch_path}" /dev/null', ] p = subprocess.Popen(CMD_update) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware update failed") def _install_usbvm_firmware_downgrade(self, arch_name): """Installs firmware downgrades for specified device in dom0. Keywords arguments: arch_name - name of the archive file """ arch_path = os.path.join(FWUPD_VM_UPDATES_DIR, arch_name) CMD_downgrade = [ "qvm-run", "--pass-io", USBVM_N, f"script --quiet --return --command" f' "{FWUPDMGR} --allow-older install {arch_path}" /dev/null', ] p = subprocess.Popen(CMD_downgrade) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware downgrade failed") def _clean_usbvm(self): """Cleans usbvm directories.""" cmd_clean = [ "qvm-run", "--pass-io", USBVM_N, f'script --quiet --return --command "{FWUPD_VM_VALIDATE} clean"', ] p = subprocess.Popen(cmd_clean) p.wait() if p.returncode != 0: raise Exception("Cleaning usbvm directories failed") def _enable_lvfs_testing_dom0(self): """Checks and enable lvfs-testing for custom metadata in dom0""" cmd_lvfs_testing = [FWUPDMGR, "enable-remote", "-y", "lvfs-testing"] if not os.path.exists(LVFS_TESTING_DOM0_FLAG): p = subprocess.Popen(cmd_lvfs_testing) p.wait() if p.returncode != 0: raise Exception("Enabling dom0 lvfs-testing failed!!") Path(LVFS_TESTING_DOM0_FLAG).touch(mode=0o644, exist_ok=False) def _enable_lvfs_testing_usbvm(self, usbvm=False): """Checks and enable lvfs-testing for custom metadata in usbvm""" if not usbvm: return 0 cmd_refresh_metadata = [ "qvm-run", "--pass-io", USBVM_N, ( "script --quiet --return --command " f'"{FWUPDMGR} enable-remote -y lvfs-testing"' ), ] cmd_validate_flag = [ "qvm-run", "--pass-io", USBVM_N, ( "script --quiet --return --command " f'"ls {LVFS_TESTING_USBVM_FLAG} &>/dev/null"' ), ] cmd_touch_flag = [ "qvm-run", "--pass-io", USBVM_N, ("script --quiet --return --command " f'"touch {LVFS_TESTING_USBVM_FLAG}"'), ] flag = subprocess.Popen(cmd_validate_flag) flag.wait() if flag.returncode != 0: p = subprocess.Popen(cmd_refresh_metadata) p.wait() if p.returncode != 0: raise Exception("Enabling usbvm lvfs-testing failed!!") p = subprocess.Popen(cmd_touch_flag) p.wait() if p.returncode != 0: raise Exception("Creating flag failed!!") def refresh_metadata(self, usbvm=False, whonix=False, metadata_url=None): """Updates metadata with downloaded files. Keyword arguments: usbvm -- usbvm support flag whonix -- Flag enforces downloading the metadata updates via Tor metadata_url -- Use custom metadata from the url """ if metadata_url: metadata_name = metadata_url.replace(FWUPD_DOWNLOAD_PREFIX, "") self.metadata_file = os.path.join(FWUPD_DOM0_METADATA_DIR, metadata_name) self.metadata_file_jcat = self.metadata_file + ".jcat" self.lvfs = "lvfs-testing" self._enable_lvfs_testing_dom0() self._enable_lvfs_testing_usbvm(usbvm=usbvm) else: self.metadata_file = FWUPD_DOM0_METADATA_FILE self.metadata_file_jcat = FWUPD_DOM0_METADATA_JCAT self.lvfs = "lvfs" self._download_metadata(whonix=whonix, metadata_url=metadata_url) if usbvm: self._validate_usbvm_dirs() self._copy_usbvm_metadata() self._validate_usbvm_metadata(metadata_url=metadata_url) self._refresh_usbvm_metadata() cmd_refresh = [ FWUPDMGR, "refresh", self.metadata_file, self.metadata_file_jcat, self.lvfs, ] p = subprocess.Popen(cmd_refresh, stdout=subprocess.PIPE) self.output = p.communicate()[0].decode() print(self.output) if p.returncode != 0: raise Exception("fwupd-qubes: Refresh failed") if not METADATA_REFRESH_REGEX.match(self.output): raise Exception("Manual metadata refresh failed!!!") def _get_dom0_updates(self): """Gathers infromations about available updates.""" cmd_get_dom0_updates = [FWUPDAGENT, "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) update_path = self.arch_path.replace(".cab", "") if not os.path.exists(update_path): raise NotADirectoryError("Firmware update files do not exist") def _user_input(self, updates_dict, downgrade=False, usbvm=False): """UI for update process. Keywords arguments: updates_dict - list of updates for specified device downgrade -- downgrade flag """ decorator = "======================================================" if usbvm: updates_list = updates_dict["dom0"] + updates_dict["usbvm"] else: updates_list = updates_dict["dom0"] dom0_updates_num = len(updates_dict["dom0"]) 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_dict["dom0"]) if usbvm: self._updates_crawler( updates_dict["usbvm"], usbvm=True, prefix=dom0_updates_num ) 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: if device_num >= dom0_updates_num: return "usbvm", device_num - dom0_updates_num else: return "dom0", 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): if device_num >= dom0_updates_num: device_abs_num = device_num - dom0_updates_num return "usbvm", device_abs_num, downgrade_num else: return "dom0", device_num, downgrade_num else: raise ValueError() except ValueError: print("Invalid choice.") def _parse_parameters(self, updates_dict, vm_name, choice): """Parses device name, url, version and SHA256 checksum of the file list. Keywords arguments: updates_dict - dictionary of updates for dom0 and usbvm vm_name - VM name choice -- number of device to be updated """ self.name = updates_dict[vm_name][choice]["Name"] self.version = updates_dict[vm_name][choice]["Releases"][0]["Version"] for ver_check in updates_dict[vm_name][choice]["Releases"]: if l_ver(ver_check["Version"]) >= l_ver(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, path, version, downgrade=False): """Verifies DMI tables for BIOS updates. Keywords arguments: path -- absolute path of the updates files version -- version of the update downgrade -- downgrade flag """ dmi_info = self._read_dmi() path_metainfo = os.path.join(path, "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 l_ver(version) <= l_ver(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 = [FWUPDAGENT, "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 _get_usbvm_devices(self): """Gathers information about devices connected in usbvm.""" if os.path.exists(FWUPD_VM_LOG): os.remove(FWUPD_VM_LOG) usbvm_cmd = f'"{FWUPDAGENT} get-devices"' log_file = f" > {FWUPD_VM_LOG}" cmd_get_usbvm_devices = ( f"qvm-run --nogui --pass-io {USBVM_N} {usbvm_cmd}{log_file}" ) p = subprocess.Popen(cmd_get_usbvm_devices, shell=True) p.wait() if p.returncode != 0 and p.returncode != 2 and not os.path.exists(FWUPD_VM_LOG): raise Exception("fwupd-qubes: Getting usbvm devices info failed") if not os.path.exists(FWUPD_VM_LOG): raise Exception("usbvm device info log does not exist") def _parse_usbvm_updates(self, usbvm_devices_info): """Creates dictionary and list with information about updates. Keywords argument: usbvm_devices_info - gathered usbvm information """ self.usbvm_updates_list = [] if "No detected devices" in usbvm_devices_info: return EXIT_CODES["NOTHING_TO_DO"] usbvm_device_info_dict = json.loads(usbvm_devices_info) for device in usbvm_device_info_dict["Devices"]: if "Releases" in device: self.usbvm_updates_list.append( { "Name": device["Name"], "Version": device["Version"], "Releases": [], } ) current_version = device["Version"] for update in device["Releases"]: if l_ver(update["Version"]) > current_version: self.usbvm_updates_list[-1]["Releases"].append( { "Version": update["Version"], "Url": update["Uri"], "Checksum": update["Checksum"][-1], "Description": update["Description"], } ) if not self.usbvm_updates_list[-1]["Releases"]: self.usbvm_updates_list.pop() def update_firmware(self, usbvm=False, whonix=False): """Updates firmware of the specified device. Keyword arguments: usbvm -- usbvm support flag whonix -- Flag enforces downloading the metadata updates via Tor """ self._get_dom0_updates() self._parse_dom0_updates_info(self.dom0_updates_info) if usbvm: self._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() self._parse_usbvm_updates(raw) update_dict = { "usbvm": self.usbvm_updates_list, "dom0": self.dom0_updates_list, } ret_input = self._user_input(update_dict, usbvm=True) else: update_dict = {"dom0": self.dom0_updates_list} ret_input = self._user_input(update_dict) if ret_input == EXIT_CODES["NOTHING_TO_DO"]: exit(EXIT_CODES["NOTHING_TO_DO"]) vm_name, choice = ret_input self._parse_parameters(update_dict, vm_name, 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) extracted_path = self.arch_path.replace(".cab", "") self._verify_dmi(extracted_path, self.version) if vm_name == "dom0": self._install_dom0_firmware_update(self.arch_path) if vm_name == "usbvm": self._validate_usbvm_dirs() self._copy_firmware_updates(self.arch_name) self._validate_usbvm_archive(self.arch_name, self.sha) self._install_usbvm_firmware_update(self.arch_name) 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 l_ver(downgrade["Version"]) < l_ver(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, usbvm=False, whonix=False): """Downgrades firmware of the specified device. Keyword arguments: usbvm -- usbvm support flag whonix -- Flag enforces downloading the metadata updates via Tor """ self._get_dom0_devices() dom0_downgrades = self._parse_downgrades(self.dom0_devices_info) if usbvm: self._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() usbvm_downgrades = self._parse_downgrades(raw) downgrade_dict = {"usbvm": usbvm_downgrades, "dom0": dom0_downgrades} ret_input = self._user_input(downgrade_dict, downgrade=True, usbvm=True) else: downgrade_dict = {"dom0": dom0_downgrades} ret_input = self._user_input(downgrade_dict, downgrade=True) if ret_input == EXIT_CODES["NOTHING_TO_DO"]: exit(EXIT_CODES["NOTHING_TO_DO"]) vm_name, device_choice, downgrade_choice = ret_input releases = downgrade_dict[vm_name][device_choice]["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_dict[vm_name][device_choice]["Name"] == "System Firmware": Path(BIOS_UPDATE_FLAG).touch(mode=0o644, exist_ok=True) extracted_path = self.arch_path.replace(".cab", "") self._verify_dmi( extracted_path, downgrade_dict[vm_name][device_choice]["Version"], downgrade=True, ) if vm_name == "dom0": self._install_dom0_firmware_downgrade(self.arch_path) if vm_name == "usbvm": self._validate_usbvm_dirs() self._copy_firmware_updates(self.arch_name) self._validate_usbvm_archive(self.arch_name, downgrade_sha) self._install_usbvm_firmware_downgrade(self.arch_name) def _output_crawler(self, updev_dict, level, help_f=False, dom0=True): """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 and dom0 is True: print(f"Dom0 {output}") elif level == 0 and dom0 is False: print(f"{USBVM_N} {output}") for nested_dict in updev_dict[updev_key]: self._output_crawler(nested_dict, level + 1) def _updates_crawler(self, updates_list, usbvm=False, prefix=0): """Prints updates information for dom0 and usbvm Keywords arguments: updates_list -- list of devices updates usbvm -- usbvm support flag prefix -- device number prefix """ available_updates = False decorator = "======================================================" print(decorator) if usbvm: print(f"{USBVM_N} updates:") else: 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, usbvm=False): """Gathers and prints devices information. Keyword arguments: usbvm -- usbvm support flag """ self._get_dom0_devices() dom0_devices_info_dict = json.loads(self.dom0_devices_info) self._output_crawler(dom0_devices_info_dict, 0) if usbvm: self._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() if "No detected devices" not in raw: usbvm_device_info_dict = json.loads(raw) else: print(f"No detected devices in {USBVM_N}") return EXIT_CODES["NOTHING_TO_DO"] self._output_crawler(usbvm_device_info_dict, 0, dom0=False) def get_updates_qubes(self, usbvm=False): """Gathers and prints updates information. Keyword arguments: usbvm -- usbvm support flag """ self._get_dom0_updates() self._parse_dom0_updates_info(self.dom0_updates_info) self._updates_crawler(self.dom0_updates_list) if usbvm: self._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() self._parse_usbvm_updates(raw) self._updates_crawler(self.usbvm_updates_list, usbvm=True) def help(self): """Prints help information""" self._output_crawler(HELP, 0, help_f=True) def check_usbvm(self): """Checks if usbvm is running""" cmd_xl_list = ["xl", "list"] p = subprocess.Popen(cmd_xl_list, stdout=subprocess.PIPE) self.output = p.communicate()[0].decode() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware downgrade failed") return USBVM_N in self.output def trusted_cleanup(self, usbvm=False): """Deletes trusted directory. Keyword arguments: usbvm -- usbvm support flag """ 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", "")) if usbvm: self._clean_usbvm() def refresh_metadata_after_bios_update(self, usbvm=False): """Refreshes metadata after bios update Keyword arguments: usbvm -- usbvm support flag """ if os.path.exists(BIOS_UPDATE_FLAG): print("BIOS was updated. Refreshing metadata...") if "--whonix" in sys.argv: self.refresh_metadata(usbvm=usbvm, whonix=True) else: self.refresh_metadata(usbvm=usbvm) os.remove(BIOS_UPDATE_FLAG) def heads_update(self, device="x230", 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 """ self._check_fwupdtool_version() if metadata_url: custom_metadata_name = metadata_url.replace(FWUPD_DOWNLOAD_PREFIX, "") self.metadata_file = os.path.join( FWUPD_DOM0_METADATA_DIR, custom_metadata_name ) else: self.metadata_file = FWUPD_DOM0_METADATA_FILE self._get_hwids() 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): self._create_dirs(FWUPD_DOM0_DIR) if os.path.exists(FWUPD_DOM0_METADATA_DIR): shutil.rmtree(FWUPD_DOM0_METADATA_DIR) self._create_dirs(FWUPD_DOM0_METADATA_DIR) else: self._create_dirs(FWUPD_DOM0_METADATA_DIR) if not os.path.exists(FWUPD_DOM0_UPDATES_DIR): self._create_dirs(FWUPD_DOM0_UPDATES_DIR) os.umask(self.old_umask) def main(): if os.geteuid() != 0: print("You need to have root privileges to run this script.\n") exit(EXIT_CODES["ERROR"]) q = QubesFwupdmgr() sys_usb = q.check_usbvm() q.validate_dom0_dirs() q.trusted_cleanup(usbvm=sys_usb) q.refresh_metadata_after_bios_update(usbvm=sys_usb) metadata_url = None device = "x230" if not os.path.exists(FWUPD_DOM0_DIR): q.refresh_metadata(usbvm=sys_usb) if len(sys.argv) < 2: q.help() exit(1) 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 = arg.replace("--board=", "") if sys.argv[1] == "get-updates": q.get_updates_qubes(usbvm=sys_usb) elif sys.argv[1] == "get-devices": q.get_devices_qubes(usbvm=sys_usb) elif sys.argv[1] == "update" and "--whonix" in sys.argv: q.update_firmware(usbvm=sys_usb, whonix=True) elif sys.argv[1] == "update" and "--whonix" not in sys.argv: q.update_firmware(usbvm=sys_usb) elif sys.argv[1] == "downgrade" and "--whonix" in sys.argv: q.downgrade_firmware(usbvm=sys_usb, whonix=True) elif sys.argv[1] == "downgrade" and "--whonix" not in sys.argv: q.downgrade_firmware(usbvm=sys_usb) elif sys.argv[1] == "clean": q.clean_cache(usbvm=sys_usb) elif sys.argv[1] == "refresh" and "--whonix" not in sys.argv: q.refresh_metadata(usbvm=sys_usb, metadata_url=metadata_url) elif sys.argv[1] == "refresh" and "--whonix" in sys.argv: q.refresh_metadata(usbvm=sys_usb, whonix=True, metadata_url=metadata_url) elif sys.argv[1] == "update-heads" and "--whonix" not in sys.argv: q.heads_update(device=device, metadata_url=metadata_url) elif sys.argv[1] == "update-heads" and "--whonix" in sys.argv: q.heads_update(device=device, metadata_url=metadata_url, whonix=True) else: q.help() exit(1) if __name__ == "__main__": main() fwupd-1.7.5/contrib/qubes/src/vms/000077500000000000000000000000001420024370600170225ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/src/vms/fwupd_common_vm.py000066400000000000000000000065661420024370600226100ustar00rootroot00000000000000#!/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.mkdir(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("Computed checksum %s did NOT match %s. " % (c_sha, 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.7.5/contrib/qubes/src/vms/fwupd_download_updates.py000066400000000000000000000105221420024370600241350ustar00rootroot00000000000000#!/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 _decrypt_update_url(self, url): self.dec_url = url if "--and--" in url: self.dec_url = self.dec_url.replace("--and--", "&") self.arch_name = "untrusted.cab" if "--or--" in url: self.dec_url = self.dec_url.replace("--or--", "|") self.arch_name = "untrusted.cab" if "--hash--" in url: self.dec_url = self.dec_url.replace("--hash--", "#") self.arch_name = "untrusted.cab" if "%20" in url: self.arch_name = "untrusted.cab" 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 = ["wget", "-P", FWUPD_VM_METADATA_DIR, 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 = ["wget", "-P", FWUPD_VM_METADATA_DIR, 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" ) def download_metadata(self, url=None): """Downloads default metadata and its signatures""" if url is not None: self.custom_url = url custom_metadata_name = url.replace(FWUPD_DOWNLOAD_PREFIX, "") 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_file() self._download_metadata_jcat() 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 = url.replace("https://fwupd.org/downloads/", "") self._decrypt_update_url(url) update_path = os.path.join(FWUPD_VM_UPDATES_DIR, self.arch_name) cmd_update = ["wget", "-O", update_path, self.dec_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.7.5/contrib/qubes/src/vms/fwupd_usbvm_validate.py000066400000000000000000000110301420024370600236010ustar00rootroot00000000000000#!/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 glob import os import shutil import subprocess import sys 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_VM_METADATA_JCAT = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz.jcat") FWUPD_VM_METADATA_FILE = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz") FWUPDMGR = "/bin/fwupdmgr" FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" class FwupdUsbvmUpdates(FwupdVmCommon): def _verify_received(self, files_path, regex_pattern): """Checks if sent files match regex filename pattern. Keyword arguments: files_path -- absolute path to inspected directory regex_pattern -- pattern of the expected files """ for untrusted_f in os.listdir(files_path): if not regex_pattern.match(untrusted_f): raise Exception("Dom0 sent unexpected file") f = untrusted_f assert "/" not in f assert "\0" not in f assert "\x1b" not in f path_f = os.path.join(files_path, f) if os.path.islink(path_f) or not os.path.isfile(path_f): raise Exception("Dom0 sent not regular file") def _extract_archive(self, archive_path, output_path): """Extracts archive file to the specified directory. Keyword arguments: archive_path -- absolute path to archive file output_path -- absolute path to the output directory """ cmd_extract = ["gcab", "-x", f"--directory={output_path}", f"{archive_path}"] p = subprocess.Popen(cmd_extract, stdout=subprocess.PIPE) p.communicate()[0].decode("ascii") if p.returncode != 0: raise Exception("gcab: Error while extracting %s." % archive_path) def validate_metadata(self, metadata_url=None): """Validates received the metadata files.""" print("Running validation of the metadata files") if metadata_url: metadata_name = metadata_url.replace(FWUPD_DOWNLOAD_PREFIX, "") metadata_file = os.path.join(FWUPD_VM_METADATA_DIR, metadata_name) else: metadata_file = FWUPD_VM_METADATA_FILE try: self._jcat_verification(f"{metadata_file}.jcat", FWUPD_VM_METADATA_DIR) except Exception as e: print(str(e), file=sys.stderr) self.clean_vm_cache() exit(1) def validate_updates(self, archive_path, sha): """Validates received an update file. Keyword arguments: archive_path - path to the firmware update archive sha -- SHA256 checksum of the firmware update archive """ print("Running validation of the update archive") self.check_shasum(archive_path, sha) archive_name = archive_path.replace(f"{FWUPD_VM_UPDATES_DIR}/", "") output_path = archive_path.replace(".cab", "") arch_temp = os.path.join(output_path, archive_name) os.mkdir(output_path) shutil.copyfile(archive_path, arch_temp) self._extract_archive(arch_temp, output_path) signature_name = os.path.join(output_path, "firmware*.jcat") file_path = glob.glob(signature_name) try: self._jcat_verification(file_path[0], output_path) shutil.rmtree(output_path) except Exception as e: print(str(e), file=sys.stderr) self.clean_vm_cache() exit(1) def main(): f = FwupdUsbvmUpdates() f_val = FwupdVmCommon() metadata_url = None if len(sys.argv) < 2: raise Exception("Invalid number of arguments.") for arg in sys.argv: if "--url=" in arg: metadata_url = arg.replace("--url=", "") if sys.argv[1] == "metadata": f.validate_metadata(metadata_url=metadata_url) elif sys.argv[1] == "dirs": f_val.validate_vm_dirs() elif sys.argv[1] == "clean": f.clean_vm_cache() elif sys.argv[1] == "updates" and len(sys.argv) < 4: raise Exception( "Invalid number of arguments.\n" "Expected archive path and checksum." ) elif sys.argv[1] == "updates" and not len(sys.argv) < 4: f.validate_updates(sys.argv[2], sys.argv[3]) else: raise Exception("Invalid command") if __name__ == "__main__": main() fwupd-1.7.5/contrib/qubes/test/000077500000000000000000000000001420024370600164055ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/test/__init__.py000066400000000000000000000000001420024370600205040ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/test/fwupd_logs.py000066400000000000000000001134701420024370600211360ustar00rootroot00000000000000#!/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", "Icons" : [ "audio-card" ], "Created" : 1614209932 }, { "Name" : "Intel(R) Core™ i7-7700HQ CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Guid" : [ "b9a2dd81-159e-5537-a7db-e7101d164d3f", "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", "Icons" : [ "audio-card" ], "Created" : 1592899254 }, { "Name" : "Intel(R) Core™ i5-8400 CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Guid" : [ "b9a2dd81-159e-5537-a7db-e7101d164d3f" ], "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", "Icons" : [ "audio-card" ], "Created" : 1592899254 }, { "Name" : "Intel(R) Core™ i5-8400 CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Guid" : [ "b9a2dd81-159e-5537-a7db-e7101d164d3f" ], "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.7.5/contrib/qubes/test/logs/000077500000000000000000000000001420024370600173515ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/test/logs/firmware.metainfo.xml000066400000000000000000000036041420024370600235130ustar00rootroot00000000000000 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.7.5/contrib/qubes/test/logs/get_devices.log000066400000000000000000000014171420024370600223400ustar00rootroot00000000000000====================================================================== 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.7.5/contrib/qubes/test/logs/get_updates.log000066400000000000000000000014021420024370600223550ustar00rootroot00000000000000====================================================== 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.7.5/contrib/qubes/test/logs/help.log000066400000000000000000000021201420024370600207770ustar00rootroot00000000000000====================================================================== 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 fwupd-1.7.5/contrib/qubes/test/logs/metainfo_name/000077500000000000000000000000001420024370600221535ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/test/logs/metainfo_name/firmware.metainfo.xml000066400000000000000000000036051420024370600263160ustar00rootroot00000000000000 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.7.5/contrib/qubes/test/logs/metainfo_version/000077500000000000000000000000001420024370600227205ustar00rootroot00000000000000fwupd-1.7.5/contrib/qubes/test/logs/metainfo_version/firmware.metainfo.xml000066400000000000000000000036041420024370600270620ustar00rootroot00000000000000 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.7.5/contrib/qubes/test/test_qubes_fwupd_heads.py000066400000000000000000000111161420024370600235060ustar00rootroot00000000000000#!/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._check_fwupdtool_version() 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, 2) def test_gather_firmware_version(self): self.q.dom0_hwids_info = "BiosVersion: CBET4000 0.2.2 heads" self.q._gather_firmware_version() self.assertEqual(self.q.heads_version, "0.2.2") @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, 0) 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, "1a54e69ca2b58d1218035115d481480eaf4c66e4" ) 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, 2) 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, 0) 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, "1a54e69ca2b58d1218035115d481480eaf4c66e4" ) 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 = "1a54e69ca2b58d1218035115d481480eaf4c66e4" 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["NO_UPDATES"]) firmware_path = os.path.join(heads_boot_path, "firmware.rom") self.assertTrue(os.path.exists(firmware_path)) if __name__ == "__main__": unittest.main() fwupd-1.7.5/contrib/qubes/test/test_qubes_fwupdmgr.py000077500000000000000000001061051420024370600230560ustar00rootroot00000000000000#!/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 from distutils.version import LooseVersion as l_ver from pathlib import Path from test.fwupd_logs import UPDATE_INFO, GET_DEVICES, DMI_DECODE from test.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 = "/root/.cache/fwupd" FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOM0_UNTRUSTED_DIR = os.path.join(FWUPD_DOM0_UPDATES_DIR, "untrusted") FWUPD_VM_LOG = os.path.join(FWUPD_DOM0_DIR, "usbvm-devices.log") FWUPD_DOM0_METADATA_DIR = os.path.join(FWUPD_DOM0_DIR, "metadata") FWUPD_DOM0_METADATA_FILE = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz") FWUPD_DOM0_METADATA_FILE_JCAT = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.gz") 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_VM_METADATA_FILE = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz") FWUPD_VM_METADATA_FILE_JCAT = os.path.join( FWUPD_VM_METADATA_DIR, "firmware.xml.gz.jcat" ) REQUIRED_DEV = "Requires device not connected" REQUIRED_USBVM = "Requires sys-usb" XL_LIST_LOG = "Name ID Mem VCPUs State Time(s)" USBVM_N = "sys-usb" 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") LVFS_TESTING_USBVM_FLAG = os.path.join(FWUPD_VM_DIR, "lvfs_testing") CUSTOM_METADATA = "https://fwupd.org/downloads/firmware-3c81bfdc9db5c8a42c09d38091944bc1a05b27b0.xml.gz" def check_usbvm(): """Checks if sys-usb is running""" if "qubes" not in platform.release(): return False q = qfwupd.QubesFwupdmgr() q.check_usbvm() return "sys-usb" in q.output 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 device_connected_usbvm(): """Checks if the testing device is connected in usbvm""" if not check_usbvm(): return False q = qfwupd.QubesFwupdmgr() q._validate_usbvm_dirs() if not os.path.exists(FWUPD_DOM0_DIR): q.refresh_metadata() q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: return "ColorHug2" in usbvm_device_info.read() def check_whonix_updatevm(): """Checks if the sys-whonix is running""" if "qubes" not in platform.release(): return False q = qfwupd.QubesFwupdmgr() q.check_usbvm() 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() sys.stdout = self.captured_output @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() 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() self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", msg="Metadata refresh failed.", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_refresh_metadata_dom0_custom(self): self.q.refresh_metadata(metadata_url=CUSTOM_METADATA) self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", msg="Metadata refresh failed.", ) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_refresh_metadata_usbvm(self): self.q.refresh_metadata(usbvm=True) self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", msg="Metadata refresh failed.", ) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_refresh_metadata_usbvm_custom(self): self.q.refresh_metadata(usbvm=True, metadata_url=CUSTOM_METADATA) self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", msg="Metadata refresh failed.", ) @unittest.skipUnless(check_whonix_updatevm(), "Requires sys-whonix") def test_refresh_metadata_whonix(self): self.q.refresh_metadata(whonix=True) self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", 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/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda", ) update_path = os.path.join( FWUPD_DOM0_UPDATES_DIR, "0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7", ) self.assertTrue(os.path.exists(update_path)) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_download_firmware_special_char(self): self.q._download_firmware_updates( "https://fwupd.org/downloads/bc334d8b098f2e91603c5f7dfdc837fb01797bbe-Dell%20XPS%2015%209560&Precision%205520%20System%20BIOS_Ver.1.18.0.cab", "86d9e5e35b0b264be1bb1e49ec16ccd1330390423bfe962267a58c27be7712b8", ) update_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, "trusted") 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/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda", whonix=True, ) update_path = os.path.join( FWUPD_DOM0_UPDATES_DIR, "0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7", ) self.assertTrue(os.path.exists(update_path)) def test_user_input_empty_dict(self): downgrade_dict = {"usbvm": [], "dom0": []} self.assertEqual(self.q._user_input(downgrade_dict), 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) downgrade_dict = { "usbvm": self.q.dom0_updates_list, "dom0": self.q.dom0_updates_list, } choice = self.q._user_input(downgrade_dict, usbvm=True) self.assertEqual(choice, 2) user_input = ["sth", "N"] with patch("builtins.input", side_effect=user_input): self.q._parse_dom0_updates_info(UPDATE_INFO) downgrade_dict = { "usbvm": self.q.dom0_updates_list, "dom0": self.q.dom0_updates_list, } choice = self.q._user_input(downgrade_dict, usbvm=True) 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) updates_dict = { "usbvm": self.q.dom0_updates_list, "dom0": self.q.dom0_updates_list, } key, choice = self.q._user_input(updates_dict) self.assertEqual(key, "dom0") self.assertEqual(choice, 0) def test_user_input_choice_usbvm(self): user_input = ["6", "2"] with patch("builtins.input", side_effect=user_input): self.q._parse_dom0_updates_info(UPDATE_INFO) updates_dict = { "usbvm": self.q.dom0_updates_list, "dom0": self.q.dom0_updates_list, } key, choice = self.q._user_input(updates_dict, usbvm=True) self.assertEqual(key, "usbvm") self.assertEqual(choice, 0) def test_parse_parameters(self): self.q._parse_dom0_updates_info(UPDATE_INFO) update_dict = {"dom0": self.q.dom0_updates_list} self.q._parse_parameters(update_dict, "dom0", 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)) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_clean_cache_dom0_n_usbvm(self): self.q._validate_usbvm_dirs() self.q.clean_cache(usbvm=True) self.assertFalse(os.path.exists(FWUPD_DOM0_METADATA_DIR)) self.assertFalse(os.path.exists(FWUPD_DOM0_UNTRUSTED_DIR)) cmd_validate_metadata = [ "qvm-run", "--pass-io", "sys-usb", f"! [ -d {FWUPD_VM_METADATA_DIR} ]", ] p = subprocess.Popen(cmd_validate_metadata) p.wait() self.assertEqual(p.returncode, 0, msg="Creating metadata directory failed") cmd_validate_udpdate = [ "qvm-run", "--pass-io", "sys-usb", f"! [ -d {FWUPD_VM_UPDATES_DIR} ]", ] p = subprocess.Popen(cmd_validate_udpdate) p.wait() self.assertEqual(p.returncode, 0, msg="Cleaning update directory failed") 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(check_usbvm(), REQUIRED_USBVM) def test_get_devices_qubes_usbvm(self): get_devices_output = io.StringIO() sys.stdout = get_devices_output self.q.get_devices_qubes(usbvm=True) 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 @unittest.skipUnless(device_connected_usbvm(), REQUIRED_DEV) def test_get_updates_qubes_usbvm(self): get_updates_output = io.StringIO() sys.stdout = get_updates_output self.q.get_updates_qubes(usbvm=True) 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" self.q._verify_dmi("test/logs/", "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" self.q._verify_dmi("test/logs/metainfo_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: self.q._verify_dmi("test/logs/metainfo_version/", "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(l_ver(old_version), l_ver(new_version)) @unittest.skipUnless( check_whonix_updatevm() and device_connected_usbvm(), REQUIRED_DEV ) def test_update_n_downgrade_firmware_whonix(self): old_version = None self.q.clean_cache(usbvm=True) self.q._get_dom0_devices() dom0_downgrades = self.q._parse_downgrades(self.q.dom0_devices_info) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() downgrades = self.q._parse_downgrades(raw) 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 + len(dom0_downgrades)), "1"] with patch("builtins.input", side_effect=user_input): self.q.downgrade_firmware(usbvm=True, whonix=True) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() downgrades = self.q._parse_downgrades(raw) new_version = downgrades[number]["Version"] self.assertGreater(l_ver(old_version), l_ver(new_version)) old_version = None new_version = None self.q._get_dom0_updates() self.q._parse_dom0_updates_info(self.q.dom0_updates_info) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() self.q._parse_usbvm_updates(raw) for number, device in enumerate(self.q.usbvm_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 + len(self.q.dom0_updates_list)), "1"] with patch("builtins.input", side_effect=user_input): self.q.update_firmware(usbvm=True, whonix=True) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() usbvm_devices_info_dict = json.loads(raw) for device in usbvm_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(l_ver(old_version), l_ver(new_version)) @unittest.skipUnless(device_connected_usbvm(), REQUIRED_DEV) def test_downgrade_firmware_usbvm(self): old_version = None self.q._get_dom0_devices() dom0_downgrades = self.q._parse_downgrades(self.q.dom0_devices_info) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() downgrades = self.q._parse_downgrades(raw) 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 + len(dom0_downgrades)), "1"] with patch("builtins.input", side_effect=user_input): self.q.downgrade_firmware(usbvm=True) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() downgrades = self.q._parse_downgrades(raw) new_version = downgrades[number]["Version"] self.assertGreater(l_ver(old_version), l_ver(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_usbvm(self): user_input = ["2", "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 = {"usbvm": downgrade_list, "dom0": downgrade_list} key, device_choice, downgrade_choice = self.q._user_input( downgrade_dict, downgrade=True, usbvm=True ) self.assertEqual(key, "usbvm") self.assertEqual(device_choice, 0) self.assertEqual(downgrade_choice, 1) 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 = {"dom0": downgrade_list} key, device_choice, downgrade_choice = self.q._user_input( downgrade_dict, downgrade=True ) self.assertEqual(key, "dom0") 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) downgrade_dict = {"usbvm": downgrade_list, "dom0": downgrade_list} N_choice = self.q._user_input(downgrade_dict, downgrade=True, usbvm=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(l_ver(old_version), l_ver(new_version)) @unittest.skipUnless(device_connected_usbvm(), REQUIRED_DEV) def test_update_firmware_usbvm(self): old_version = None new_version = None self.q._get_dom0_updates() self.q._parse_dom0_updates_info(self.q.dom0_updates_info) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() self.q._parse_usbvm_updates(raw) for number, device in enumerate(self.q.usbvm_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 + len(self.q.dom0_updates_list)), "1"] with patch("builtins.input", side_effect=user_input): self.q.update_firmware(usbvm=True) self.q._get_usbvm_devices() with open(FWUPD_VM_LOG) as usbvm_device_info: raw = usbvm_device_info.read() usbvm_devices_info_dict = json.loads(raw) for device in usbvm_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(l_ver(old_version), l_ver(new_version)) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_get_usbvm_devices(self): self.q._get_usbvm_devices() self.assertTrue(os.path.exists(FWUPD_VM_LOG)) def test_parse_usbvm_updates(self): self.q._parse_usbvm_updates(GET_DEVICES) self.assertEqual(self.q.usbvm_updates_list[0]["Name"], "ColorHug2") self.assertEqual(self.q.usbvm_updates_list[0]["Version"], "2.0.6") self.assertListEqual( self.q.usbvm_updates_list[0]["Releases"], [ { "Checksum": "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda", "Description": "

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

    ", "Url": "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Version": "2.0.7", } ], ) def test_parse_usbvm_updates_no_updates_available(self): self.q._parse_usbvm_updates(GET_DEVICES_NO_UPDATES) self.assertListEqual(self.q.usbvm_updates_list, []) def test_updates_crawler(self): crawler_output = io.StringIO() sys.stdout = crawler_output self.q._parse_usbvm_updates(GET_DEVICES) self.q._updates_crawler(self.q.usbvm_updates_list, usbvm=True) with open("test/logs/get_updates.log", "r") as getupdates: self.assertEqual( getupdates.read(), crawler_output.getvalue().strip() + "\n" ) sys.stdout = self.captured_output @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_validate_usbvm_dirs(self): self.q._validate_usbvm_dirs() cmd_validate_metadata = [ "qvm-run", "--pass-io", "sys-usb", f"[ -d {FWUPD_VM_METADATA_DIR} ]", ] p = subprocess.Popen(cmd_validate_metadata) p.wait() self.assertEqual(p.returncode, 0, msg="Creating metadata directory failed") cmd_validate_udpdate = [ "qvm-run", "--pass-io", "sys-usb", f"[ -d {FWUPD_VM_UPDATES_DIR} ]", ] p = subprocess.Popen(cmd_validate_udpdate) p.wait() self.assertEqual(p.returncode, 0, msg="Creating update directory failed") @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_copy_usbvm_metadata(self): self.q.metadata_file = FWUPD_DOM0_METADATA_FILE self.q.metadata_file_jcat = self.q.metadata_file + ".jcat" self.q._download_metadata() self.q._validate_usbvm_dirs() self.q._copy_usbvm_metadata() cmd_validate_metadata_file = [ "qvm-run", "--pass-io", "sys-usb", f"[ -f {FWUPD_VM_METADATA_FILE} ]", ] p = subprocess.Popen(cmd_validate_metadata_file) p.wait() self.assertEqual(p.returncode, 0, msg="Metadata file does not exist") cmd_validate_metadata_jcat = [ "qvm-run", "--pass-io", "sys-usb", f"[ -f {FWUPD_VM_METADATA_FILE_JCAT} ]", ] p = subprocess.Popen(cmd_validate_metadata_jcat) p.wait() self.assertEqual(p.returncode, 0, msg="Metadata jcat signature does not exist") @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_enable_lvfs_testing_dom0(self): if os.path.exists(LVFS_TESTING_DOM0_FLAG): os.remove(LVFS_TESTING_DOM0_FLAG) self.q._enable_lvfs_testing_dom0() self.assertTrue(os.path.exists(LVFS_TESTING_DOM0_FLAG)) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_enable_lvfs_testing_usbvm(self): cmd_validate_flag = [ "qvm-run", "--pass-io", USBVM_N, ( "script --quiet --return --command " f'"ls {LVFS_TESTING_USBVM_FLAG} &>/dev/null"' ), ] cmd_rm_flag = [ "qvm-run", "--pass-io", USBVM_N, ("script --quiet --return --command " f'"rm {LVFS_TESTING_USBVM_FLAG}"'), ] flag = subprocess.Popen(cmd_validate_flag) flag.wait() if flag.returncode == 0: rm_flag = subprocess.Popen(cmd_rm_flag) rm_flag.wait() if rm_flag.returncode != 0: raise Exception("Removing lvfs-testing flag failed!!") self.q._enable_lvfs_testing_usbvm(usbvm=True) flag = subprocess.Popen(cmd_validate_flag) flag.wait() self.assertEqual(flag.returncode, 0) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_validate_usbvm_metadata(self): self.q.metadata_file = FWUPD_DOM0_METADATA_FILE self.q.metadata_file_jcat = self.q.metadata_file + ".jcat" self.q._download_metadata() self.q._validate_usbvm_dirs() self.q._copy_usbvm_metadata() self.q._validate_usbvm_metadata() @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_refresh_usbvm_metadata(self): self.q.metadata_file = FWUPD_DOM0_METADATA_FILE self.q.metadata_file_jcat = self.q.metadata_file + ".jcat" self.q.lvfs = "lvfs" self.q._download_metadata() self.q._validate_usbvm_dirs() self.q._copy_usbvm_metadata() self.q._validate_usbvm_metadata() self.q._refresh_usbvm_metadata() @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_clean_usbvm(self): self.q._validate_usbvm_dirs() self.q._clean_usbvm() cmd_validate_metadata = [ "qvm-run", "--pass-io", "sys-usb", f"! [ -d {FWUPD_VM_METADATA_DIR} ]", ] p = subprocess.Popen(cmd_validate_metadata) p.wait() self.assertEqual(p.returncode, 0, msg="Cleaning metadata directory failed") cmd_validate_udpdate = [ "qvm-run", "--pass-io", "sys-usb", f"! [ -d {FWUPD_VM_METADATA_DIR} ]", ] p = subprocess.Popen(cmd_validate_udpdate) p.wait() self.assertEqual(p.returncode, 0, msg="Cleaning update directory failed") @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_validate_usbvm_archive(self): url = "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab" sha = "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda" name = url.replace("https://fwupd.org/downloads/", "") self.q._clean_usbvm() self.q._validate_usbvm_dirs() self.q._download_firmware_updates(url, sha) self.q._copy_firmware_updates(name) self.q._validate_usbvm_archive(name, sha) cmd_validate_udpdate = [ "qvm-run", "--pass-io", "sys-usb", "[ -f %s ]" % os.path.join(FWUPD_VM_UPDATES_DIR, name), ] p = subprocess.Popen(cmd_validate_udpdate) p.wait() self.assertEqual(p.returncode, 0, msg="Archive validation failed") @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_check_usbvm(self): self.q.check_usbvm() self.assertIn(XL_LIST_LOG, self.q.output) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_bios_refresh_metadata(self): sys_usb = self.q.check_usbvm() Path(BIOS_UPDATE_FLAG).touch(mode=0o644, exist_ok=True) self.q.refresh_metadata_after_bios_update(usbvm=sys_usb) self.assertEqual( self.q.output, "Successfully refreshed metadata manually\n", msg="Metadata refresh failed.", ) @unittest.skipUnless(check_usbvm(), REQUIRED_USBVM) def test_trusted_cleanup(self): trusted_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, "trusted.cab") if not os.path.exists(trusted_path): Path(FWUPD_DOM0_UPDATES_DIR).mkdir(exist_ok=True) Path(trusted_path).touch(mode=0o644, exist_ok=True) os.mkdir(trusted_path.replace(".cab", "")) self.q.trusted_cleanup(usbvm=True) self.assertFalse(os.path.exists(trusted_path)) self.assertFalse(os.path.exists(trusted_path.replace(".cab", ""))) if __name__ == "__main__": unittest.main() fwupd-1.7.5/contrib/reformat-code.py000077500000000000000000000046621420024370600174230ustar00rootroot00000000000000#!/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 = "origin/%s" % 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("Reformatting code against %s" % 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("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) cmd = [formatter, "-p1"] if args.debug: print(cmd) ret = subprocess.run(cmd, input=ret.stdout, capture_output=True, text=True) if ret.returncode: print("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) cmd = ["patch", "-p0"] if args.debug: print(cmd) ret = subprocess.run(cmd, input=ret.stdout, capture_output=True, text=True) if ret.returncode: print("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) sys.exit(0) fwupd-1.7.5/contrib/run-tests.sh000077500000000000000000000000551420024370600166120ustar00rootroot00000000000000#!/bin/sh meson build && ninja -C build test fwupd-1.7.5/contrib/setup000077500000000000000000000072421420024370600154020ustar00rootroot00000000000000#!/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_dev() { read -p "Set up dbus activated daemon and PolicyKit actions from /usr/local? (y/N) " question if [ "$question" = "y" ]; then ./contrib/prepare-system /usr/local install fi } setup_unsafe_polkit_rules() { read -p "Install developer-friendly **unsafe** PolicyKit rules into /etc/polkit-1/rules.d? (y/N) " question if [ "$question" = "y" ]; then sudo cp ./policy/org.freedesktop.fwupd-unsafe.rules /etc/polkit-1/rules.d/ fi } setup_vscode() { # Add default vscode settings if not existing SETTINGS_FILE=./.vscode/settings.json SETTINGS_TEMPLATE_FILE=./contrib/vscode/settings.json if [ ! -f "$SETTINGS_FILE" ]; then mkdir ./.vscode echo "Copy $SETTINGS_TEMPLATE_FILE to $SETTINGS_FILE." cp "$SETTINGS_TEMPLATE_FILE" "$SETTINGS_FILE" fi } 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_precommit() { echo "Configuring pre-commit hooks" python3 -m venv venv source venv/bin/activate 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() { if ! python3 $HELPER test-markdown; then install_pip markdown --upgrade fi } 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 "$@" #always setup pre-commit setup_precommit #always setup git environment setup_git #if interactive install build deps and prepare environment if [ -t 2 ]; then case $OS in debian|ubuntu|arch|fedora) setup_deps setup_run_dev ;; void) setup_deps ;; esac setup_unsafe_polkit_rules check_markdown setup_vscode rename_branch setup_prepush fi fwupd-1.7.5/contrib/setup-win32.nsi.in000066400000000000000000000100541420024370600175270ustar00rootroot00000000000000#!Nsis Installer Command Script # # To build an installer from the script you would normally do: # # dnf install mingw32-nsis # makensis setup-win32.nsi Name "" OutFile "setup/fwupd-@FWUPD_VERSION@-setup-x86_64.exe" InstallDir "$ProgramFiles\fwupd" InstallDirRegKey HKLM SOFTWARE\fwupd "Install_Dir" ShowInstDetails hide ShowUninstDetails hide XPStyle on Page directory Page instfiles ComponentText "Select which optional components you want to install." DirText "Please select the installation folder." Section "fwupd" SectionIn RO SetOutPath "$INSTDIR\bin" # deps File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/iconv.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libarchive-13.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbrotlicommon.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbrotlidec.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libbz2-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libcrypto-1_1-x64.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libssh2-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libssl-1_1-x64.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libssp-0.dll" File /r "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libffi-*.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgcc_s_seh-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgio-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libglib-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgmodule-2.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgmp-10.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgnutls-30.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgnutls-30.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgobject-2.0-0.dll" File /r "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libhogweed-*.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libidn2-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libintl-8.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libjson-glib-1.0-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/liblzma-5.dll" File /r "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libnettle-*.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libp11-kit-0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libpcre-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libcurl-4.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libtasn1-6.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libusb-1.0.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libwinpthread-1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/libxml2-2.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/zlib1.dll" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/curl.exe" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/gspawn-win64-helper-console.exe" File "/usr/x86_64-w64-mingw32/sys-root/mingw/bin/gspawn-win64-helper.exe" File "/etc/pki/tls/certs/ca-bundle.crt" # fwupd File "dfu-tool.exe" File "jcat-tool.exe" File "fwupdtool.exe" File "libfwupd-2.dll" File "libfwupdplugin-@FWUPD_PLUGINVER@.dll" File "libgcab-1.0-0.dll" File "libxmlb-2.dll" File "libjcat-1.dll" File "libgusb-2.dll" SetOutPath "$INSTDIR\fwupd-plugins-@FWUPD_PLUGINVER@" File /r "fwupd-plugins-@FWUPD_PLUGINVER@/libfu_plugin_*.dll" SetOutPath "$INSTDIR\etc\fwupd" File "etc/fwupd/daemon.conf" SetOutPath "$INSTDIR\etc\pki\fwupd" File "etc/pki/fwupd/LVFS-CA.pem" SetOutPath "$INSTDIR\share\fwupd\quirks.d" File /r "share/fwupd/quirks.d/*.quirk" SetOutPath "$INSTDIR\etc\fwupd\remotes.d" File "etc/fwupd/remotes.d/lvfs.conf" File "etc/fwupd/remotes.d/lvfs-testing.conf" ReadEnvStr $0 COMSPEC SetOutPath "$INSTDIR" SectionEnd Section "Uninstall" RMDir /rebootok /r "$SMPROGRAMS\fwupd" RMDir /rebootok /r "$INSTDIR\bin" RMDir /rebootok /r "$INSTDIR\etc" RMDir /rebootok /r "$INSTDIR\lib" RMDir /rebootok /r "$INSTDIR\share" RMDir /rebootok "$INSTDIR" SectionEnd Section -post WriteUninstaller "$INSTDIR\Uninstall fwupd.exe" SectionEnd fwupd-1.7.5/contrib/snap/000077500000000000000000000000001420024370600152505ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/README.md000066400000000000000000000015631420024370600165340ustar00rootroot00000000000000# 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.7.5/contrib/snap/activate-shutdown/000077500000000000000000000000001420024370600207215ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/activate-shutdown/Makefile000066400000000000000000000005221420024370600223600ustar00rootroot00000000000000build: true install: install -d ${DESTDIR}/etc/systemd/system/ install -m0644 fwupd-activate.service ${DESTDIR}/etc/systemd/system # fixes up shutdown activation script for classic snap sed -i "s,/libexec/fwupd/,/snap/bin/fwupd.," \ ${SNAPCRAFT_STAGE}/lib/systemd/system-shutdown/fwupd.shutdown fwupd-1.7.5/contrib/snap/activate-shutdown/fwupd-activate.service000066400000000000000000000003231420024370600252240ustar00rootroot00000000000000[Unit] Description=Activate fwupd updates After=snapd.service [Service] Type=oneshot RemainAfterExit=true ExecStop=/snap/bin/fwupd.fwupdtool activate SuccessExitStatus=0 2 [Install] WantedBy=multi-user.target fwupd-1.7.5/contrib/snap/dbxtool.wrapper000077500000000000000000000000721420024370600203270ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/dbxtool $@ fwupd-1.7.5/contrib/snap/dfu-tool.wrapper000077500000000000000000000000731420024370600204060ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/dfu-tool $@ fwupd-1.7.5/contrib/snap/fix-bash-completion/000077500000000000000000000000001420024370600211205ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/fix-bash-completion/Makefile000066400000000000000000000007521420024370600225640ustar00rootroot00000000000000build: true install: #fixes up fwupdtool -> fwupd.fwupdtool sed -i "s,\(complete -F _fwupd[a-z]*\) \(fwupd.*\),\1 fwupd.\2,; \ s,\(command.*\)\(fwupdtool\),\1fwupd.\2,; \ s,\(command.*\)\(fwupdagent\),\1fwupd.\2," \ ${SNAPCRAFT_STAGE}/share/bash-completion/completions/* # fixes up dbus service for classic snap sed -i 's!SystemdService=\(.*\)!SystemdService=snap.fwupd.fwupd.service!' \ ${SNAPCRAFT_STAGE}/share/dbus-1/system-services/org.freedesktop.fwupd.service fwupd-1.7.5/contrib/snap/fwup-efi-signed/000077500000000000000000000000001420024370600202415ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/fwup-efi-signed/Makefile000066400000000000000000000010711420024370600217000ustar00rootroot00000000000000DEB_HOST_ARCH=$(shell dpkg-architecture -q DEB_HOST_ARCH) EFI_NAME := UNKNOWN-EFI-NAME ifeq ($(DEB_HOST_ARCH),amd64) EFI_NAME := x64 endif ifeq ($(DEB_HOST_ARCH),i386) EFI_NAME := ia32 endif ifeq ($(DEB_HOST_ARCH),arm64) EFI_NAME := aa64 endif ifeq ($(DEB_HOST_ARCH),armhf) EFI_NAME := arm endif SIGNED := \ fwupd$(EFI_NAME).efi.signed all: $(SIGNED) $(SIGNED): ./download-fwupd install: $(SIGNED) install -d $(DESTDIR)/libexec/fwupd/efi install -m0644 $(SIGNED) $(SIGNED).version \ $(DESTDIR)/libexec/fwupd/efi clean: rm -f $(SIGNED) $(SIGNED).version fwupd-1.7.5/contrib/snap/fwup-efi-signed/download-fwupd000077500000000000000000000020151420024370600231170ustar00rootroot00000000000000#! /usr/bin/python3 import re import shutil from urllib.parse import urlparse, urlunparse from urllib.request import urlopen import apt import apt_pkg ARCH_TO_EFI_NAME = { "amd64": "x64", "i386": "ia32", "arm64": "aa64", "armhf": "arm", } arch = apt_pkg.config["Apt::Architecture"] efi_name = ARCH_TO_EFI_NAME[arch] cache = apt.Cache() fwupd_efi = cache["fwupd"].candidate pool_parsed = urlparse(fwupd_efi.uri) dists_dir = "/dists/devel/main/uefi/fwupd-%s/current/" % (fwupd_efi.architecture) DOWNLOAD_LIST = { "fwupd%s.efi.signed" % efi_name: "fwupd%s.efi.signed" % efi_name, "version": "fwupd%s.efi.signed.version" % efi_name, } for base in DOWNLOAD_LIST: dists_parsed = list(pool_parsed) dists_parsed[2] = re.sub(r"/pool/.*", dists_dir + base, dists_parsed[2]) dists_uri = urlunparse(dists_parsed) target = DOWNLOAD_LIST[base] print("Downloading %s to %s..." % (dists_uri, target)) with urlopen(dists_uri) as dists, open(target, "wb") as out: shutil.copyfileobj(dists, out) fwupd-1.7.5/contrib/snap/fwupd-command000077500000000000000000000024761420024370600177500ustar00rootroot00000000000000#!/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" #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 # don't update between versions, we want to preserve previous data [ ! -d "$SNAP_USER_DATA/etc" ] && [ -d "$SNAP/etc" ] && cp -R "$SNAP/etc" "$SNAP_USER_DATA" [ ! -d "$SNAP_USER_DATA/var" ] && [ -d "$SNAP/var" ] && cp -R "$SNAP/var" "$SNAP_USER_DATA" # 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 exec "$@" fwupd-1.7.5/contrib/snap/fwupd.wrapper000077500000000000000000000001021420024370600177730ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/libexec/fwupd/fwupd $@ fwupd-1.7.5/contrib/snap/fwupdagent.wrapper000077500000000000000000000000751420024370600210230ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdagent $@ fwupd-1.7.5/contrib/snap/fwupdmgr.wrapper000077500000000000000000000000731420024370600205100ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdmgr $@ fwupd-1.7.5/contrib/snap/fwupdtool.wrapper000077500000000000000000000000741420024370600207010ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdtool $@ fwupd-1.7.5/contrib/snap/libefivar-fixpkgconfig/000077500000000000000000000000001420024370600216675ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/libefivar-fixpkgconfig/Makefile000066400000000000000000000011601420024370600233250ustar00rootroot00000000000000build: true install: sed -i 's!libdir=\(.*\)!libdir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!includedir=\(.*\)!includedir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!Cflags:\(.*\)!Cflags:\1 -L$$\{libdir\}!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efiboot.pc sed -i 's!libdir=\(.*\)!libdir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc sed -i 's!includedir=\(.*\)!includedir=${SNAPCRAFT_STAGE}\1!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc sed -i 's!Cflags:\(.*\)!Cflags:\1 -L$$\{libdir\}!' ${SNAPCRAFT_STAGE}/lib/pkgconfig/efivar.pc fwupd-1.7.5/contrib/snap/update-mime/000077500000000000000000000000001420024370600174575ustar00rootroot00000000000000fwupd-1.7.5/contrib/snap/update-mime/Makefile000066400000000000000000000002021420024370600211110ustar00rootroot00000000000000build: true install: update-mime-database ../install/usr/share/mime glib-compile-schemas ../install/usr/share/glib-2.0/schemas fwupd-1.7.5/contrib/standalone-installer/000077500000000000000000000000001420024370600204325ustar00rootroot00000000000000fwupd-1.7.5/contrib/standalone-installer/README.md000066400000000000000000000004361420024370600217140ustar00rootroot00000000000000# 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.7.5/contrib/standalone-installer/assets/000077500000000000000000000000001420024370600217345ustar00rootroot00000000000000fwupd-1.7.5/contrib/standalone-installer/assets/header.py000066400000000000000000000247011420024370600235420ustar00rootroot00000000000000#!/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 = "/root/snap/%s/common" % app # 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 = ["%s.fwupdmgr" % 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 = ["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 = "%s/.var/app/%s" % (os.getenv("HOME"), 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("fwupd %s is installed and must be removed" % version.version) 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( "allow-older argument doesn't make sense with command %s" % args.command ) if args.cleanup: error("Cleanup argument doesn't make sense with command %s" % args.command) if args.directory is None: error("No directory specified") if not os.path.exists(args.directory): print("Creating %s" % 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.7.5/contrib/standalone-installer/make.py000077500000000000000000000115511420024370600217270ustar00rootroot00000000000000#!/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("%s doesn't exist" % local) 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.7.5/contrib/vscode/000077500000000000000000000000001420024370600155725ustar00rootroot00000000000000fwupd-1.7.5/contrib/vscode/README.md000066400000000000000000000030231420024370600170470ustar00rootroot00000000000000# 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 `./contrib/debugging/build.sh` to build fwupd with all default options and create helper scripts pre-configured for debugger use. The application will be placed into `./dist` and helper scripts will be created for `fwupdtool`, `fwupdmgr`, and `fwupd`. ## Running To run any of the applications, execute the appropriate helper script in `./dist`. ## 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 sudo DEBUG=1 ./dist/fwupdtool.sh 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.7.5/contrib/vscode/build.sh000077500000000000000000000016021420024370600172270ustar00rootroot00000000000000#!/bin/sh # Copyright (C) 2018 Dell, Inc. SOURCE=$(dirname $0) ROOT=$1 if [ -z "$ROOT" ]; then ROOT=`pwd` fi # build in tree sudo rm -rf build ${ROOT}/dist meson build --prefix=${ROOT}/dist -Dsystemd=false -Dudevdir=${ROOT}/dist -Ddocs=none ninja -C build install #create helper scripts TEMPLATE=${SOURCE}/launcher.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,libexec/fwupd/fwupd," \ ${TEMPLATE} > ${ROOT}/dist/fwupd.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,bin/fwupdtool," \ ${TEMPLATE} > ${ROOT}/dist/fwupdtool.sh sed "s,#ROOT#,${ROOT},; s,#EXECUTABLE#,bin/fwupdmgr," \ ${TEMPLATE} > ${ROOT}/dist/fwupdmgr.sh chmod +x ${ROOT}/dist/*.sh #create debugging targets TARGET=${ROOT}/.vscode mkdir -p ${TARGET} if [ -f ${TARGET}/launch.json ]; then echo "${TARGET}/launch.json already exists, not overwriting" else cp ${SOURCE}/launch.json ${TARGET} fi fwupd-1.7.5/contrib/vscode/launch.json000066400000000000000000000054541420024370600177470ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "gdbserver (fwupdtool)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/dist/libexec/fwupd/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}/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}/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.7.5/contrib/vscode/launcher.sh000077500000000000000000000004441420024370600177340ustar00rootroot00000000000000#!/bin/sh gcc=$(gcc -dumpmachine) export ROOT=#ROOT# export FWUPD_LOCALSTATEDIR=${ROOT}/dist export FWUPD_SYSCONFDIR=${ROOT}/dist/etc export LD_LIBRARY_PATH=${ROOT}/dist/lib/${gcc} if [ -n "${DEBUG}" ]; then DEBUG="gdbserver localhost:9091" fi ${DEBUG} ${ROOT}/dist/#EXECUTABLE# "$@" fwupd-1.7.5/contrib/vscode/settings.json000066400000000000000000000001131420024370600203200ustar00rootroot00000000000000{ "editor.tabSize": 8, "mesonbuild.buildFolder": "build" } fwupd-1.7.5/data/000077500000000000000000000000001420024370600135605ustar00rootroot00000000000000fwupd-1.7.5/data/90-fwupd-devices.rules000066400000000000000000000004311420024370600176250ustar00rootroot00000000000000######################################################################## # 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.7.5/data/bash-completion/000077500000000000000000000000001420024370600166445ustar00rootroot00000000000000fwupd-1.7.5/data/bash-completion/fwupdagent000066400000000000000000000011421420024370600207310ustar00rootroot00000000000000_fwupdagent_cmd_list=( 'get-devices' 'get-updates' 'get-upgrades' 'security' ) _fwupdagent_opts=( '--verbose' ) _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdagent_opts[@]}' -- "$cur") ) } _fwupdagent() { local cur prev command COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $command in *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdagent_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdagent fwupdagent fwupd-1.7.5/data/bash-completion/fwupdmgr000066400000000000000000000072351420024370600204310ustar00rootroot00000000000000_fwupdmgr_cmd_list=( 'activate' 'block-firmware' 'clear-results' 'disable-remote' 'device-test' 'downgrade' 'download' 'enable-remote' 'get-approved-firmware' 'get-blocked-firmware' 'get-details' 'get-devices' 'get-history' 'get-releases' 'get-remotes' 'get-results' 'get-topology' 'get-updates' 'get-upgrades' 'get-plugins' 'install' 'modify-config' 'modify-remote' 'reinstall' 'refresh' 'report-history' 'security' 'set-approved-firmware' 'switch-branch' 'sync-bkc' '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' '--show-all' '--sign' '--filter' '--disable-ssl-strict' '--ipfs' '--json' ) _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_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_remotes() { local remotes remotes="$(command fwupdmgr get-remotes | command awk '/Remote ID/ { print $4 }')" COMPREPLY+=( $(compgen -W "${remotes}" -- "$cur") ) } _fwupdmgr() { local cur prev command COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $prev in --filter) _show_filters return 0 ;; esac case $command in activate|clear-results|downgrade|get-releases|get-results|unlock|verify|verify-update|get-updates|switch-branch|update|upgrade) if [[ "$prev" = "$command" ]]; then _show_device_ids else _show_modifiers fi ;; get-details) #browse for file if [[ "$prev" = "$command" ]]; then _filedir #modifiers else _show_modifiers fi ;; device-test) #browse for files if [[ "$prev" = "$command" ]]; then _filedir #modifiers else _show_modifiers fi ;; install) #find files if [[ "$prev" = "$command" ]]; then _filedir #device ID or modifiers elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _show_device_ids _show_modifiers #modifiers else _show_modifiers fi ;; modify-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #add key elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; 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") ) #modifiers else _show_modifiers fi ;; enable-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #modifiers else _show_modifiers fi ;; disable-remote) #find remotes if [[ "$prev" = "$command" ]]; then _show_remotes #modifiers else _show_modifiers fi ;; refresh) #find first file if [[ "$prev" = "$command" ]]; then _filedir #find second file elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _filedir #find remote ID elif [[ "$prev" = "${COMP_WORDS[3]}" ]]; then _show_remotes else _show_modifiers fi ;; *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdmgr_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdmgr fwupdmgr fwupd-1.7.5/data/bash-completion/fwupdtool000066400000000000000000000064151420024370600206200ustar00rootroot00000000000000_fwupdtool_cmd_list=( 'activate' 'build-firmware' 'clear-history' 'esp-list' 'esp-mount' 'esp-unmount' 'firmware-build' 'firmware-convert' 'firmware-export' 'firmware-extract' 'firmware-parse' 'firmware-sign' 'firmware-patch' 'get-updates' 'get-upgrades' 'get-details' 'get-firmware-types' 'get-device-flags' 'get-devices' 'get-history' 'get-plugins' 'get-remotes' 'get-topology' 'hwids' 'update' 'upgrade' 'install' 'install-blob' 'monitor' 'reinstall' 'security' 'switch-branch' 'self-sign' 'smbios-dump' 'attach' 'detach' 'firmware-dump' 'refresh' 'verify-update' 'watch' 'unbind-driver' 'bind-driver' 'export-hwids' ) _fwupdtool_opts=( '--verbose' '--enable-json-state' '--allow-reinstall' '--allow-older' '--force' '--show-all' '--plugins' '--prepare' '--cleanup' '--filter' '--method' '--disable-ssl-strict' '--no-safety-check' '--ignore-checksum' '--ignore-vid-pid' ) _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_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 COMPREPLY=() cur=`_get_cword` prev=${COMP_WORDS[COMP_CWORD-1]} command=${COMP_WORDS[1]} case $prev in --plugins) _show_plugins return 0 ;; --filter) _show_filters return 0 ;; esac case $command in get-details|install|install-blob|firmware-dump) #find files if [[ "$prev" = "$command" ]]; then _filedir #modifiers else _show_modifiers fi ;; attach|detach|activate|verify-update|reinstall|get-updates) if [[ "$prev" = "$command" ]]; then _show_device_ids #modifiers else _show_modifiers fi ;; build-firmware) #file in if [[ "$prev" = "$command" ]]; then _filedir #file out elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _filedir #script elif [[ "$prev" = "${COMP_WORDS[3]}" ]]; then _filedir #output elif [[ "$prev" = "${COMP_WORDS[4]}" ]]; then _filedir else _show_modifiers fi ;; firmware-parse|firmware-patch) #find files if [[ "$prev" = "$command" ]]; then _filedir #firmware_type elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _show_firmware_types else _show_modifiers fi ;; firmware-convert) #file in if [[ "$prev" = "$command" ]]; then _filedir #file out elif [[ "$prev" = "${COMP_WORDS[2]}" ]]; then _filedir #firmware_type in elif [[ "$prev" = "${COMP_WORDS[3]}" ]]; then _show_firmware_types #firmware_type out elif [[ "$prev" = "${COMP_WORDS[4]}" ]]; then _show_firmware_types else _show_modifiers fi ;; *) #find first command if [[ ${COMP_CWORD} = 1 ]]; then COMPREPLY=( $(compgen -W '${_fwupdtool_cmd_list[@]}' -- "$cur") ) #modifiers for all commands else _show_modifiers fi ;; esac return 0 } complete -F _fwupdtool fwupdtool fwupd-1.7.5/data/bash-completion/meson.build000066400000000000000000000006601420024370600210100ustar00rootroot00000000000000if bashcomp.found() completions_dir = bashcomp.get_pkgconfig_variable('completionsdir', define_variable: bashcomp.version().version_compare('>= 2.10') ? ['datadir', datadir] : ['prefix', prefix], ) install_data(['fwupdtool'], install_dir : completions_dir, ) if build_daemon install_data(['fwupdagent', 'fwupdmgr'], install_dir : completions_dir, ) endif # build_daemon endif # bashcomp.found() fwupd-1.7.5/data/builder/000077500000000000000000000000001420024370600152065ustar00rootroot00000000000000fwupd-1.7.5/data/builder/README.md000066400000000000000000000045761420024370600165010ustar00rootroot00000000000000Building Firmware ================= Most of the time when you’re distributing firmware you have permission from the OEM or ODM to redistribute the non-free parts of the system firmware, e.g. Dell can re-distribute the proprietary Intel Management Engine as part as the firmware capsule that gets flashed onto the hardware. In some cases that’s not possible, for example for smaller vendors or people selling OpenHardware. For reasons (IFD, FMAP and CBFS…) you need to actually build the target firmware on the system you’re deploying onto, where build means executing random low-level tools to push random blobs of specific sizes into specific unnecessarily complex partition formats rather than actually compiling .c into executable code. The solution of a manually updated interactive bash script isn’t awesome from a user-experience or security point of view. The other things that might be required is a way to `dd` a few bytes of randomness into the target image at a specific offset and also to copy the old network MAC address into the new firmware. The firmware-builder functionality allows you to ship an archive (typically in `.tar` format, as the `.cab` file will be compressed already) within the `.cab` file as the main “release”. Within the `.tar` archive will be a startup.sh file and all the utilities or scripts needed to run the build operation, statically linked if required. At firmware deploy time fwupd will explode the tar file into a newly-created temp directory, create a bubblewrap container which has no network and limited file-system access and then run the startup.sh script. Once complete, fwupd will copy out just the `firmware.bin` file and then destroy the bubblewrap container and the temporary directory. This is the directory that is available to the bubble-wrap confined script. If, for instance, a plugin needs the old system firmware blob (for a bsdiff) then the plugin can write to this directory and the startup.sh script will be able to access it as the chroot-ed `/boot`. Firmware `.cab` files using this functionality should list the `.tar` file: and also should include the name of the script to run as additional metadata: startup.sh firmware.bin fwupd-1.7.5/data/builder/meson.build000066400000000000000000000001331420024370600173450ustar00rootroot00000000000000install_data('README.md', install_dir : join_paths(datadir, 'doc', 'fwupd', 'builder') ) fwupd-1.7.5/data/cfi.quirk000066400000000000000000000027641420024370600154070ustar00rootroot00000000000000[CFI\FLASHID_3730] Name = A25Lxxx CfiDeviceCmdChipErase = 0xc7 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_1F65] Name = AT25F512A/B CfiDeviceCmdReadId = 0x15 CfiDeviceCmdChipErase = 0x62 CfiDeviceCmdSectorErase = 0x00 FirmwareSizeMax = 0x10000 [CFI\FLASHID_1C31] Name = EN25Fxx CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_C840] Name = GD25Qxxx CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_0020] Name = M25PxxA/xx CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0x00 [CFI\FLASHID_C220] Name = MX25Lxxx/xxxC/xxxE CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_C222] Name = MX25Lxxx1E CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 [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 #[CFI\FLASHID_XXXX] #Name = FM25Fxxx #CfiDeviceCmdChipErase = 0xC7 #CfiDeviceCmdSectorErase = 0x20 #[CFI\FLASHID_XXXX] #Name = KH25LxxxxE #CfiDeviceCmdChipErase = 0x60 #CfiDeviceCmdSectorErase = 0x20 #[CFI\FLASHID_XXXX] #Name = MX25Vxxx #CfiDeviceCmdChipErase = 0x60 #CfiDeviceCmdSectorErase = 0x20 fwupd-1.7.5/data/daemon.conf000066400000000000000000000036121420024370600156740ustar00rootroot00000000000000[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.7.5/data/device-tests/000077500000000000000000000000001420024370600161575ustar00rootroot00000000000000fwupd-1.7.5/data/device-tests/8bitdo-nes30pro.json000066400000000000000000000005601420024370600217130ustar00rootroot00000000000000{ "name": "8BitDo NES30Pro", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/1cb9a0277f536ecd81ca1cea6fd80d60cdbbdcd8-8Bitdo-SFC30PRO_NES30PRO-4.01.cab", "components": [ { "version": "4.01", "guids": [ "c6566b1b-0c6e-5d2e-9376-78c23ab57bf2" ] } ] } ] } fwupd-1.7.5/data/device-tests/8bitdo-sf30pro.json000066400000000000000000000005561420024370600215430ustar00rootroot00000000000000{ "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.7.5/data/device-tests/8bitdo-sfc30.json000066400000000000000000000005631420024370600211630ustar00rootroot00000000000000{ "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.7.5/data/device-tests/aiaiai-h05.json000066400000000000000000000005561420024370600206670ustar00rootroot00000000000000{ "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.7.5/data/device-tests/bizlink-no-sku-vli.json000066400000000000000000000021141420024370600225140ustar00rootroot00000000000000{ "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.7.5/data/device-tests/dell-kh08p.json000066400000000000000000000014201420024370600207170ustar00rootroot00000000000000{ "name": "Dell KH08P [BCM5719]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/d1e6fd47cf673138ee7f0f0073901262ff4cd06b6f98e1a6ba55eb1998d1a58b-talos2-bcm5719-0.4.44.cab", "components": [ { "version": "0.4.62", "guids": [ "https://fwupd.org/downloads/6cf165037a381eb29c183319e031def6b87e3ce955781ecf73f28751a1365db2-kh08p-bcm5719-0.4.62.cab" ] } ] }, { "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.7.5/data/device-tests/dell-wd19tb.json000066400000000000000000000023261420024370600211050ustar00rootroot00000000000000{ "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.7.5/data/device-tests/fwupd-a3bu-xplained.json000066400000000000000000000012001420024370600226220ustar00rootroot00000000000000{ "name": "LVFS A3BU XPLAINED", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/f5bbeaba1037dce31dd12f349e8148ae35f98b61-a3bu-xplained123.cab", "components": [ { "version": "1.23", "guids": [ "80478b9a-3643-5e47-ab0f-ed28abe1019d" ] } ] }, { "url": "https://fwupd.org/downloads/24d838541efe0340bf67e1cc5a9b95526e4d3702-a3bu-xplained124.cab", "components": [ { "version": "1.24", "guids": [ "80478b9a-3643-5e47-ab0f-ed28abe1019d" ] } ] } ] } fwupd-1.7.5/data/device-tests/fwupd-at90usbkey.json000066400000000000000000000011671420024370600222020ustar00rootroot00000000000000{ "name": "LVFS AT90USBKEY", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/b6bef375597e848971f230cf992c9740f7bf5b92-at90usbkey123.cab", "components": [ { "version": "1.23", "guids": [ "c1874c52-5f6a-5864-926d-ea84bcdc82ea" ] } ] }, { "url": "https://fwupd.org/downloads/47807fd4a94a4d5514ac6bf7a73038e00ed63225-at90usbkey124.cab", "components": [ { "version": "1.24", "guids": [ "c1874c52-5f6a-5864-926d-ea84bcdc82ea" ] } ] } ] } fwupd-1.7.5/data/device-tests/google-servo-micro.json000066400000000000000000000012701420024370600225710ustar00rootroot00000000000000{ "name": "Google Servo Micro", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/3c3123b6eaa89d5469553b301210a9e4cb06efa98a1cb7c6847a1d10bb0a0c4b-servo_micro_v2.4.0.cab", "components": [ { "version": "2.4.0", "guids": [ "13564257-c649-586d-b4e4-4f048d480f36" ] } ] }, { "url": "https://fwupd.org/downloads/1dc362734f138e71fa838ac5503491d297715d7e9bccc0424a62c4a5c68526cc-servo_micro_v2.4.17.cab", "components": [ { "version": "2.4.17", "guids": [ "13564257-c649-586d-b4e4-4f048d480f36" ] } ] } ] } fwupd-1.7.5/data/device-tests/hp-dock-g5.json000066400000000000000000000013101420024370600207030ustar00rootroot00000000000000{ "name": "HP USB-C Dock G5", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/eb866447bb755c00e748cce14918dcbfaec0ec123237daefce5876c769c2bf92-HP-USBC_DOCK_G5-V1.0.11.0.cab", "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", "components": [ { "version": "1.0.13.0", "guids": [ "9434f89a-3351-536d-a281-f70203326833" ] } ] } ] } fwupd-1.7.5/data/device-tests/hughski-colorhug-plus.json000066400000000000000000000014011420024370600233110ustar00rootroot00000000000000{ "name": "Hughski ColorHug Plus", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/5cbff92158331aeb10008ca36fa918a9637dde7bfe31de3e0523d14090be8977-fakedevice01_dfu.cab", "components": [ { "version": "0.1", "guids": [ "dfbaaded-754b-5214-a5f2-46aa3331e8ce", "f5b42624-ff57-5073-ba09-b7c9c04241be" ] } ] }, { "url": "https://fwupd.org/downloads/8bc3afd07a0af3baaab8b19893791dd3972e8305-fakedevice02_dfu.cab", "components": [ { "version": "0.2", "guids": [ "dfbaaded-754b-5214-a5f2-46aa3331e8ce", "f5b42624-ff57-5073-ba09-b7c9c04241be" ] } ] } ] } fwupd-1.7.5/data/device-tests/hughski-colorhug.json000066400000000000000000000012451420024370600223360ustar00rootroot00000000000000{ "name": "Hughski ColorHug", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/9a4e77009da7d3b5f15a1388afeb9e5d41a5a8ae-hughski-colorhug2-1.2.5.cab", "components": [ { "version": "1.2.5", "guids": [ "40338ceb-b966-4eae-adae-9c32edfcc484" ] } ] }, { "url": "https://fwupd.org/downloads/2a066c8a1bfbd99f161c867b4dbe7e51ac36fc2b16ef37b11d18419874fbcb6c-hughski-colorhug-1.2.6.cab", "components": [ { "version": "1.2.6", "guids": [ "40338ceb-b966-4eae-adae-9c32edfcc484" ] } ] } ] } fwupd-1.7.5/data/device-tests/hughski-colorhug2.json000066400000000000000000000012471420024370600224220ustar00rootroot00000000000000{ "name": "Hughski ColorHug2", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "components": [ { "version": "2.0.6", "guids": [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ] } ] }, { "url": "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab", "components": [ { "version": "2.0.7", "guids": [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ] } ] } ] } fwupd-1.7.5/data/device-tests/hyper-no-sku-vli.json000066400000000000000000000021051420024370600222010ustar00rootroot00000000000000{ "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.7.5/data/device-tests/jabra-speak-410.json000066400000000000000000000012031420024370600215300ustar00rootroot00000000000000{ "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.7.5/data/device-tests/jabra-speak-510.json000066400000000000000000000012051420024370600215330ustar00rootroot00000000000000{ "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.7.5/data/device-tests/jabra-speak-710.json000066400000000000000000000012051420024370600215350ustar00rootroot00000000000000{ "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.7.5/data/device-tests/lenovo-03x7168.json000066400000000000000000000013331420024370600213120ustar00rootroot00000000000000{ "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.7.5/data/device-tests/lenovo-03x7605.json000066400000000000000000000013311420024370600213040ustar00rootroot00000000000000{ "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.7.5/data/device-tests/lenovo-03x7608-vli.json000066400000000000000000000011401420024370600220750ustar00rootroot00000000000000{ "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.7.5/data/device-tests/lenovo-03x7609-cxaudio.json000066400000000000000000000007321420024370600227460ustar00rootroot00000000000000{ "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", "components": [ { "name": "cxaudio", "version": "49-0E-14", "guids": [ "dbb8d54c-42e6-5215-b7ac-1df16872bb06" ] } ] } ] } fwupd-1.7.5/data/device-tests/lenovo-40au0065-vli.json000066400000000000000000000026051420024370600222310ustar00rootroot00000000000000{ "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.7.5/data/device-tests/lenovo-GX90T33021-vli.json000066400000000000000000000014101420024370600223420ustar00rootroot00000000000000{ "name": "Lenovo Travel Hub 1in3 [VL211]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/2004603b40bd529d85c2fcfcf9d50d77b47229e6564ef0ea9c03633bdccff94d-Lenovo-Travel_Hub_1in3_New.cab", "components": [ { "name": "tier1", "version": "44.33", "guids": [ "7636b85e-d79f-5d30-a329-458957958b88" ] } ] }, { "url": "https://fwupd.org/downloads/5fb4f4dce233626558806c2b7474d3b5ed4f500f6013aa3b376a54322642d449-Lenovo-Travel_Hub_1in3_Old.cab", "components": [ { "name": "tier1", "version": "04.33", "guids": [ "7636b85e-d79f-5d30-a329-458957958b88" ] } ] } ] } fwupd-1.7.5/data/device-tests/logitech-bolt-receiver.json000066400000000000000000000023121420024370600234060ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-k780.json000066400000000000000000000012471420024370600213430ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-m650.json000066400000000000000000000015031420024370600213340ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-m750.json000066400000000000000000000006201420024370600213340ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-mr0077.json000066400000000000000000000015041420024370600216020ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-rqr12-signed.json000066400000000000000000000010001420024370600230530ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-rqr12.json000066400000000000000000000014421420024370600216160ustar00rootroot00000000000000{ "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.7.5/data/device-tests/logitech-rqr24-signed.json000066400000000000000000000011501420024370600230640ustar00rootroot00000000000000{ "name": "Logitech Unifying Receiver (RQR24 SIGNED)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/b30e729cc544c711a6ad947cef9ce6614b394a7b-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.7.5/data/device-tests/logitech-rqr24.json000066400000000000000000000014421420024370600216210ustar00rootroot00000000000000{ "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.7.5/data/device-tests/meson.build000066400000000000000000000024511420024370600203230ustar00rootroot00000000000000install_data([ '8bitdo-nes30pro.json', '8bitdo-sf30pro.json', '8bitdo-sfc30.json', 'aiaiai-h05.json', 'bizlink-no-sku-vli.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', 'system76-thelio.json', 'ugreen-cm260.json', 'wacom-intuos-bt-m-bluetooth.json', 'wacom-intuos-bt-m.json', 'wacom-intuos-bt-m-main.json', 'wacom-intuos-bt-s.json', ], install_dir : join_paths(datadir, 'fwupd', 'device-tests'), ) fwupd-1.7.5/data/device-tests/nordic-hid-nrf52840-mcuboot.json000066400000000000000000000013611420024370600237270ustar00rootroot00000000000000{ "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.7.5/data/device-tests/realtek-rts5423.json000066400000000000000000000012741420024370600216310ustar00rootroot00000000000000{ "name": "Realtek 4-Port USB Hub", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/460a18d93f5d6118908d8437009ebbd6eceb3c6a0cdfbaf31f8a96df008564da-Realtek-RTS5423-1.56.cab", "components": [ { "version": "1.56", "guids": [ "b2d2fae3-1546-5d16-a9af-ac117a255a91" ] } ] }, { "url": "https://fwupd.org/downloads/659e721b0efbe2dfc003d3d30ea5b771c00113cc9a162333ee5a8918c4515f69-Realtek-RTS5423-1.57.cab", "components": [ { "version": "1.57", "guids": [ "b2d2fae3-1546-5d16-a9af-ac117a255a91" ] } ] } ] } fwupd-1.7.5/data/device-tests/realtek-rts5855.json000066400000000000000000000011621420024370600216360ustar00rootroot00000000000000{ "name": "Realtek RTS5855 Webcam", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/ff989c4b71c92a4a217dfb2f82c1c87691b8eb31-rts5855_v0.4.cab", "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.7.5/data/device-tests/synaptics-prometheus.json000066400000000000000000000006441420024370600232640ustar00rootroot00000000000000{ "name": "Prometheus Fingerprint Reader", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/7c260a13ea6df444f7a1fa8fa2bf431876a8c7203b6aeac371564aad30756ff1-Synaptics-Prometheus-10.01.3121519.cab", "components": [ { "version": "10.01.3121519", "guids": [ "8088f861-6318-5b1e-9ce4-fbddbedb09ac" ] } ] } ] } fwupd-1.7.5/data/device-tests/system76-thelio.json000066400000000000000000000020711420024370600220350ustar00rootroot00000000000000{ "name": "System76 Thelio Io", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/ed423e586cdd35d41f0dd708ea8e5b5b97c54d48eef7e23a02d9a088b231b3fd-thelio-io_0.0.0.cab", "components": [ { "version": "0.0.0", "protocol": "org.usb.dfu", "guids": [ "fdac0b40-51c6-591b-a049-e9cfc05e9271" ] } ] }, { "url": "https://fwupd.org/downloads/1be9bcfa7b5f0db2bca927505f82d8829be5882ca295e33af492d0e1fdacc162-thelio-io_1.0.0.cab", "components": [ { "version": "1.0.0", "protocol": "org.usb.dfu", "guids": [ "fdac0b40-51c6-591b-a049-e9cfc05e9271" ] } ] }, { "url": "https://fwupd.org/downloads/63d4a480162b729fc57ff7c92c1e2254540f43d6-thelio-io_1.0.2.cab", "components": [ { "version": "1.0.2", "protocol": "org.usb.dfu", "guids": [ "fdac0b40-51c6-591b-a049-e9cfc05e9271" ] } ] } ] } fwupd-1.7.5/data/device-tests/ugreen-cm260.json000066400000000000000000000012701420024370600211640ustar00rootroot00000000000000{ "name": "Ugreen CM260", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/53cd390673287a99e5d4cc4cf30b5fade0f5e708fe0b1bd424bf36467642d76c-Ugreen-CM260-7.2.1.0.cab", "components": [ { "version": "7.2.1.0", "guids": [ "7afc5bff-be55-5e95-81ca-584f13207b1d" ] } ] }, { "url": "https://fwupd.org/downloads/eec2c6216fa86077504587633fc8d018a366a1cfe3df8dd9c575c2e8ef0ef423-Ugreen-CM260-7.2.2.0.cab", "components": [ { "version": "7.2.1.0", "guids": [ "7afc5bff-be55-5e95-81ca-584f13207b1d" ] } ] } ] } fwupd-1.7.5/data/device-tests/wacom-intuos-bt-m-bluetooth.json000066400000000000000000000006561420024370600243460ustar00rootroot00000000000000{ "name": "Wacom Intuos BT-M", "interactive": false, "steps": [ { "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.7.5/data/device-tests/wacom-intuos-bt-m-main.json000066400000000000000000000006441420024370600232620ustar00rootroot00000000000000{ "name": "Wacom Intuos BT-M", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/d16b682de56c42b134f1e5af7f9926c62dc246df851648e49aa1d1d0e5b38532-Wacom-Intuos_BT-M_MainFW-1.66.cab", "components": [ { "name": "main", "version": "1.66", "guids": [ "edf56833-dbe5-56ca-a651-734b01bb02ba" ] } ] } ] } fwupd-1.7.5/data/device-tests/wacom-intuos-bt-m.json000066400000000000000000000006561420024370600223430ustar00rootroot00000000000000{ "name": "Wacom Intuos BT-M", "interactive": false, "steps": [ { "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.7.5/data/device-tests/wacom-intuos-bt-s.json000066400000000000000000000020671420024370600223470ustar00rootroot00000000000000{ "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.7.5/data/fish-completion/000077500000000000000000000000001420024370600166605ustar00rootroot00000000000000fwupd-1.7.5/data/fish-completion/fwupdmgr.fish000066400000000000000000000136041420024370600213720ustar00rootroot00000000000000function __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 show-all -d 'Show all results' complete -c fwupdmgr -l disable-ssl-strict -d 'Ignore SSL strict checks when downloading' complete -c fwupdmgr -l ipfs -d 'Use IPFS 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 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-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-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 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 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' # 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.7.5/data/fish-completion/meson.build000066400000000000000000000001471420024370600210240ustar00rootroot00000000000000install_data(['fwupdmgr.fish'], install_dir : join_paths(datadir, 'fish', 'vendor_completions.d'), ) fwupd-1.7.5/data/fwupd-offline-update.service.in000066400000000000000000000006411420024370600215750ustar00rootroot00000000000000[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.7.5/data/fwupd.service.in000066400000000000000000000005701420024370600166760ustar00rootroot00000000000000[Unit] Description=Firmware update daemon Documentation=https://fwupd.org/ After=dbus.service Before=display-manager.service [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.7.5/data/fwupd.shutdown.in000077500000000000000000000004071420024370600171130ustar00rootroot00000000000000#!/bin/sh # no history database exists [ -f @localstatedir@/lib/fwupd/pending.db ] || exit 0 # activate firmware when we have a read-only filesysten if ! @bindir@/fwupdtool activate; then ret=$? [ "$ret" -eq "2" ] && exit 0 exit $ret fi fwupd-1.7.5/data/installed-tests/000077500000000000000000000000001420024370600166775ustar00rootroot00000000000000fwupd-1.7.5/data/installed-tests/README.md000066400000000000000000000055111420024370600201600ustar00rootroot00000000000000# Installed tests A test suite that can be used to interact with a fake device is installed when configured with `-Ddaemon=true` and `-Dtests=true`. By default this test suite is disabled. ## Enabling To enable the test suite: 1. Modify `/etc/fwupd/daemon.conf` to remove the `test` plugin from `DisabledPlugins` ```shell # sed "s,^Enabled=false,Enabled=true," -i /etc/fwupd/remotes.d/fwupd-tests.conf ``` 2. Enable the `fwupd-tests` remote for local CAB files. ```shell # fwupdmgr enable-remote fwupd-tests ``` ## 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.7.5/data/installed-tests/fakedevice123.bin000066400000000000000000000000121420024370600216760ustar00rootroot000000000000000x1020003 fwupd-1.7.5/data/installed-tests/fakedevice123.bin.asc000066400000000000000000000007521420024370600224560ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQEcBAABAgAGBQJfey1HAAoJEEim2A5FOLrCn2MIAK6BnVojGYSwHVpZm58b05Xs rNqozg5pvfDfB0Bde1S0T/4TlDEnJNUku0Gz5IFNbR3ENT5VnJgBkE5xa8Rmv6cy Gm30CmX+UE1E8qK4BVhUdbNN8bEmeMtzUMK2KfpwMXlIqcpSjpln76PQIxMHj+3P 600bkcppkLEKhiOo+THNhiHxPYJ+wjSSPm3paeMmUuApIvP4YFH8uQ5qkKLdLDVI V5QOx3O5P3avmHu936GILG9EwV3TkR1eNOe33OqtrGvpoMTcsxUF0Wc/qmUD066d c9hkTe01paQoN0HW/RMgrIaMnLFwK2mBcwySOo6TU9MIyQfDmLGN3u12nCrmRH8= =Rq70 -----END PGP SIGNATURE----- fwupd-1.7.5/data/installed-tests/fakedevice123.metainfo.xml000066400000000000000000000017601420024370600235420ustar00rootroot00000000000000 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+ ACME Corp

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

    fwupd-1.7.5/data/installed-tests/fakedevice124.bin000066400000000000000000000000121420024370600216770ustar00rootroot000000000000000x1020004 fwupd-1.7.5/data/installed-tests/fakedevice124.bin.asc000066400000000000000000000007521420024370600224570ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQEcBAABAgAGBQJfey2FAAoJEEim2A5FOLrCl88IAKCggwAz3qBrBfrc91sYHCq5 OthMyftOUTQ4JpfISY38k20pwFEhsSHKdKAYDKEVO2jopw+Cr9oTyFycWK20R2lz tUn4e1EF8zQ29OLxGbvgGlP5/4vPJ2Cv5ujkub6LtNBrOMkNJ6+bB6G8nJZRTElU e3wi9+E9oKPBgP40A/y79pzPiFMxXl1piYjU3JNeofd3nbtmyRqb6VAs9exQ94+p CMWWZaJ9igxSAsQiE/NxZpO8qgG3KEmsW7yXRiaIe6xHxb49+JQdjxqS8Oc/C9sX FSiVHDPzlUegZtcRWZy2zeSNTqmu8vzNSei0xEaLCaQ6PO+pQibxS2VZI/jDLdQ= =Gha4 -----END PGP SIGNATURE----- fwupd-1.7.5/data/installed-tests/fakedevice124.metainfo.xml000066400000000000000000000017721420024370600235460ustar00rootroot00000000000000 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+ ACME Corp

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

    fwupd-1.7.5/data/installed-tests/fwupd-tests.xml000066400000000000000000000105421420024370600217100ustar00rootroot00000000000000 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.7.5/data/installed-tests/fwupd.sh000077500000000000000000000011321420024370600203600ustar00rootroot00000000000000#!/bin/sh exec 2>&1 dirname=`dirname $0` run_test() { if [ -f $dirname/$1 ]; then $dirname/$1 rc=$?; if [ $rc != 0 ]; then exit $rc; fi 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 # success! exit 0 fwupd-1.7.5/data/installed-tests/fwupd.test.in000066400000000000000000000002171420024370600213320ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "G_TEST_SRCDIR=@installedtestsdatadir@ G_TEST_BUILDDIR=@installedtestsdatadir@ @installedtestsbindir@/fwupd.sh" fwupd-1.7.5/data/installed-tests/fwupdmgr-p2p.sh000077500000000000000000000011201420024370600215620ustar00rootroot00000000000000#!/bin/sh # 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="/var/run/fwupd.sock" rm -rf ${FWUPD_DBUS_SOCKET} /usr/libexec/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..." gdbus call --system --dest org.freedesktop.fwupd --object-path / --method org.freedesktop.fwupd.Quit # success! exit 0 fwupd-1.7.5/data/installed-tests/fwupdmgr-p2p.test.in000066400000000000000000000001051420024370600225330ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr-p2p.sh" fwupd-1.7.5/data/installed-tests/fwupdmgr.sh000077500000000000000000000042441420024370600210750ustar00rootroot00000000000000#!/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 "Enabling fwupd-tests remote..." fwupdmgr enable-remote fwupd-tests 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.7.5/data/installed-tests/fwupdmgr.test.in000066400000000000000000000001011420024370600220300ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr.sh" fwupd-1.7.5/data/installed-tests/meson.build000066400000000000000000000032731420024370600210460ustar00rootroot00000000000000con2 = configuration_data() con2.set('installedtestsdir', installed_test_datadir) con2.set('installedtestsbindir', installed_test_bindir) con2.set('installedtestsdatadir', installed_test_datadir) con2.set('bindir', bindir) 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 : 'fwupd.test.in', output : 'fwupd.test', configuration : con2, install: true, install_dir: installed_test_datadir, ) install_data([ 'fwupdmgr.sh', 'fwupdmgr-p2p.sh', 'fwupd-tests.xml', ], install_dir : installed_test_datadir, ) install_data([ 'fwupd.sh', ], install_dir : installed_test_bindir, ) custom_target('installed-cab123', input : [ 'fakedevice123.bin', 'fakedevice123.bin.asc', '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.bin.asc', '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(sysconfdir, 'fwupd', 'remotes.d'), ) fwupd-1.7.5/data/installed-tests/remote.conf.in000066400000000000000000000004161420024370600214470ustar00rootroot00000000000000[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 Enabled=false Title=fwupd test suite Keyring=none MetadataURI=file://@installedtestsdir@/fwupd-tests.xml fwupd-1.7.5/data/meson.build000066400000000000000000000070731420024370600157310ustar00rootroot00000000000000subdir('builder') 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(['daemon.conf'], install_dir : join_paths(sysconfdir, 'fwupd') ) install_data(['power.quirk', 'cfi.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d')) endif if get_option('metainfo') install_data(['org.freedesktop.fwupd.metainfo.xml'], 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') ) endif if build_daemon and get_option('gudev') install_data(['90-fwupd-devices.rules'], install_dir : join_paths(udevdir, 'rules.d') ) endif if get_option('systemd') and build_daemon con2 = configuration_data() con2.set('libexecdir', libexecdir) con2.set('bindir', bindir) con2.set('datadir', datadir) con2.set('localstatedir', localstatedir) rw_directories = [] if get_option('plugin_uefi_capsule') rw_directories += ['-/boot/efi', '-/efi/EFI', '-/boot/EFI', '-/boot/grub'] 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 get_option('plugin_redfish') 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 get_option('offline') 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 (get_option('systemd') or get_option('elogind')) and build_daemon 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 fwupd-1.7.5/data/motd/000077500000000000000000000000001420024370600145235ustar00rootroot00000000000000fwupd-1.7.5/data/motd/85-fwupd.motd.in000077500000000000000000000001121420024370600173710ustar00rootroot00000000000000#!/bin/sh if [ -f @motd_fullpath@ ]; then cat @motd_fullpath@ fi fwupd-1.7.5/data/motd/README.md000066400000000000000000000010331420024370600157770ustar00rootroot00000000000000# 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.7.5/data/motd/fwupd-refresh.preset000066400000000000000000000000361420024370600205270ustar00rootroot00000000000000disable fwupd-refresh.service fwupd-1.7.5/data/motd/fwupd-refresh.service.in000066400000000000000000000006241420024370600212750ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata and update motd Documentation=man:fwupdmgr(1) After=network.target [Service] Type=oneshot CacheDirectory=fwupdmgr StandardError=null DynamicUser=yes 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.7.5/data/motd/fwupd-refresh.timer000066400000000000000000000002371420024370600203500ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata regularly [Timer] OnCalendar=*-*-* 6,18:00 RandomizedDelaySec=12h Persistent=true [Install] WantedBy=timers.target fwupd-1.7.5/data/motd/meson.build000066400000000000000000000016121420024370600166650ustar00rootroot00000000000000 if get_option('systemd') install_data(['fwupd-refresh.timer'], install_dir: systemdunitdir) install_data(['fwupd-refresh.preset'], install_dir: systemdsystempresetdir) 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('motd_fullpath', motd_fullpath) if get_option('systemd') 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, ) fwupd-1.7.5/data/org.freedesktop.fwupd.conf000066400000000000000000000020231420024370600206510ustar00rootroot00000000000000 fwupd-1.7.5/data/org.freedesktop.fwupd.metainfo.xml000066400000000000000000003332741420024370600223440ustar00rootroot00000000000000 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/ richard_at_hughsie.com fwupd moderate fwupdmgr fwupdtool fwupdagent

    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 critial 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 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 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 critial 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.7.5/data/org.freedesktop.fwupd.service.in000066400000000000000000000002611420024370600217730ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.fwupd Documentation=https://fwupd.org/ Exec=@libexecdir@/fwupd/fwupd User=root SystemdService=fwupd.service AssumedAppArmorLabel=unconfined fwupd-1.7.5/data/org.freedesktop.fwupd.svg000066400000000000000000000243251420024370600205340ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template fwupd firmwareupdater fwupd-1.7.5/data/pki/000077500000000000000000000000001420024370600143435ustar00rootroot00000000000000fwupd-1.7.5/data/pki/GPG-KEY-Linux-Foundation-Firmware000066400000000000000000000041711420024370600223070ustar00rootroot00000000000000-----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.7.5/data/pki/GPG-KEY-Linux-Foundation-Metadata000066400000000000000000000041711420024370600222530ustar00rootroot00000000000000-----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.7.5/data/pki/GPG-KEY-Linux-Vendor-Firmware-Service000066400000000000000000000016771420024370600230440ustar00rootroot00000000000000-----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.7.5/data/pki/LVFS-CA.pem000066400000000000000000000032171420024370600161040ustar00rootroot00000000000000-----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.7.5/data/pki/meson.build000066400000000000000000000016371420024370600165140ustar00rootroot00000000000000# only install files that are going to be used if libjcat.type_name() != 'internal' and libjcat.version().version_compare('>= 0.1.9') supported_gpg = libjcat.get_pkgconfig_variable('supported_gpg') supported_pkcs7 = libjcat.get_pkgconfig_variable('supported_pkcs7') else supported_gpg = '1' supported_pkcs7 = '1' endif if supported_gpg == '1' 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 == '1' 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.7.5/data/power.quirk000066400000000000000000000002451420024370600157720ustar00rootroot00000000000000#This file provides manufacturer specified minimum battery thresholds [LENOVO] BatteryThreshold = 25 [Star Labs] BatteryThreshold = 30 [HP] BatteryThreshold = 50 fwupd-1.7.5/data/remotes.d/000077500000000000000000000000001420024370600154605ustar00rootroot00000000000000fwupd-1.7.5/data/remotes.d/README.md000066400000000000000000000067461420024370600167540ustar00rootroot00000000000000# 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: * 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 LVFS currently outputs XML with absolute URI locations, e.g. `http://foo/bar.cab` rather than `bar.cab` This makes mirroring the upstream LVFS (or other private instance) somewhat tricky. To work around this issue client remotes can specify `FirmwareBaseURI` to replace the URI of the firmware before it is downloaded. For mirroring the LVFS content to a new CDN, you could use: [fwupd Remote] Enabled=true Type=download Keyring=gpg MetadataURI=https://my.new.cdn/mirror/firmware.xml.gz FirmwareBaseURI=https://my.new.cdn/mirror New instances of the LVFS can actually output a relative URL for firmware files, e.g. `bar.cab` and when downloading the `MetadataURI` name and path prefix is used in this case. This is not enabled for the "upstream" LVFS instance as versions of fwupd older than 1.0.3 are unable to automatically use the `MetadataURI` value for firmware downloads. fwupd-1.7.5/data/remotes.d/lvfs-testing.conf000066400000000000000000000005531420024370600207570ustar00rootroot00000000000000[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.gz ReportURI=https://fwupd.org/lvfs/firmware/report #Username= #Password= OrderBefore=lvfs,fwupd AutomaticReports=false ApprovalRequired=false fwupd-1.7.5/data/remotes.d/lvfs-testing.metainfo.xml000066400000000000000000000027461420024370600224410ustar00rootroot00000000000000 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.7.5/data/remotes.d/lvfs.conf000066400000000000000000000006331420024370600173030ustar00rootroot00000000000000[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.gz ReportURI=https://fwupd.org/lvfs/firmware/report SecurityReportURI=https://fwupd.org/lvfs/hsireports/upload OrderBefore=fwupd AutomaticReports=false AutomaticSecurityReports=false ApprovalRequired=false fwupd-1.7.5/data/remotes.d/lvfs.metainfo.xml000066400000000000000000000023221420024370600207540ustar00rootroot00000000000000 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.7.5/data/remotes.d/meson.build000066400000000000000000000033061420024370600176240ustar00rootroot00000000000000if build_standalone and get_option('lvfs') != 'false' install_data([ 'lvfs-testing.conf', ], install_dir : join_paths(sysconfdir, 'fwupd', 'remotes.d') ) con3 = configuration_data() 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'), ) i18n.merge_file( input: 'lvfs.metainfo.xml', output: 'org.freedesktop.fwupd.remotes.lvfs.metainfo.xml', type: 'xml', po_dir: join_paths(meson.source_root(), 'po'), data_dirs: join_paths(meson.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.source_root(), 'po'), data_dirs: join_paths(meson.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: true, 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.7.5/data/remotes.d/vendor-directory.conf000066400000000000000000000004461420024370600216320ustar00rootroot00000000000000[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=false Title=Vendor (Automatic) Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/firmware ApprovalRequired=false fwupd-1.7.5/data/remotes.d/vendor.conf000066400000000000000000000004721420024370600176270ustar00rootroot00000000000000[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 MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/vendor.xml.gz ApprovalRequired=false fwupd-1.7.5/docs/000077500000000000000000000000001420024370600135775ustar00rootroot00000000000000fwupd-1.7.5/docs/architecture-plan.svg000066400000000000000000002241361420024370600177420ustar00rootroot00000000000000 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.7.5/docs/env.md000066400000000000000000000071551420024370600147210ustar00rootroot00000000000000--- 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_DEVICE_TESTS_BASE_URI` sets the base URI when downloading firmware for the device-tests * `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_DOWNLOAD_VERBOSE` can be used to show wget or curl output * 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` ## Self Tests * `CI_NETWORK` if CI is running with network access * `TPM_SERVER_RUNNING` if an emulated TPM is running ## Shared libfwupdplugin * `FU_HID_DEVICE_VERBOSE` shows HID traffic * `FU_SREC_FIRMWARE_VERBOSE` shows more information about parsing * `FU_UDEV_DEVICE_DEBUG` shows more information about UDEV devices, including parents * `FU_USB_DEVICE_DEBUG` shows more information about USB devices * `FWUPD_DEVICE_LIST_VERBOSE` display devices being added and removed from the list * `FWUPD_PROBE_VERBOSE` dump the detected devices to the console, even if not supported by fwupd ## Plugins Most plugins read a plugin-specific runtime key to increase verbosity more than the usual `VERBOSE`. This can be also used when using fwupdtool e.g. using `--plugin-verbose=dell` will set the environment variable of `FWUPD_DELL_VERBOSE` automatically. 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_PLUGIN_TEST` used by the test plugin to pass data out-of-band to the loader * `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_TEST_PLUGIN_XML` used by the test plugin to load XML state out-of-band before startup * `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 * `FWUPD_WAC_EMULATE` emulates a fake device for testing ## 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_LOCALSTATEDIR` * `FWUPD_LOCALSTATEDIR_QUIRKS` * `FWUPD_OFFLINE_TRIGGER` * `FWUPD_PLUGINDIR` * `FWUPD_PROCFS` * `FWUPD_SYSCONFDIR` * `FWUPD_SYSFSDRIVERDIR` * `FWUPD_SYSFSFWATTRIBDIR` * `FWUPD_SYSFSFWDIR` * `FWUPD_SYSFSSECURITYDIR` * `FWUPD_SYSFSTPMDIR` * `FWUPD_UEFI_ESP_PATH` * `HOME` * `RUNTIME_DIRECTORY` * `SNAP` * `SNAP_USER_DATA` * `STATE_DIRECTORY` fwupd-1.7.5/docs/fwupd-docs.xml000066400000000000000000000060501420024370600163750ustar00rootroot00000000000000 ]> fwupd Reference Manual About fwupd fwupd is a daemon for updating firmware. libfwupd Functionality exported by libfwupd for client applications. Plugin Reference Functionality available to plugins. API Index Index of deprecated API fwupd-1.7.5/docs/fwupd.toml.in000066400000000000000000000030221420024370600162230ustar00rootroot00000000000000[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://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."Json-1.0"] name = "Json" description = "API for efficient parsing and writing of JSON (JavaScript Object Notation) streams" docs_url = "https://developer.gnome.org/json-glib/stable/" [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.7.5/docs/fwupdplugin.toml.in000066400000000000000000000034531420024370600174520ustar00rootroot00000000000000[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", "tutorial.md", "hsi.md", ] content_images = [ "architecture-plan.svg", "../data/org.freedesktop.fwupd.svg", ] 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.7.5/docs/hsi.html000066400000000000000000000010541420024370600152500ustar00rootroot00000000000000 Redirecting to https://fwupd.github.io/libfwupdplugin/hsi.html fwupd-1.7.5/docs/hsi.md000066400000000000000000001012211420024370600147010ustar00rootroot00000000000000--- 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. ### [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]* ## [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. ### [UEFI SecureBoot](#org.fwupd.hsi.Uefi.SecureBoot) 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. **Impact:** When Secure Boot is not enabled any EFI binary can be run at startup, which gives the attacker full access to your hardware. **Possible results:** - `not-found`: support has not been detected - `not-enabled`: detected, but has been turned off - `enabled`: supported and enabled To meet HSI-1 on UEFI systems that run this test, the result must be `enabled`. *[v1.5.0]* **Resolution:** Turn off CSM boot and enable Secure Boot in the BIOS setup. **References:** - [Ubuntu SecureBoot Wiki Page](https://wiki.ubuntu.com/UEFI/SecureBoot) ### [UEFI PK](#org.fwupd.hsi.Uefi.Pk) UEFI defines a platform key for the system. This should not be a test key, e.g. `DO NOT TRUST - AMI Test PK` **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. **Possible results:** - `valid`: valid key - `not-valid`: an invalid key has been enrolled To meet HSI-1 on UEFI systems that run this test, the result must be `valid`. *[v1.5.0]* **References:** - [Ubuntu SecureBoot Wiki Page](https://wiki.ubuntu.com/UEFI/SecureBoot/Testing) ### [BIOS Write Enable (BWE)](#org.fwupd.hsi.Spi.Bioswe) 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. **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. **Possible results:** - `not-found`: the SPI device was not found - `not-enabled`: write enable is disabled - `enabled`: write enable is enabled To meet HSI-1 on systems that run this test, the result must be `not-enabled`. *[v1.5.0]* **References:** - [Intel C200 Datasheet](https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf) ### [BIOS Lock Enable (BLE)](#org.fwupd.hsi.Spi.Ble) 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. **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. **Possible results:** - `enabled`: the register is locked - `not-enabled`: the register is not locked To meet HSI-1 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [Intel C200 Datasheet](https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf) ### [SMM Bios Write Protect (SMM\_BWP)](#org.fwupd.hsi.Spi.SmmBwp) 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. **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. **Possible results:** - `locked`: the region is locked - `not-locked`: the region is not locked To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.5.0]* **References:** - [Intel C200 Datasheet](https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf) ### [Read-only SPI Descriptor](#org.fwupd.hsi.Spi.Descriptor) 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. **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. **Possible results:** - `not-valid`: any region can write to the flash descriptor - `locked`: the SPI BAR is locked and read only from all regions - `not-locked`: the SPI BAR is not locked To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.6.0]* ### [TPM 2.0 Present](#org.fwupd.hsi.Tpm.Version20) A TPM securely stores platform specific secrets that can only be divulged to trusted consumers in a secure environment. **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. **Possible results:** - `found`: device found in v2 mode - `not-found`: no device found - `not-enabled`: not in v2 mode To meet HSI-1 on systems that run this test, the result must be `found`. *[v1.5.0]* **References:** - [TPM Wikipedia Page](https://en.wikipedia.org/wiki/Trusted_Platform_Module) ### [ME not in manufacturing mode](#org.fwupd.hsi.Mei.ManufacturingMode) 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. **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. **Possible results:** - `locked`: device has had manufacturing mode disabled - `not-locked`: device is in manufacturing mode To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.5.0]* **References:** - [ME Manufacturing Mode: obscured dangers](https://malware.news/t/intel-me-manufacturing-mode-obscured-dangers-and-their-relationship-to-apple-macbook-vulnerability-cve-2018-4251/23214) - [Intel security advisory SA-00086](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00086.html) ### [ME Flash Descriptor Override](#org.fwupd.hsi.Mei.OverrideStrap) The Flash Descriptor Security Override Strap is not accessible to end users on consumer boards and Intel stresses that this is for debugging only. **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. **Possible results:** - `locked`: device in in normal runtime mode - `not-locked`: device is in debugging mode To meet HSI-1 on systems that run this test, the result must be `locked`. *[v1.5.0]* **References:** - [Chromium documentation for Intel ME](https://chromium.googlesource.com/chromiumos/third_party/flashrom/+/master/Documentation/mysteries_intel.txt) ### [CSME Version](#org.fwupd.hsi.Mei.Version) 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: CVE-2017-5705, CVE-2017-5708, CVE-2017-5711, CVE-2017-5712, CVE-2017-5711, CVE-2017-5712, CVE-2017-5706, CVE-2017-5709, CVE-2017-5707 or CVE-2017-5710. **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. **Possible results:** - `valid`: is not affected by the most critical CVEs - `not-valid`: affected by one of the below CVEs To meet HSI-1 on systems that run this test, the result must be `valid`. *[v1.5.0]* **References:** - [Intel CSME Security Review Cumulative Update](https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00086.html) ### [Intel DCI](#org.fwupd.hsi.IntelDci.Enabled) Newer Intel CPUs support debugging over USB3 via a proprietary Direct Connection Interface (DCI) with the use of off-the-shelf hardware. **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. **Possible results:** - `enabled`: debugging is currently enabled - `not-enabled`: debugging is not currently enabled To meet HSI-1 on systems that run this test, the result must be `not-enabled`. *[v1.5.0]* **References:** - [Intel Direct Connect Interface](https://www.intel.co.uk/content/www/uk/en/support/articles/000029393/processors.html) - [Chipsec 4xxlp register definitions](https://github.com/chipsec/chipsec/blob/master/chipsec/cfg/8086/pch_4xxlp.xml#L270) - [RISC-V EDK PCH register definitions](https://github.com/riscv/riscv-edk2-platforms/blob/85a50de1b459d1d6644a402081120770aa6dd8c7/Silicon/Intel/CoffeelakeSiliconPkg/Pch/Include/Register/PchRegsDci.h) ### [Intel DCI](#org.fwupd.hsi.IntelDci.Locked) Newer Intel CPUs support debugging over USB3 via a proprietary Direct Connection Interface (DCI) with the use of off-the-shelf hardware. **Impact:** A local attacker with root access would be able to enable DCI. This would allow them full access to all registers and memory in the system, and is able to make changes. This allows using SMM to write to system firmware for a persistent backdoor. **Possible results:** - `locked`: CPU debugging has been disabled - `not-locked`: is is still possible to enable CPU debugging To meet HSI-2 on systems that run this test, the result must be `locked`. *[v1.5.0]* **References:** - [Intel Direct Connect Interface](https://www.intel.co.uk/content/www/uk/en/support/articles/000029393/processors.html) ### [Empty PCR in TPM](#org.fwupd.hsi.Tpm.EmptyPcr) 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. **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. **Possible results:** - `valid`: all correct - `not-valid`: at least one empty checksum has been found - `not-found`: no TPM hardware could be found To meet HSI-1 on systems that run this test, all PCRs from 0 to 7 in all banks must have non-empty measurements *[v1.7.2]* **References:** - [CVE-2021-42299: TPM Carte Blanche](https://github.com/google/security-research/blob/master/pocs/bios/tpm-carte-blanche/writeup.md) ### [PCR0 TPM Event Log Reconstruction](#org.fwupd.hsi.Tpm.ReconstructionPcr0) 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. **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. **Possible results:** - `valid`: all correct - `not-valid`: could not reconstitute the hash value - `not-found`: no TPM hardware could be found To meet HSI-2 on systems that run this test, the result must be `valid`. *[v1.5.0]* **References:** - [Linux Kernel TPM Documentation](https://www.kernel.org/doc/html/latest/security/tpm/tpm_event_log.html) ### [Pre-boot DMA protection](#org.fwupd.hsi.AcpiDmar) 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. The ACPI DMAR table is used to set up pre-boot DMA protection which eliminates some firmware attacks. **Impact:** Without a DMAR table the IOMMU is disabled at boot. 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. **Possible results:** - `enabled`: detected correctly - `not-valid`: could not determine state - `not-enabled`: was not enabled To meet HSI-3 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [IOMMU Wikipedia Page](https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit) ### [Intel BootGuard: Enabled](#org.fwupd.hsi.IntelBootguard.Enabled) 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. **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. **Possible results:** - `enabled`: detected and enabled - `not-enabled`: not detected, or detected but not enabled To meet HSI-2 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [Coreboot documentation](https://github.com/coreboot/coreboot/blob/master/src/soc/intel/jasperlake/include/soc/me.h) ### [Intel BootGuard: Verified](#org.fwupd.hsi.IntelBootguard.Verified) 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. **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. **Possible results:** - `success`: verified boot chain - `not-valid`: boot is not verified To meet HSI-2 on systems that run this test, the result must be `success`. *[v1.5.0]* ### [Intel BootGuard: ACM](#org.fwupd.hsi.IntelBootguard.Acm) 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. **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. **Possible results:** - `valid`: ACM protected - `not-valid`: boot is not verified To meet HSI-2 on systems that run this test, the result must be `valid`. *[v1.5.0]* ### [Intel BootGuard: Policy](#org.fwupd.hsi.IntelBootguard.Policy) 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. **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. **Possible results:** - `valid`: error enforce policy is set to shutdown - `not-valid`: policy is invalid To meet HSI-3 on systems that run this test, the result must be `valid`. *[v1.5.0]* ### [Intel BootGuard: OTP](#org.fwupd.hsi.IntelBootguard.Otp) 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. **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. **Possible results:** - `valid`: SOC is locked - `not-valid`: SOC is not locked To meet HSI-2 on systems that run this test, the result must be `valid`. *[v1.5.0]* ### [Suspend to RAM disabled](#org.fwupd.hsi.SuspendToRam) 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. **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. **Possible results:** - `enabled`: sleep enabled - `not-enabled`: suspend-to-ram being used - `not-valid`: could not determine the default To meet HSI-3 on systems that run this test, the result must be `not-enabled`. *[v1.5.0]* **References:** - [Cold Boot Attack Wikipedia Page](https://en.wikipedia.org/wiki/Cold_boot_attack) ### [Intel CET: Available](#org.fwupd.hsi.IntelCet.Enabled) Control enforcement technology is available on new Intel platforms and prevents exploits from hijacking the control-flow transfer instructions for both forward-edge (indirect call/jmp) and back-edge transfer (ret). **Impact:** A local or physical attacker with an existing unrelated vulnerability can use a reliable and well-known method to run arbitrary code. **Possible results:** - `enabled`: feature enabled by the platform - `not-supported`: not supported To meet HSI-3 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [Intel CET Technology Preview](https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf) ### [Intel CET: Active](#org.fwupd.hsi.IntelCet.Active) Control enforcement technology is available on new Intel platforms and prevents exploits from hijacking the control-flow transfer instructions for both forward-edge (indirect call/jmp) and back-edge transfer (ret). **Impact:** A local or physical attacker with an existing unrelated vulnerability can use a ROP gadget to run arbitrary code. **Possible results:** - `supported`: being used - `not-supported`: not being used by the host To meet HSI-3 on systems that run this test, the result must be `supported`. *[v1.5.0]* **References:** - [Intel CET Technology Preview](https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf) ### [DRAM memory encryption](#org.fwupd.hsi.EncryptedRam) 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. **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. **Possible results:** - `encrypted`: detected and enabled - `not-encrypted`: detected but disabled - `not-supported`: not available To meet HSI-4 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [Intel TME Press Release](https://software.intel.com/content/www/us/en/develop/blogs/intel-releases-new-technology-specification-for-memory-encryption.html) - [WikiChip SME Overview](https://en.wikichip.org/wiki/x86/sme) ### [Supervisor Mode Access Prevention](#org.fwupd.hsi.IntelSmap) 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. **Impact:** A local or remote attacker can use a simple exploit to modify the contents of kernel memory which can lead to privilege escalation. **Possible results:** - `enabled`: features are detected and enabled - `not-supported`: not enabled To meet HSI-4 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **References:** - [SMAP Wikipedia Page](https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention) ### [Kernel DMA protection](#org.fwupd.hsi.Iommu) 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. **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. **Possible results:** - `enabled`: hardware detected and enabled - `not-found`: hardware was not detected To meet HSI-2 on systems that run this test, the result must be `enabled`. *[v1.5.0]* **Resolution:** If available, turn on IOMMU in the system BIOS. You may also have to use additional kernel boot parameters, for example `intel_iommu=on iommu=pt`. **References:** - [IOMMU Wikipedia Page](https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit) ### [Suspend-to-Idle](#org.fwupd.hsi.SuspendToIdle) The platform should be set up with Suspend-to-Idle as the default S3 sleep state. **Impact:** A local attacker could overwrite the S3 resume script to modify system RAM which can lead to privilege escalation. **Possible results:** - `enabled`: deep sleep enabled - `not-enabled`: suspend-to-idle being used - `not-valid`: could not determine the default To meet HSI-3 on systems that run this test, the result must be `not-enabled`. *[v1.5.0]* ## [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](#ommissions) ### 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.7.5/docs/index.html000066400000000000000000000031731420024370600156000ustar00rootroot00000000000000 fwupd

    libfwupd

    Functionality exported by libfwupd for client applications.

    libfwupdplugin

    Functionality available to fwupd plugins.

    fwupd-1.7.5/docs/meson.build000066400000000000000000000047761420024370600157570ustar00rootroot00000000000000if get_option('docs') == 'docgen' toml_conf = configuration_data() docgen_version = source_version if git.found() and source_version != fwupd_version docgen_version = run_command(git, 'branch', '--show-current').stdout().strip() endif toml_conf.set('version', docgen_version) 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, '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', 'fwupd'), ) custom_target('doc-fwupdplugin', input: [ fwupdplugin_toml, fwupdplugin_gir[0], ], output: 'libfwupdplugin', command: [ gidocgen, '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_source_dir()), '@INPUT1@', ], depends: [ fwupdplugin_gir[0], ], build_by_default: true, install: true, install_dir: join_paths(datadir, 'doc', 'fwupd'), ) install_data(['index.html', 'hsi.html'], install_dir : join_paths(datadir, 'doc', 'fwupd') ) install_data(['urlmap_fwupd.js'], install_dir : join_paths(datadir, 'doc', 'fwupd', 'libfwupd') ) install_data(['urlmap_fwupdplugin.js'], install_dir : join_paths(datadir, 'doc', 'fwupd', 'libfwupdplugin') ) elif get_option('docs') == 'gtkdoc' gnome.gtkdoc( 'fwupd', src_dir : [ join_paths(meson.source_root(), 'libfwupd'), join_paths(meson.source_root(), 'libfwupdplugin'), join_paths(meson.build_root(), 'libfwupd'), join_paths(meson.build_root(), 'libfwupdplugin'), join_paths(meson.current_source_dir()), ], main_xml : 'fwupd-docs.xml', install : true ) endif fwupd-1.7.5/docs/test-deps.py000077500000000000000000000005501420024370600160640ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import sys import markdown from distutils.version import LooseVersion # https://github.com/fwupd/fwupd/pull/3337#issuecomment-858947695 if LooseVersion(markdown.version) < LooseVersion("3.3.3"): print("python3-markdown version 3.3.3 required for gi-docgen") sys.exit(1) # success sys.exit(0) fwupd-1.7.5/docs/tutorial.md000066400000000000000000000777121420024370600160020ustar00rootroot00000000000000--- title: Plugin Tutorial --- ## Introduction At the heart of fwupd is a plugin loader 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. In general, building things out-of-tree isn't something that we think is a very good idea; the API and ABI *internal* to fwupd is still changing and there's a huge benefit to getting plugins upstream where they can undergo review and be ported as the API adapts. For this reason we don't install the plugin headers onto the system, although you can of course just install the `.so` binary file manually. A plugin only needs to define the vfuncs that are required, and the plugin name is taken automatically from the suffix of the `.so` file. /* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include struct FuPluginData { gpointer proxy; }; static void fu_plugin_foo_init(FuPlugin *plugin) { fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu"); fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_foo_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); destroy_proxy(data->proxy); } static gboolean fu_plugin_foo_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); data->proxy = create_proxy(); if(data->proxy == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to create proxy"); return FALSE; } return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_foo_init; vfuncs->destroy = fu_plugin_foo_destroy; vfuncs->startup = fu_plugin_foo_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_plugin_foo_coldplug(FuPlugin *plugin, 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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { … vfuncs->coldplug = fu_plugin_foo_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_plugin_foo_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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { … vfuncs->write_firmware = fu_plugin_foo_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_plugin_foo_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_plugin_foo_cleanup(FuPlugin *plugin, FuDevice *device, GError **error) { return g_file_set_contents("/var/lib/fwupd/something", fu_device_get_id(device), -1, error); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { … vfuncs->prepare = fu_plugin_foo_prepare; vfuncs->cleanup = fu_plugin_foo_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_plugin_foo_detach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { if (hardware_in_bootloader) return TRUE; return _device_detach(device, progress, error); } static gboolean fu_plugin_foo_attach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { if (!hardware_in_bootloader) return TRUE; return _device_attach(device, progress, error); } static gboolean fu_plugin_foo_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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { … vfuncs->detach = fu_plugin_foo_detach; vfuncs->attach = fu_plugin_foo_attach; vfuncs->reload = fu_plugin_foo_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 `fu_plugin_init` 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. void fu_plugin_steelseries_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD); fu_context_add_quirk_key(ctx, "SteelSeriesDeviceKind"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_steelseries_init; } ### 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()`. ### 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. ### 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` flag when running fwupd or fwupdtool. Besides that, there are many environment variables that allow some debug traces to be printed conditionally, for example: `FWUPD_PROBE_VERBOSE`, `FU_HID_DEVICE_VERBOSE`, `FWUPD_DEVICE_LIST_VERBOSE` and many other plugin-specific envvars. ### 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_common_dump_full` and `fu_common_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. fwupd-1.7.5/docs/urlmap_fwupd.js000066400000000000000000000002311420024370600166360ustar00rootroot00000000000000baseURLs = [ [ 'Gio', 'https://people.gnome.org/~ebassi/docs/_build/Gio/' ], [ 'GObject', 'https://people.gnome.org/~ebassi/docs/_build/GObject/' ], ] fwupd-1.7.5/docs/urlmap_fwupdplugin.js000066400000000000000000000002671420024370600200660ustar00rootroot00000000000000baseURLs = [ [ 'Gio', 'https://people.gnome.org/~ebassi/docs/_build/Gio/' ], [ 'GObject', 'https://people.gnome.org/~ebassi/docs/_build/GObject/' ], [ 'Fwupd', '../libfwupd/' ], ] fwupd-1.7.5/libfwupd/000077500000000000000000000000001420024370600144635ustar00rootroot00000000000000fwupd-1.7.5/libfwupd/README.md000066400000000000000000000032051420024370600157420ustar00rootroot00000000000000# 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` ## 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.7.5/libfwupd/fwupd-client-private.h000066400000000000000000000023171420024370600207100ustar00rootroot00000000000000/* * 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); #ifdef HAVE_GIO_UNIX void fwupd_client_get_details_stream_async(FwupdClient *self, GUnixInputStream *istr, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); 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); 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); #endif fwupd-1.7.5/libfwupd/fwupd-client-sync.c000066400000000000000000002076571420024370600202230ustar00rootroot00000000000000/* * 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_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, "Not supported as is unavailable"); 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_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_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, "Not supported as is unavailable"); 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, "Not supported as is unavailable"); 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_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_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_remote_async(self, remote, 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; } 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); /* 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_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); } fwupd-1.7.5/libfwupd/fwupd-client-sync.h000066400000000000000000000170011420024370600202060ustar00rootroot00000000000000/* * 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; GPtrArray * fwupd_client_get_devices(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_plugins(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_history(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_releases(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_downgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_upgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_details(FwupdClient *self, const gchar *filename, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_details_bytes(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_verify(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_verify_update(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_unlock(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_modify_config(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_activate(FwupdClient *self, GCancellable *cancellable, const gchar *device_id, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_clear_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdDevice * fwupd_client_get_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_host_security_attrs(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_host_security_events(FwupdClient *self, guint limit, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdDevice * fwupd_client_get_device_by_id(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_devices_by_guid(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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; 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; gboolean fwupd_client_install_release(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) G_DEPRECATED_FOR(fwupd_client_install_release2); 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; 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; 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; gboolean fwupd_client_refresh_remote(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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; 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; GHashTable * fwupd_client_get_report_metadata(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fwupd_client_get_remotes(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdRemote * fwupd_client_get_remote_by_id(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar ** fwupd_client_get_approved_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_set_approved_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar ** fwupd_client_get_blocked_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_set_blocked_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar * fwupd_client_self_sign(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_set_feature_flags(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fwupd_client_download_bytes(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_download_file(FwupdClient *self, const gchar *url, GFile *file, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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_END_DECLS fwupd-1.7.5/libfwupd/fwupd-client.c000066400000000000000000004705001420024370600172360ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #ifdef HAVE_LIBCURL #include #endif #ifdef HAVE_GIO_UNIX #include #endif #include #include #include #include #include "fwupd-client-private.h" #include "fwupd-client-sync.h" #include "fwupd-common-private.h" #include "fwupd-deprecated.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-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; 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_machine_id; gchar *host_security_id; GMutex proxy_mutex; /* for @proxy */ GDBusProxy *proxy; GProxyResolver *proxy_resolver; gchar *user_agent; GHashTable *hints; /* str:str */ #ifdef SOUP_SESSION_COMPAT GObject *soup_session; GModule *soup_module; /* we leak this */ #endif } 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, /* compat ABI, do not use! */ PROP_HOST_PRODUCT, PROP_HOST_MACHINE_ID, PROP_HOST_SECURITY_ID, PROP_HOST_BKC, PROP_INTERACTIVE, 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_7_62_0 G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) #endif #ifdef HAVE_LIBCURL 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(g_steal_pointer(&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_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"); } static 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"); } 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_properties_changed_cb(GDBusProxy *proxy, GVariant *changed_properties, const 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, "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, "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)); } } 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); 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); 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; } #ifdef SOUP_SESSION_COMPAT if (priv->soup_session != NULL) { g_object_set(priv->soup_session, "user-agent", priv->user_agent, NULL); } #endif 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); /* calculate percentage */ if (dltotal > 0 && dlnow >= 0 && dlnow <= dltotal) { guint percentage = (guint)((100 * dlnow) / dltotal); g_debug("download progress: %u%%", percentage); fwupd_client_set_percentage(self, percentage); } else if (ultotal > 0 && ulnow >= 0 && ulnow <= ultotal) { guint percentage = (guint)((100 * ulnow) / ultotal); g_debug("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) 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) curl_easy_setopt(helper->curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(helper->curl, CURLOPT_XFERINFOFUNCTION, fwupd_client_progress_callback_cb); curl_easy_setopt(helper->curl, CURLOPT_XFERINFODATA, self); curl_easy_setopt(helper->curl, CURLOPT_USERAGENT, priv->user_agent); curl_easy_setopt(helper->curl, CURLOPT_CONNECTTIMEOUT, 60L); curl_easy_setopt(helper->curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(helper->curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(helper->curl, CURLOPT_MAXREDIRS, 5L); /* relax the SSL checks for broken corporate proxies */ if (g_getenv("DISABLE_SSL_STRICT") != NULL) curl_easy_setopt(helper->curl, CURLOPT_SSL_VERIFYPEER, 0L); /* this disables the double-compression of the firmware.xml.gz file */ 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(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)); 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)); /* 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: 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 fwupd_client_get_devices_async() should only be called * after fwupd_client_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_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; } /* use peer-to-peer only if the env variable is set */ if (socket_filename != NULL) { g_autofree gchar *address = g_strdup_printf("unix:path=%s", socket_filename); g_dbus_connection_new_for_address(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: 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); } 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 (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)) { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_NOT_SUPPORTED; } else if (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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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, "Not supported as is unavailable"); #endif } /** * fwupd_client_install_bytes_finish: * @self: a #FwupdClient * @res: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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, "Not supported as is unavailable"); #endif } /** * fwupd_client_install_finish: * @self: a #FwupdClient * @res: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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_7_62_0 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) { return g_str_has_prefix(perhaps_url, "ipfs://") || g_str_has_prefix(perhaps_url, "ipns://"); } 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; } /* remote file */ for (guint i = 0; i < locations->len; i++) { uri_tmp = g_ptr_array_index(locations, i); if (fwupd_client_is_url_ipfs(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)); } } /* 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_IPFS) > 0 && !fwupd_client_is_url_ipfs(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: 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_DISABLE_IPFS * @cancellable: (nullable): optional #GCancellable * @callback: 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: a device * @release: a release * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @callback: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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, "Not supported as is unavailable"); #endif } /** * fwupd_client_get_details_bytes_finish: * @self: a #FwupdClient * @res: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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 daemon version number. * * 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_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_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_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: 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, "Not supported as is unavailable"); #endif } /** * fwupd_client_update_metadata_bytes_finish: * @self: a #FwupdClient * @res: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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; 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_task_return_error(task, g_steal_pointer(&error)); return; } data->metadata = g_steal_pointer(&bytes); /* 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); GChecksumType checksum_kind; g_autofree gchar *checksum = NULL; /* save signature */ bytes = fwupd_client_download_bytes_finish(FWUPD_CLIENT(source), res, &error); if (bytes == NULL) { 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? */ checksum_kind = fwupd_checksum_guess_kind(fwupd_remote_get_checksum(data->remote)); 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_debug("metadata signature of %s is unchanged, skipping", fwupd_remote_get_id(data->remote)); g_task_return_boolean(task, TRUE); return; } /* download metadata */ fwupd_client_download_bytes_async(self, fwupd_remote_get_metadata_uri(data->remote), FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, fwupd_client_refresh_remote_metadata_cb, g_steal_pointer(&task)); } /** * fwupd_client_refresh_remote_async: * @self: a #FwupdClient * @remote: a #FwupdRemote * @cancellable: (nullable): optional #GCancellable * @callback: 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) { FwupdClientRefreshRemoteData *data; g_autoptr(GTask) task = 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->remote = g_object_ref(remote); g_task_set_task_data(task, g_steal_pointer(&data), (GDestroyNotify)fwupd_client_refresh_remote_data_free); /* download signature */ fwupd_client_download_bytes_async(self, fwupd_remote_get_metadata_uri_sig(remote), FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, fwupd_client_refresh_remote_signature_cb, g_steal_pointer(&task)); } /** * fwupd_client_refresh_remote_finish: * @self: a #FwupdClient * @res: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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: 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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); GString *str = g_string_new(NULL); g_autofree gchar *system = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(package_name != NULL); g_return_if_fail(package_version != 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", priv->daemon_version); /* success */ g_free(priv->user_agent); priv->user_agent = g_string_free(str, FALSE); } #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); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwupd_client_download_write_callback_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf); res = curl_easy_perform(curl); fwupd_client_set_status(self, FWUPD_STATUS_IDLE); 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_debug("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_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download, server response was %u", (guint)status_code); return NULL; } return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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_debug("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_debug("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) { FwupdClientPrivate *priv = GET_PRIVATE(self); 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)); 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; } 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: 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) { FwupdClientPrivate *priv = GET_PRIVATE(self); 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)); g_return_if_fail(priv->proxy != NULL); /* 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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(); curl_easy_setopt(helper->curl, CURLOPT_ERRORBUFFER, errbuf); curl_easy_setopt(helper->curl, CURLOPT_WRITEFUNCTION, fwupd_client_download_write_callback_cb); 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_debug("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_byte_array_free_to_bytes(g_steal_pointer(&buf)), (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: 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); curl_easy_setopt(helper->curl, CURLOPT_MIMEPOST, helper->mime); part = curl_mime_addpart(helper->mime); curl_mime_data(part, payload, CURL_ZERO_TERMINATED); curl_mime_name(part, "payload"); if (signature != NULL) { part = curl_mime_addpart(helper->mime); 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"); curl_easy_setopt(helper->curl, CURLOPT_HTTPHEADER, helper->headers); curl_easy_setopt(helper->curl, CURLOPT_POST, 1L); curl_easy_setopt(helper->curl, CURLOPT_POSTFIELDSIZE, strlen(payload)); curl_easy_setopt(helper->curl, CURLOPT_COPYPOSTFIELDS, payload); } fwupd_client_set_status(self, FWUPD_STATUS_IDLE); g_debug("uploading to %s", url); 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: the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of fwupd_client_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); } /** * 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)); } #ifdef SOUP_SESSION_COMPAT /* this is bad; we dlopen libsoup-2.4.so.1 and get the gtype manually * to avoid deps on both libcurl and libsoup whilst preserving ABI */ static void fwupd_client_ensure_soup_session(FwupdClient *self) { FwupdClientObjectNewFunc func = NULL; FwupdClientPrivate *priv = GET_PRIVATE(self); GType soup_gtype; /* already set up */ if (priv->soup_session != NULL) return; /* known GType, just create */ soup_gtype = g_type_from_name("SoupSession"); if (soup_gtype != 0) { priv->soup_session = g_object_new(soup_gtype, NULL); return; } /* load the library at runtime, leaking the module */ if (priv->soup_module == NULL) { g_autofree gchar *fn = NULL; fn = g_build_filename(FWUPD_LIBDIR, "libsoup-2.4.so.1", NULL); priv->soup_module = g_module_open(fn, G_MODULE_BIND_LAZY); if (priv->soup_module == NULL) { g_warning("failed to find libsoup library"); return; } } if (!g_module_symbol(priv->soup_module, "soup_session_new", (gpointer *)&func)) { g_warning("failed to find soup_session_get_type()"); g_module_close(priv->soup_module); priv->soup_module = NULL; return; } priv->soup_session = func(); g_object_set(priv->soup_session, "timeout", (guint)60, NULL); } #endif 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: #ifdef SOUP_SESSION_COMPAT fwupd_client_ensure_soup_session(self); g_value_set_object(value, priv->soup_session); #else g_value_set_object(value, NULL); #endif 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_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_INTERACTIVE: g_value_set_boolean(value, priv->interactive); 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; 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_changed), 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_READABLE | 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-product: * * The host product string * * Since: 1.3.1 */ pspec = g_param_spec_string("host-product", NULL, NULL, NULL, G_PARAM_READABLE | 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_READABLE | 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_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_SECURITY_ID, 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); /* 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->daemon_version); g_free(priv->host_bkc); 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); #ifdef SOUP_SESSION_COMPAT if (priv->soup_session != NULL) g_object_unref(priv->soup_session); #endif 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.7.5/libfwupd/fwupd-client.h000066400000000000000000000371621420024370600172460ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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_IPFS: Only use IPFS 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_IPFS = 1 << 0, /* Since: 1.5.6 */ /*< private >*/ FWUPD_CLIENT_DOWNLOAD_FLAG_LAST } FwupdClientDownloadFlags; /** * 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); void fwupd_client_set_main_context(FwupdClient *self, GMainContext *main_ctx); void fwupd_client_connect_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_connect_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_devices_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_devices_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_plugins_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_plugins_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_history_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_history_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_releases_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_releases_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_downgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_downgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_upgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_upgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_details_bytes_async(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_details_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_verify_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_verify_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_verify_update_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_verify_update_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_unlock_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_unlock_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_modify_config_async(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_modify_config_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_activate_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_activate_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_clear_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_clear_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); FwupdDevice * fwupd_client_get_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_host_security_attrs_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_host_security_attrs_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_host_security_events_async(FwupdClient *self, guint limit, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_host_security_events_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_device_by_id_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); FwupdDevice * fwupd_client_get_device_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_devices_by_guid_async(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_devices_by_guid_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_install_async(FwupdClient *self, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_install_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_install_bytes_async(FwupdClient *self, const gchar *device_id, GBytes *bytes, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_install_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_install_release_async(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_DEPRECATED_FOR(fwupd_client_install_release2_async); 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); gboolean fwupd_client_install_release_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_update_metadata_bytes_async(FwupdClient *self, const gchar *remote_id, GBytes *metadata, GBytes *signature, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_update_metadata_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_refresh_remote_async(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_refresh_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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); gboolean fwupd_client_modify_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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); gboolean fwupd_client_modify_device_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_report_metadata_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GHashTable * fwupd_client_get_report_metadata_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdStatus fwupd_client_get_status(FwupdClient *self); gboolean fwupd_client_get_tainted(FwupdClient *self); gboolean fwupd_client_get_daemon_interactive(FwupdClient *self); guint fwupd_client_get_percentage(FwupdClient *self); const gchar * fwupd_client_get_daemon_version(FwupdClient *self); const gchar * fwupd_client_get_host_bkc(FwupdClient *self); const gchar * fwupd_client_get_host_product(FwupdClient *self); const gchar * fwupd_client_get_host_machine_id(FwupdClient *self); const gchar * fwupd_client_get_host_security_id(FwupdClient *self); void fwupd_client_get_remotes_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_remotes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_remote_by_id_async(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); FwupdRemote * fwupd_client_get_remote_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_approved_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_set_approved_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_set_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_get_blocked_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GPtrArray * fwupd_client_get_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_set_blocked_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_set_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_self_sign_async(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gchar * fwupd_client_self_sign_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_set_feature_flags_async(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); gboolean fwupd_client_set_feature_flags_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; const gchar * fwupd_client_get_user_agent(FwupdClient *self); void fwupd_client_set_user_agent(FwupdClient *self, const gchar *user_agent); void fwupd_client_set_user_agent_for_package(FwupdClient *self, const gchar *package_name, const gchar *package_version); void fwupd_client_download_bytes_async(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GBytes * fwupd_client_download_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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); GBytes * fwupd_client_upload_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_ensure_networking(FwupdClient *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-common-private.h000066400000000000000000000025531420024370600207240ustar00rootroot00000000000000/* * 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); GHashTable * fwupd_variant_to_hash_kv(GVariant *dict); gchar * fwupd_build_user_agent_system(void); void fwupd_input_stream_read_bytes_async(GInputStream *stream, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data); GBytes * fwupd_input_stream_read_bytes_finish(GInputStream *stream, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fwupd_common_json_add_string(JsonBuilder *builder, const gchar *key, const gchar *value); void fwupd_common_json_add_stringv(JsonBuilder *builder, const gchar *key, gchar **value); void fwupd_common_json_add_int(JsonBuilder *builder, const gchar *key, guint64 value); void fwupd_common_json_add_boolean(JsonBuilder *builder, const gchar *key, gboolean value); #ifdef HAVE_GIO_UNIX GUnixInputStream * fwupd_unix_input_stream_from_bytes(GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT; GUnixInputStream * fwupd_unix_input_stream_from_fn(const gchar *fn, GError **error) G_GNUC_WARN_UNUSED_RESULT; #endif G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-common.c000066400000000000000000001016601420024370600172460ustar00rootroot00000000000000/* * 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 #if !GLIB_CHECK_VERSION(2, 54, 0) #include #endif /** * 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; if (checksum == NULL) return 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 == 128) return G_CHECKSUM_SHA512; return G_CHECKSUM_SHA1; } static 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_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_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; } /** * 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) { const gchar *filename = NULL; const gchar *paths[] = {"/etc/os-release", "/usr/lib/os-release", NULL}; g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; g_autoptr(GHashTable) hash = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find the correct file */ for (guint i = 0; paths[i] != NULL; i++) { if (g_file_test(paths[i], G_FILE_TEST_EXISTS)) { filename = paths[i]; break; } } hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (filename == 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(filename, &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); } 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) { 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(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); GPtrArray *checksums; GPtrArray *guids; /* identify the firmware used */ json_builder_set_member_name(builder, "Checksum"); checksums = fwupd_release_get_checksums(rel); json_builder_add_string_value(builder, fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1)); /* 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)); } /* 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]); } #if !GLIB_CHECK_VERSION(2, 54, 0) static gboolean str_has_sign(const gchar *str) { return str[0] == '-' || str[0] == '+'; } static gboolean str_has_hex_prefix(const gchar *str) { return str[0] == '0' && g_ascii_tolower(str[1]) == 'x'; } static gboolean g_ascii_string_to_unsigned(const gchar *str, guint base, guint64 min, guint64 max, guint64 *out_num, GError **error) { const gchar *end_ptr = NULL; gint saved_errno = 0; guint64 number; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(base >= 2 && base <= 36, FALSE); g_return_val_if_fail(min <= max, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (str[0] == '\0') { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Empty string is not a number"); return FALSE; } errno = 0; number = g_ascii_strtoull(str, (gchar **)&end_ptr, base); saved_errno = errno; if (g_ascii_isspace(str[0]) || str_has_sign(str) || (base == 16 && str_has_hex_prefix(str)) || (saved_errno != 0 && saved_errno != ERANGE) || end_ptr == NULL || *end_ptr != '\0') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "“%s” is not an unsigned number", str); return FALSE; } if (saved_errno == ERANGE || number < min || number > max) { g_autofree gchar *min_str = g_strdup_printf("%" G_GUINT64_FORMAT, min); g_autofree gchar *max_str = g_strdup_printf("%" G_GUINT64_FORMAT, max); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Number “%s” is out of bounds [%s, %s]", str, min_str, max_str); return FALSE; } if (out_num != NULL) *out_num = number; return TRUE; } #endif /* GLIB_CHECK_VERSION(2,54,0) */ /** * 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: 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; } static void fwupd_input_stream_read_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { GByteArray *bufarr; GInputStream *stream = G_INPUT_STREAM(source); g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); #if GLIB_CHECK_VERSION(2, 64, 0) guint8 *buf; gsize bufsz = 0; #endif /* read buf */ bytes = g_input_stream_read_bytes_finish(stream, res, &error); if (bytes == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* add bytes to buffer */ bufarr = g_task_get_task_data(task); if (g_bytes_get_size(bytes) > 0) { GCancellable *cancellable = g_task_get_cancellable(task); g_debug("add %u", (guint)g_bytes_get_size(bytes)); g_byte_array_append(bufarr, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); g_input_stream_read_bytes_async(g_steal_pointer(&stream), 256 * 1024, /* bigger chunk */ G_PRIORITY_DEFAULT, cancellable, fwupd_input_stream_read_bytes_cb, g_steal_pointer(&task)); return; } /* success */ #if GLIB_CHECK_VERSION(2, 64, 0) buf = g_byte_array_steal(bufarr, &bufsz); g_task_return_pointer(task, g_bytes_new_take(buf, bufsz), (GDestroyNotify)g_bytes_unref); #else g_task_return_pointer(task, g_bytes_new(bufarr->data, bufarr->len), (GDestroyNotify)g_bytes_unref); #endif } /** * fwupd_input_stream_read_bytes_async: (skip): **/ void fwupd_input_stream_read_bytes_async(GInputStream *stream, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { g_autoptr(GTask) task = NULL; g_return_if_fail(G_IS_INPUT_STREAM(stream)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); task = g_task_new(stream, cancellable, callback, callback_data); g_task_set_task_data(task, g_byte_array_new(), (GDestroyNotify)g_byte_array_unref); g_input_stream_read_bytes_async(stream, 64 * 1024, /* small */ G_PRIORITY_DEFAULT, cancellable, fwupd_input_stream_read_bytes_cb, g_steal_pointer(&task)); } /** * fwupd_input_stream_read_bytes_finish: (skip): **/ GBytes * fwupd_input_stream_read_bytes_finish(GInputStream *stream, GAsyncResult *res, GError **error) { g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); g_return_val_if_fail(g_task_is_valid(res, stream), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } #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-1.7.5/libfwupd/fwupd-common.h000066400000000000000000000047731420024370600172620ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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_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 */ #ifndef __GI_SCANNER__ typedef guint8 fwupd_guid_t[16]; #endif const gchar * fwupd_checksum_get_best(GPtrArray *checksums); const gchar * fwupd_checksum_get_by_kind(GPtrArray *checksums, GChecksumType kind); GChecksumType fwupd_checksum_guess_kind(const gchar *checksum); gchar * fwupd_checksum_format_for_display(const gchar *checksum); gchar * fwupd_build_user_agent(const gchar *package_name, const gchar *package_version) G_DEPRECATED_FOR(fwupd_client_set_user_agent_for_package); gchar * fwupd_build_machine_id(const gchar *salt, GError **error); GHashTable * fwupd_get_os_release(GError **error); gchar * fwupd_build_history_report_json(GPtrArray *devices, GError **error); 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); gboolean fwupd_guid_from_string(const gchar *guidstr, fwupd_guid_t *guid, FwupdGuidFlags flags, GError **error); #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_END_DECLS fwupd-1.7.5/libfwupd/fwupd-context-test.c000066400000000000000000000063341420024370600204210ustar00rootroot00000000000000/* * 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_FLAGS_NONE); 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.7.5/libfwupd/fwupd-deprecated.h000066400000000000000000000002541420024370600200600ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS /* indeed, nothing */ G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-device-private.h000066400000000000000000000010011420024370600206560ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-device.h" G_BEGIN_DECLS GVariant * fwupd_device_to_variant(FwupdDevice *self); GVariant * fwupd_device_to_variant_full(FwupdDevice *self, FwupdDeviceFlags flags); void fwupd_device_incorporate(FwupdDevice *self, FwupdDevice *donor); void fwupd_device_to_json(FwupdDevice *self, JsonBuilder *builder); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-device.c000066400000000000000000002541031420024370600172160ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; GPtrArray *guids; GPtrArray *vendor_ids; GPtrArray *protocols; GPtrArray *instance_ids; GPtrArray *icons; 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 install_duration; FwupdUpdateState update_state; gchar *update_error; gchar *update_message; gchar *update_image; FwupdStatus status; GPtrArray *releases; FwupdDevice *parent; /* noref */ } FwupdDevicePrivate; enum { PROP_0, PROP_VERSION_FORMAT, PROP_FLAGS, PROP_PROTOCOL, PROP_STATUS, PROP_PARENT, PROP_UPDATE_STATE, PROP_UPDATE_MESSAGE, PROP_UPDATE_ERROR, PROP_UPDATE_IMAGE, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdDevice, fwupd_device, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_device_get_instance_private(o)) /** * 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_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); 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_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)); 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: 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); /* 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); } /** * 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_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_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); 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->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->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->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_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->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->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_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_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_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_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_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); } static 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); } 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_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_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 update state. * * Returns: the update state, 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 state, e.g. %FWUPD_VERSION_FORMAT_PENDING * * Sets the update state. * * 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"); } 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: * @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) { 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 (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); } 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, "VendorIds"); 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->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->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->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); 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); } } 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); 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)); } /* 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); } fwupd_pad_kv_dfl(str, FWUPD_RESULT_KEY_FLAGS, priv->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->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(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_FORMAT: g_value_set_uint(value, priv->version_format); break; case PROP_FLAGS: g_value_set_uint64(value, priv->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_PARENT: g_value_set_object(value, priv->parent); break; case PROP_UPDATE_STATE: g_value_set_uint(value, priv->update_state); 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_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_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_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; 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-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: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: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); } 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->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); } 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_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; /* 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: (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; /* 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: a device * @self2: 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_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.7.5/libfwupd/fwupd-device.h000066400000000000000000000162751420024370600172310ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" #include "fwupd-release.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); const gchar * fwupd_device_get_id(FwupdDevice *self); void fwupd_device_set_id(FwupdDevice *self, const gchar *id); const gchar * fwupd_device_get_parent_id(FwupdDevice *self); void fwupd_device_set_parent_id(FwupdDevice *self, const gchar *parent_id); const gchar * fwupd_device_get_composite_id(FwupdDevice *self); void fwupd_device_set_composite_id(FwupdDevice *self, const gchar *composite_id); FwupdDevice * fwupd_device_get_root(FwupdDevice *self); FwupdDevice * fwupd_device_get_parent(FwupdDevice *self); void fwupd_device_set_parent(FwupdDevice *self, FwupdDevice *parent); void fwupd_device_add_child(FwupdDevice *self, FwupdDevice *child); void fwupd_device_remove_child(FwupdDevice *self, FwupdDevice *child); GPtrArray * fwupd_device_get_children(FwupdDevice *self); const gchar * fwupd_device_get_name(FwupdDevice *self); void fwupd_device_set_name(FwupdDevice *self, const gchar *name); const gchar * fwupd_device_get_serial(FwupdDevice *self); void fwupd_device_set_serial(FwupdDevice *self, const gchar *serial); const gchar * fwupd_device_get_summary(FwupdDevice *self); void fwupd_device_set_summary(FwupdDevice *self, const gchar *summary); const gchar * fwupd_device_get_branch(FwupdDevice *self); void fwupd_device_set_branch(FwupdDevice *self, const gchar *branch); const gchar * fwupd_device_get_description(FwupdDevice *self); void fwupd_device_set_description(FwupdDevice *self, const gchar *description); const gchar * fwupd_device_get_version(FwupdDevice *self); void fwupd_device_set_version(FwupdDevice *self, const gchar *version); const gchar * fwupd_device_get_version_lowest(FwupdDevice *self); void fwupd_device_set_version_lowest(FwupdDevice *self, const gchar *version_lowest); guint64 fwupd_device_get_version_lowest_raw(FwupdDevice *self); void fwupd_device_set_version_lowest_raw(FwupdDevice *self, guint64 version_lowest_raw); const gchar * fwupd_device_get_version_bootloader(FwupdDevice *self); void fwupd_device_set_version_bootloader(FwupdDevice *self, const gchar *version_bootloader); guint64 fwupd_device_get_version_bootloader_raw(FwupdDevice *self); void fwupd_device_set_version_bootloader_raw(FwupdDevice *self, guint64 version_bootloader_raw); guint64 fwupd_device_get_version_raw(FwupdDevice *self); void fwupd_device_set_version_raw(FwupdDevice *self, guint64 version_raw); guint64 fwupd_device_get_version_build_date(FwupdDevice *self); void fwupd_device_set_version_build_date(FwupdDevice *self, guint64 version_build_date); FwupdVersionFormat fwupd_device_get_version_format(FwupdDevice *self); void fwupd_device_set_version_format(FwupdDevice *self, FwupdVersionFormat version_format); guint32 fwupd_device_get_flashes_left(FwupdDevice *self); void fwupd_device_set_flashes_left(FwupdDevice *self, guint32 flashes_left); guint32 fwupd_device_get_install_duration(FwupdDevice *self); void fwupd_device_set_install_duration(FwupdDevice *self, guint32 duration); guint64 fwupd_device_get_flags(FwupdDevice *self); void fwupd_device_set_flags(FwupdDevice *self, guint64 flags); void fwupd_device_add_flag(FwupdDevice *self, FwupdDeviceFlags flag); void fwupd_device_remove_flag(FwupdDevice *self, FwupdDeviceFlags flag); gboolean fwupd_device_has_flag(FwupdDevice *self, FwupdDeviceFlags flag); guint64 fwupd_device_get_created(FwupdDevice *self); void fwupd_device_set_created(FwupdDevice *self, guint64 created); guint64 fwupd_device_get_modified(FwupdDevice *self); void fwupd_device_set_modified(FwupdDevice *self, guint64 modified); GPtrArray * fwupd_device_get_checksums(FwupdDevice *self); void fwupd_device_add_checksum(FwupdDevice *self, const gchar *checksum); const gchar * fwupd_device_get_plugin(FwupdDevice *self); void fwupd_device_set_plugin(FwupdDevice *self, const gchar *plugin); G_DEPRECATED_FOR(fwupd_device_get_protocols) const gchar * fwupd_device_get_protocol(FwupdDevice *self); G_DEPRECATED_FOR(fwupd_device_add_protocol) void fwupd_device_set_protocol(FwupdDevice *self, const gchar *protocol); void fwupd_device_add_protocol(FwupdDevice *self, const gchar *protocol); gboolean fwupd_device_has_protocol(FwupdDevice *self, const gchar *protocol); GPtrArray * fwupd_device_get_protocols(FwupdDevice *self); const gchar * fwupd_device_get_vendor(FwupdDevice *self); void fwupd_device_set_vendor(FwupdDevice *self, const gchar *vendor); G_DEPRECATED_FOR(fwupd_device_get_vendor_ids) const gchar * fwupd_device_get_vendor_id(FwupdDevice *self); G_DEPRECATED_FOR(fwupd_device_add_vendor_id) void fwupd_device_set_vendor_id(FwupdDevice *self, const gchar *vendor_id); void fwupd_device_add_vendor_id(FwupdDevice *self, const gchar *vendor_id); gboolean fwupd_device_has_vendor_id(FwupdDevice *self, const gchar *vendor_id); GPtrArray * fwupd_device_get_vendor_ids(FwupdDevice *self); void fwupd_device_add_guid(FwupdDevice *self, const gchar *guid); gboolean fwupd_device_has_guid(FwupdDevice *self, const gchar *guid); GPtrArray * fwupd_device_get_guids(FwupdDevice *self); const gchar * fwupd_device_get_guid_default(FwupdDevice *self); void fwupd_device_add_instance_id(FwupdDevice *self, const gchar *instance_id); gboolean fwupd_device_has_instance_id(FwupdDevice *self, const gchar *instance_id); GPtrArray * fwupd_device_get_instance_ids(FwupdDevice *self); void fwupd_device_add_icon(FwupdDevice *self, const gchar *icon); gboolean fwupd_device_has_icon(FwupdDevice *self, const gchar *icon); GPtrArray * fwupd_device_get_icons(FwupdDevice *self); FwupdUpdateState fwupd_device_get_update_state(FwupdDevice *self); void fwupd_device_set_update_state(FwupdDevice *self, FwupdUpdateState update_state); const gchar * fwupd_device_get_update_error(FwupdDevice *self); void fwupd_device_set_update_error(FwupdDevice *self, const gchar *update_error); const gchar * fwupd_device_get_update_message(FwupdDevice *self); void fwupd_device_set_update_message(FwupdDevice *self, const gchar *update_message); const gchar * fwupd_device_get_update_image(FwupdDevice *self); void fwupd_device_set_update_image(FwupdDevice *self, const gchar *update_image); FwupdStatus fwupd_device_get_status(FwupdDevice *self); void fwupd_device_set_status(FwupdDevice *self, FwupdStatus status); void fwupd_device_add_release(FwupdDevice *self, FwupdRelease *release); GPtrArray * fwupd_device_get_releases(FwupdDevice *self); FwupdRelease * fwupd_device_get_release_default(FwupdDevice *self); gint fwupd_device_compare(FwupdDevice *self1, FwupdDevice *self2); FwupdDevice * fwupd_device_from_variant(GVariant *value); GPtrArray * fwupd_device_array_from_variant(GVariant *value); void fwupd_device_array_ensure_parents(GPtrArray *devices); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-enums-private.h000066400000000000000000000271051420024370600205630ustar00rootroot00000000000000/* * 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_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_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_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_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" G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-enums.c000066400000000000000000000635321420024370600171120ustar00rootroot00000000000000/* * 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 a 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"; 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; 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_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_UNKNOWN) return "unknown"; return NULL; } /** * fwupd_device_flag_from_string: * @device_flag: (nullable): a string, e.g. `require-ac` * * Converts a string to a 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, "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; return FWUPD_DEVICE_FLAG_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_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"; 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, "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; return FWUPD_DEVICE_FLAG_UNKNOWN; } /** * fwupd_update_state_to_string: * @update_state: the update state, e.g. %FWUPD_UPDATE_STATE_PENDING * * Converts a 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 a 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 a 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 a 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"; return NULL; } /** * fwupd_feature_flag_from_string: * @feature_flag: (nullable): a string, e.g. `detach-action` * * Converts a string to a 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; 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 a 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"; 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; 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.7.5/libfwupd/fwupd-enums.h000066400000000000000000000650771420024370600171250ustar00rootroot00000000000000/* * 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 */ /*< 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 * * 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 */ /*< private >*/ FWUPD_FEATURE_FLAG_LAST } FwupdFeatureFlags; /** * FWUPD_DEVICE_FLAG_NONE: * * No flags set * * Since 0.1.3 */ #define FWUPD_DEVICE_FLAG_NONE (0u) /** * FWUPD_DEVICE_FLAG_INTERNAL: * * Device is internal to the platform and cannot be removed easily. * * Since 0.1.3 */ #define FWUPD_DEVICE_FLAG_INTERNAL (1u << 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 (1u << 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 (1u << 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 (1u << 3) /** * FWUPD_DEVICE_FLAG_LOCKED: * * The device can not be updated without manual user interaction. * * Since 0.6.3 */ #define FWUPD_DEVICE_FLAG_LOCKED (1u << 4) /** * FWUPD_DEVICE_FLAG_SUPPORTED: * * The device is found in metadata loaded into the daemon. * * Since 0.7.1 */ #define FWUPD_DEVICE_FLAG_SUPPORTED (1u << 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 (1u << 6) /** * FWUPD_DEVICE_FLAG_REGISTERED: * * The device has been registered with other plugins. * * Since 0.9.7 */ #define FWUPD_DEVICE_FLAG_REGISTERED (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 20) /** * FWUPD_DEVICE_FLAG_ENSURE_SEMVER: * * Deprecated, no not use * * Since: 1.2.9 * Deprecate: 1.5.5 */ #define FWUPD_DEVICE_FLAG_ENSURE_SEMVER (1u << 21) /** * FWUPD_DEVICE_FLAG_HISTORICAL: * * The device is used for historical data only. * * Since: 1.3.2 */ #define FWUPD_DEVICE_FLAG_HISTORICAL (1u << 22) /** * FWUPD_DEVICE_FLAG_ONLY_SUPPORTED: * * Deprecated, no not use * * Since: 1.3.3 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_ONLY_SUPPORTED (1u << 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 (1u << 24) /** * FWUPD_DEVICE_FLAG_CAN_VERIFY: * * THe device checksums can be compared against metadata. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_CAN_VERIFY (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 36) /** * FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN: * * The device is updatable but is currently inhbitied 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 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 (1llu << 46) /** * 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_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 (1u << 0) /** * FWUPD_RELEASE_FLAG_TRUSTED_METADATA: * * The payload metadata is trusted. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_TRUSTED_METADATA (1u << 1) /** * FWUPD_RELEASE_FLAG_IS_UPGRADE: * * The release is newer than the device version. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_UPGRADE (1u << 2) /** * FWUPD_RELEASE_FLAG_IS_DOWNGRADE: * * The release is older than the device version. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_DOWNGRADE (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 7) /** * 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 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 (1u << 12) /** * 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_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 * * The update state. **/ 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; 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_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.7.5/libfwupd/fwupd-error.c000066400000000000000000000107561420024370600171140ustar00rootroot00000000000000/* * 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: * * The error quark for [error@FwupdError]. * * 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.7.5/libfwupd/fwupd-error.h000066400000000000000000000046651420024370600171230ustar00rootroot00000000000000/* * 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 be 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.7.5/libfwupd/fwupd-plugin-private.h000066400000000000000000000005161420024370600207270ustar00rootroot00000000000000/* * 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); void fwupd_plugin_to_json(FwupdPlugin *self, JsonBuilder *builder); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-plugin.c000066400000000000000000000260571420024370600172620ustar00rootroot00000000000000/* * 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_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); } 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; /* 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.7.5/libfwupd/fwupd-plugin.h000066400000000000000000000024261420024370600172610ustar00rootroot00000000000000/* * 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); const gchar * fwupd_plugin_get_name(FwupdPlugin *self); void fwupd_plugin_set_name(FwupdPlugin *self, const gchar *name); guint64 fwupd_plugin_get_flags(FwupdPlugin *self); void fwupd_plugin_set_flags(FwupdPlugin *self, guint64 flags); void fwupd_plugin_add_flag(FwupdPlugin *self, FwupdPluginFlags flag); void fwupd_plugin_remove_flag(FwupdPlugin *self, FwupdPluginFlags flag); gboolean fwupd_plugin_has_flag(FwupdPlugin *self, FwupdPluginFlags flag); FwupdPlugin * fwupd_plugin_from_variant(GVariant *value); GPtrArray * fwupd_plugin_array_from_variant(GVariant *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-release-private.h000066400000000000000000000005541420024370600210530ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-release.h" G_BEGIN_DECLS GVariant * fwupd_release_to_variant(FwupdRelease *self); void fwupd_release_to_json(FwupdRelease *self, JsonBuilder *builder); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-release.c000066400000000000000000001664511420024370600174070ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-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; } FwupdReleasePrivate; 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); } /** * 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: 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: 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 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 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 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 description * * 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 description * * 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_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->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)); } 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; } } static 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); } static 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); } 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); } static 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_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); } } /** * 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_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); } return g_string_free(str, FALSE); } static void fwupd_release_class_init(FwupdReleaseClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_release_finalize; } 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->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_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; /* 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_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.7.5/libfwupd/fwupd-release.h000066400000000000000000000141621420024370600174030ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.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); const gchar * fwupd_release_get_version(FwupdRelease *self); void fwupd_release_set_version(FwupdRelease *self, const gchar *version); G_DEPRECATED_FOR(fwupd_release_get_locations) const gchar * fwupd_release_get_uri(FwupdRelease *self); G_DEPRECATED_FOR(fwupd_release_add_location) void fwupd_release_set_uri(FwupdRelease *self, const gchar *uri); GPtrArray * fwupd_release_get_locations(FwupdRelease *self); void fwupd_release_add_location(FwupdRelease *self, const gchar *location); GPtrArray * fwupd_release_get_issues(FwupdRelease *self); void fwupd_release_add_issue(FwupdRelease *self, const gchar *issue); GPtrArray * fwupd_release_get_categories(FwupdRelease *self); void fwupd_release_add_category(FwupdRelease *self, const gchar *category); gboolean fwupd_release_has_category(FwupdRelease *self, const gchar *category); GPtrArray * fwupd_release_get_checksums(FwupdRelease *self); void fwupd_release_add_checksum(FwupdRelease *self, const gchar *checksum); gboolean fwupd_release_has_checksum(FwupdRelease *self, const gchar *checksum); GPtrArray * fwupd_release_get_tags(FwupdRelease *self); void fwupd_release_add_tag(FwupdRelease *self, const gchar *tag); gboolean fwupd_release_has_tag(FwupdRelease *self, const gchar *tag); GHashTable * fwupd_release_get_metadata(FwupdRelease *self); void fwupd_release_add_metadata(FwupdRelease *self, GHashTable *hash); void fwupd_release_add_metadata_item(FwupdRelease *self, const gchar *key, const gchar *value); const gchar * fwupd_release_get_metadata_item(FwupdRelease *self, const gchar *key); const gchar * fwupd_release_get_filename(FwupdRelease *self); void fwupd_release_set_filename(FwupdRelease *self, const gchar *filename); const gchar * fwupd_release_get_protocol(FwupdRelease *self); void fwupd_release_set_protocol(FwupdRelease *self, const gchar *protocol); const gchar * fwupd_release_get_id(FwupdRelease *self); void fwupd_release_set_id(FwupdRelease *self, const gchar *id); const gchar * fwupd_release_get_appstream_id(FwupdRelease *self); void fwupd_release_set_appstream_id(FwupdRelease *self, const gchar *appstream_id); const gchar * fwupd_release_get_detach_caption(FwupdRelease *self); void fwupd_release_set_detach_caption(FwupdRelease *self, const gchar *detach_caption); const gchar * fwupd_release_get_detach_image(FwupdRelease *self); void fwupd_release_set_detach_image(FwupdRelease *self, const gchar *detach_image); const gchar * fwupd_release_get_remote_id(FwupdRelease *self); void fwupd_release_set_remote_id(FwupdRelease *self, const gchar *remote_id); const gchar * fwupd_release_get_vendor(FwupdRelease *self); void fwupd_release_set_vendor(FwupdRelease *self, const gchar *vendor); const gchar * fwupd_release_get_name(FwupdRelease *self); void fwupd_release_set_name(FwupdRelease *self, const gchar *name); const gchar * fwupd_release_get_name_variant_suffix(FwupdRelease *self); void fwupd_release_set_name_variant_suffix(FwupdRelease *self, const gchar *name_variant_suffix); const gchar * fwupd_release_get_summary(FwupdRelease *self); void fwupd_release_set_summary(FwupdRelease *self, const gchar *summary); const gchar * fwupd_release_get_branch(FwupdRelease *self); void fwupd_release_set_branch(FwupdRelease *self, const gchar *branch); const gchar * fwupd_release_get_description(FwupdRelease *self); void fwupd_release_set_description(FwupdRelease *self, const gchar *description); const gchar * fwupd_release_get_homepage(FwupdRelease *self); void fwupd_release_set_homepage(FwupdRelease *self, const gchar *homepage); const gchar * fwupd_release_get_details_url(FwupdRelease *self); void fwupd_release_set_details_url(FwupdRelease *self, const gchar *details_url); const gchar * fwupd_release_get_source_url(FwupdRelease *self); void fwupd_release_set_source_url(FwupdRelease *self, const gchar *source_url); guint64 fwupd_release_get_size(FwupdRelease *self); void fwupd_release_set_size(FwupdRelease *self, guint64 size); guint64 fwupd_release_get_created(FwupdRelease *self); void fwupd_release_set_created(FwupdRelease *self, guint64 created); const gchar * fwupd_release_get_license(FwupdRelease *self); void fwupd_release_set_license(FwupdRelease *self, const gchar *license); FwupdTrustFlags fwupd_release_get_trust_flags(FwupdRelease *self) G_DEPRECATED_FOR(fwupd_release_get_flags); void fwupd_release_set_trust_flags(FwupdRelease *self, FwupdTrustFlags trust_flags) G_DEPRECATED_FOR(fwupd_release_set_flags); FwupdReleaseFlags fwupd_release_get_flags(FwupdRelease *self); void fwupd_release_set_flags(FwupdRelease *self, FwupdReleaseFlags flags); void fwupd_release_add_flag(FwupdRelease *self, FwupdReleaseFlags flag); void fwupd_release_remove_flag(FwupdRelease *self, FwupdReleaseFlags flag); gboolean fwupd_release_has_flag(FwupdRelease *self, FwupdReleaseFlags flag); FwupdReleaseUrgency fwupd_release_get_urgency(FwupdRelease *self); void fwupd_release_set_urgency(FwupdRelease *self, FwupdReleaseUrgency urgency); guint32 fwupd_release_get_install_duration(FwupdRelease *self); void fwupd_release_set_install_duration(FwupdRelease *self, guint32 duration); const gchar * fwupd_release_get_update_message(FwupdRelease *self); void fwupd_release_set_update_message(FwupdRelease *self, const gchar *update_message); const gchar * fwupd_release_get_update_image(FwupdRelease *self); void fwupd_release_set_update_image(FwupdRelease *self, const gchar *update_image); FwupdRelease * fwupd_release_from_variant(GVariant *value); GPtrArray * fwupd_release_array_from_variant(GVariant *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-remote-private.h000066400000000000000000000021301420024370600207160ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-remote.h" G_BEGIN_DECLS GVariant * fwupd_remote_to_variant(FwupdRemote *self); gboolean fwupd_remote_load_from_filename(FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error); void fwupd_remote_set_priority(FwupdRemote *self, gint priority); void fwupd_remote_set_agreement(FwupdRemote *self, const gchar *agreement); void fwupd_remote_set_mtime(FwupdRemote *self, guint64 mtime); gchar ** fwupd_remote_get_order_after(FwupdRemote *self); gchar ** fwupd_remote_get_order_before(FwupdRemote *self); void fwupd_remote_set_remotes_dir(FwupdRemote *self, const gchar *directory); void fwupd_remote_set_filename_source(FwupdRemote *self, const gchar *filename_source); void fwupd_remote_set_keyring_kind(FwupdRemote *self, FwupdKeyringKind keyring_kind); gboolean fwupd_remote_setup(FwupdRemote *self, GError **error); void fwupd_remote_to_json(FwupdRemote *self, JsonBuilder *builder); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-remote.c000066400000000000000000001376531420024370600172640ustar00rootroot00000000000000/* * 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-deprecated.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] */ static void fwupd_remote_finalize(GObject *obj); typedef struct { FwupdRemoteKind kind; 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; gchar *filename_cache; gchar *filename_cache_sig; gchar *filename_source; gboolean enabled; gboolean approval_required; gint priority; guint64 mtime; gchar **order_after; gchar **order_before; gchar *remotes_dir; gboolean automatic_reports; gboolean automatic_security_reports; } FwupdRemotePrivate; enum { PROP_0, PROP_ID, PROP_ENABLED, PROP_APPROVAL_REQUIRED, PROP_AUTOMATIC_REPORTS, PROP_AUTOMATIC_SECURITY_REPORTS, 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_7_62_0 typedef gchar curlptr; G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) #endif /** * 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, "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_boolean(builder, "Enabled", priv->enabled); fwupd_common_json_add_boolean(builder, "ApprovalRequired", priv->approval_required); fwupd_common_json_add_boolean(builder, "AutomaticReports", priv->automatic_reports); fwupd_common_json_add_boolean(builder, "AutomaticSecurityReports", priv->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_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); } 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); } static void fwupd_remote_set_title(FwupdRemote *self, const gchar *title) { FwupdRemotePrivate *priv = GET_PRIVATE(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); /* not changed */ if (g_strcmp0(priv->agreement, agreement) == 0) return; g_free(priv->agreement); priv->agreement = g_strdup(agreement); } static void fwupd_remote_set_checksum(FwupdRemote *self, const gchar *checksum) { FwupdRemotePrivate *priv = GET_PRIVATE(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); priv->keyring_kind = keyring_kind; } /* note, this has to be set before url */ static 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); 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 *url, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); #ifdef HAVE_LIBCURL_7_62_0 g_autoptr(curlptr) tmp_uri = NULL; g_autoptr(CURLU) uri = curl_url(); /* create URI, substituting if required */ if (priv->firmware_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; } 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); 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; } curl_url_get(uri, CURLUPART_PATH, &path, 0); basename = g_path_get_dirname(path); path_new = g_build_filename(basename, url, NULL); 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 username and password */ if (priv->username != NULL) curl_url_set(uri, CURLUPART_USER, priv->username, 0); if (priv->password != NULL) curl_url_set(uri, CURLUPART_PASSWORD, priv->password, 0); 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); return g_build_filename(priv->firmware_base_uri, basename, NULL); } if (g_strstr_len(url, -1, "/") == NULL) { g_autofree gchar *basename = g_path_get_dirname(priv->metadata_uri); return g_build_filename(basename, url, NULL); } return g_strdup(url); #endif } /* note, this has to be set before username and password */ static void fwupd_remote_set_metadata_uri(FwupdRemote *self, const gchar *metadata_uri) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *suffix; /* save this so we can export the object as a GVariant */ 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) 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; } static 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... */ 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 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 = 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 checksum: "); return FALSE; } g_checksum_update(checksum, (guchar *)buf, (gssize)sz); fwupd_remote_set_checksum(self, g_checksum_get_string(checksum)); } 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; fwupd_remote_set_metadata_uri(self, tmp); } } /* all keys are optional */ if (g_key_file_has_key(kf, group, "Enabled", NULL)) priv->enabled = g_key_file_get_boolean(kf, group, "Enabled", NULL); if (g_key_file_has_key(kf, group, "ApprovalRequired", NULL)) priv->approval_required = g_key_file_get_boolean(kf, group, "ApprovalRequired", NULL); 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, "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)) priv->automatic_reports = g_key_file_get_boolean(kf, group, "AutomaticReports", NULL); if (g_key_file_has_key(kf, group, "AutomaticSecurityReports", NULL)) priv->automatic_security_reports = g_key_file_get_boolean(kf, group, "AutomaticSecurityReports", NULL); /* success */ fwupd_remote_set_filename_source(self, filename); return TRUE; } /** * 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_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 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; } /** * 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) { 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, url, 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(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_debug("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); } /* 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) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return priv->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) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return priv->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) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return priv->automatic_security_reports; } /** * 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) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return priv->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, "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) { priv->enabled = g_variant_get_boolean(value); } else if (g_strcmp0(key, "ApprovalRequired") == 0) { priv->approval_required = g_variant_get_boolean(value); } 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, "FirmwareBaseUri") == 0) { fwupd_remote_set_firmware_base_uri(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "AutomaticReports") == 0) { priv->automatic_reports = g_variant_get_boolean(value); } else if (g_strcmp0(key, "AutomaticSecurityReports") == 0) { priv->automatic_security_reports = g_variant_get_boolean(value); } } } /** * 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->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 != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string(priv->checksum)); } 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->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)); } g_variant_builder_add(&builder, "{sv}", "Enabled", g_variant_new_boolean(priv->enabled)); g_variant_builder_add(&builder, "{sv}", "ApprovalRequired", g_variant_new_boolean(priv->approval_required)); g_variant_builder_add(&builder, "{sv}", "AutomaticReports", g_variant_new_boolean(priv->automatic_reports)); g_variant_builder_add(&builder, "{sv}", "AutomaticSecurityReports", g_variant_new_boolean(priv->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, priv->enabled); break; case PROP_APPROVAL_REQUIRED: g_value_set_boolean(value, priv->approval_required); break; case PROP_ID: g_value_set_string(value, priv->id); break; case PROP_AUTOMATIC_REPORTS: g_value_set_boolean(value, priv->automatic_reports); break; case PROP_AUTOMATIC_SECURITY_REPORTS: g_value_set_boolean(value, priv->automatic_security_reports); 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); FwupdRemotePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_ENABLED: priv->enabled = g_value_get_boolean(value); break; case PROP_APPROVAL_REQUIRED: priv->approval_required = g_value_get_boolean(value); break; case PROP_ID: fwupd_remote_set_id(self, g_value_get_string(value)); break; case PROP_AUTOMATIC_REPORTS: priv->automatic_reports = g_value_get_boolean(value); break; case PROP_AUTOMATIC_SECURITY_REPORTS: priv->automatic_security_reports = g_value_get_boolean(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); } 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->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; 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.7.5/libfwupd/fwupd-remote.h000066400000000000000000000062551420024370600172620ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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; FwupdRemoteKind fwupd_remote_kind_from_string(const gchar *kind); const gchar * fwupd_remote_kind_to_string(FwupdRemoteKind kind); FwupdRemote * fwupd_remote_new(void); const gchar * fwupd_remote_get_id(FwupdRemote *self); const gchar * fwupd_remote_get_title(FwupdRemote *self); const gchar * fwupd_remote_get_agreement(FwupdRemote *self); const gchar * fwupd_remote_get_remotes_dir(FwupdRemote *self); const gchar * fwupd_remote_get_checksum(FwupdRemote *self); const gchar * fwupd_remote_get_username(FwupdRemote *self); const gchar * fwupd_remote_get_password(FwupdRemote *self); const gchar * fwupd_remote_get_filename_cache(FwupdRemote *self); const gchar * fwupd_remote_get_filename_cache_sig(FwupdRemote *self); const gchar * fwupd_remote_get_filename_source(FwupdRemote *self); const gchar * fwupd_remote_get_firmware_base_uri(FwupdRemote *self); const gchar * fwupd_remote_get_report_uri(FwupdRemote *self); const gchar * fwupd_remote_get_security_report_uri(FwupdRemote *self); const gchar * fwupd_remote_get_metadata_uri(FwupdRemote *self); const gchar * fwupd_remote_get_metadata_uri_sig(FwupdRemote *self); gboolean fwupd_remote_get_enabled(FwupdRemote *self); gboolean fwupd_remote_get_approval_required(FwupdRemote *self); gboolean fwupd_remote_get_automatic_reports(FwupdRemote *self); gboolean fwupd_remote_get_automatic_security_reports(FwupdRemote *self); gint fwupd_remote_get_priority(FwupdRemote *self); guint64 fwupd_remote_get_age(FwupdRemote *self); FwupdRemoteKind fwupd_remote_get_kind(FwupdRemote *self); FwupdKeyringKind fwupd_remote_get_keyring_kind(FwupdRemote *self); gchar * fwupd_remote_build_firmware_uri(FwupdRemote *self, const gchar *url, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_remote_load_signature(FwupdRemote *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_remote_load_signature_bytes(FwupdRemote *self, GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdRemote * fwupd_remote_from_variant(GVariant *value); GPtrArray * fwupd_remote_array_from_variant(GVariant *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-request-private.h000066400000000000000000000003531420024370600211200ustar00rootroot00000000000000/* * 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_END_DECLS fwupd-1.7.5/libfwupd/fwupd-request.c000066400000000000000000000354731420024370600174560ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.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; guint64 created; gchar *device_id; gchar *message; gchar *image; } FwupdRequestPrivate; enum { PROP_0, PROP_ID, PROP_KIND, PROP_MESSAGE, PROP_IMAGE, 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 a 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_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_PLUGIN, 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)); } 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; } } static 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); } static 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_request_get_message: * @self: a #FwupdRequest * * Gets the update message. * * 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); return priv->message; } /** * 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_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_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_KIND: g_value_set_uint(value, priv->kind); 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_KIND: fwupd_request_set_kind(self, g_value_get_uint(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: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); } 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.7.5/libfwupd/fwupd-request.h000066400000000000000000000047541420024370600174610ustar00rootroot00000000000000/* * 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. * * 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. * * Since 1.6.2 */ #define FWUPD_REQUEST_ID_PRESS_UNLOCK "org.freedesktop.fwupd.request.press-unlock" const gchar * fwupd_request_kind_to_string(FwupdRequestKind kind); FwupdRequestKind fwupd_request_kind_from_string(const gchar *kind); FwupdRequest * fwupd_request_new(void); gchar * fwupd_request_to_string(FwupdRequest *self); const gchar * fwupd_request_get_id(FwupdRequest *self); void fwupd_request_set_id(FwupdRequest *self, const gchar *id); guint64 fwupd_request_get_created(FwupdRequest *self); void fwupd_request_set_created(FwupdRequest *self, guint64 created); const gchar * fwupd_request_get_device_id(FwupdRequest *self); void fwupd_request_set_device_id(FwupdRequest *self, const gchar *device_id); const gchar * fwupd_request_get_message(FwupdRequest *self); void fwupd_request_set_message(FwupdRequest *self, const gchar *message); const gchar * fwupd_request_get_image(FwupdRequest *self); void fwupd_request_set_image(FwupdRequest *self, const gchar *image); FwupdRequestKind fwupd_request_get_kind(FwupdRequest *self); void fwupd_request_set_kind(FwupdRequest *self, FwupdRequestKind kind); FwupdRequest * fwupd_request_from_variant(GVariant *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-security-attr-private.h000066400000000000000000000164541420024370600222600ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-security-attr.h" G_BEGIN_DECLS /** * FWUPD_SECURITY_ATTR_ID_ACPI_DMAR: * * Host Security ID attribute for ACPI DMAR table * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_ACPI_DMAR "org.fwupd.hsi.AcpiDmar" /** * 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 attesation * * 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_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_INTEL_DCI_ENABLED: * * Host Security ID attribute for Intel DCI enabled * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_DCI_ENABLED "org.fwupd.hsi.IntelDci.Enabled" /** * FWUPD_SECURITY_ATTR_ID_INTEL_DCI_LOCKED: * * Host Security ID attribute for Intel DCI locked * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_DCI_LOCKED "org.fwupd.hsi.IntelDci.Locked" /** * 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" GVariant * fwupd_security_attr_to_variant(FwupdSecurityAttr *self); void fwupd_security_attr_to_json(FwupdSecurityAttr *self, JsonBuilder *builder); gboolean fwupd_security_attr_from_json(FwupdSecurityAttr *self, JsonNode *json_node, GError **error); FwupdSecurityAttr * fwupd_security_attr_copy(FwupdSecurityAttr *self); FwupdSecurityAttrResult fwupd_security_attr_get_result_fallback(FwupdSecurityAttr *self); void fwupd_security_attr_set_result_fallback(FwupdSecurityAttr *self, FwupdSecurityAttrResult result); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-security-attr.c000066400000000000000000001144331420024370600205770ustar00rootroot00000000000000/* * 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 *plugin; gchar *url; guint64 created; FwupdSecurityAttrLevel level; FwupdSecurityAttrResult result; FwupdSecurityAttrResult result_fallback; FwupdSecurityAttrFlags flags; } 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_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"; 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, "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; 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_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: 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 (!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_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_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; } /** * 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_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)); 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_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->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->metadata != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_METADATA, fwupd_hash_kv_to_variant(priv->metadata)); } 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_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_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; } } static 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); } 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); } static 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_security_attr_from_json: * @self: a #FwupdSecurityAttr * @json_node: 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) { #if JSON_CHECK_VERSION(1, 6, 0) 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, 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_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)); /* 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_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; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "json-glib version too old"); return FALSE; #endif } /** * 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_NAME, priv->name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_URI, priv->url); 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); } } } static 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_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)); 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_PLUGIN, priv->plugin); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_URI, priv->url); 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->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->appstream_id); g_free(priv->name); 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; 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_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); 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. * * 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.7.5/libfwupd/fwupd-security-attr.h000066400000000000000000000162121420024370600206000ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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_RUNTIME_UPDATES: Suffix `U` * @FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION: Suffix `A` * @FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE: Suffix `!` * * 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_RUNTIME_UPDATES = 1 << 8, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION = 1 << 9, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE = 1 << 10, } 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); const gchar * fwupd_security_attr_get_appstream_id(FwupdSecurityAttr *self); void fwupd_security_attr_set_appstream_id(FwupdSecurityAttr *self, const gchar *appstream_id); FwupdSecurityAttrLevel fwupd_security_attr_get_level(FwupdSecurityAttr *self); void fwupd_security_attr_set_level(FwupdSecurityAttr *self, FwupdSecurityAttrLevel level); FwupdSecurityAttrResult fwupd_security_attr_get_result(FwupdSecurityAttr *self); void fwupd_security_attr_set_result(FwupdSecurityAttr *self, FwupdSecurityAttrResult result); const gchar * fwupd_security_attr_get_name(FwupdSecurityAttr *self); void fwupd_security_attr_set_name(FwupdSecurityAttr *self, const gchar *name); const gchar * fwupd_security_attr_get_plugin(FwupdSecurityAttr *self); void fwupd_security_attr_set_plugin(FwupdSecurityAttr *self, const gchar *plugin); const gchar * fwupd_security_attr_get_url(FwupdSecurityAttr *self); void fwupd_security_attr_set_url(FwupdSecurityAttr *self, const gchar *url); guint64 fwupd_security_attr_get_created(FwupdSecurityAttr *self); void fwupd_security_attr_set_created(FwupdSecurityAttr *self, guint64 created); GPtrArray * fwupd_security_attr_get_obsoletes(FwupdSecurityAttr *self); void fwupd_security_attr_add_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id); gboolean fwupd_security_attr_has_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id); GPtrArray * fwupd_security_attr_get_guids(FwupdSecurityAttr *self); void fwupd_security_attr_add_guid(FwupdSecurityAttr *self, const gchar *guid); void fwupd_security_attr_add_guids(FwupdSecurityAttr *self, GPtrArray *guids); gboolean fwupd_security_attr_has_guid(FwupdSecurityAttr *self, const gchar *guid); const gchar * fwupd_security_attr_get_metadata(FwupdSecurityAttr *self, const gchar *key); void fwupd_security_attr_add_metadata(FwupdSecurityAttr *self, const gchar *key, const gchar *value); FwupdSecurityAttrFlags fwupd_security_attr_get_flags(FwupdSecurityAttr *self); void fwupd_security_attr_set_flags(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flags); void fwupd_security_attr_add_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag); gboolean fwupd_security_attr_has_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag); 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); GPtrArray * fwupd_security_attr_array_from_variant(GVariant *value); G_END_DECLS fwupd-1.7.5/libfwupd/fwupd-self-test.c000066400000000000000000001006171420024370600176650ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #ifdef HAVE_FNMATCH_H #include #endif #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-release-private.h" #include "fwupd-remote-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 */ #ifdef HAVE_FNMATCH_H if (fnmatch(txt2, txt1, FNM_NOESCAPE) == 0) return TRUE; #else if (g_strcmp0(txt1, txt2) == 0) return TRUE; #endif /* 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; } /* https://gitlab.gnome.org/GNOME/glib/issues/225 */ static guint _g_string_replace(GString *string, const gchar *search, const gchar *replace) { gchar *tmp; guint count = 0; gsize search_idx = 0; gsize replace_len; gsize search_len; g_return_val_if_fail(string != NULL, 0); g_return_val_if_fail(search != NULL, 0); g_return_val_if_fail(replace != NULL, 0); /* nothing to do */ if (string->len == 0) return 0; search_len = strlen(search); replace_len = strlen(replace); do { tmp = g_strstr_len(string->str + search_idx, -1, search); if (tmp == NULL) break; /* advance the counter in case @replace contains @search */ search_idx = (gsize)(tmp - string->str); /* reallocate the string if required */ if (search_len > replace_len) { g_string_erase(string, (gssize)search_idx, (gssize)(search_len - replace_len)); memcpy(tmp, replace, replace_len); } else if (search_len < replace_len) { g_string_insert_len(string, (gssize)search_idx, replace, (gssize)(replace_len - search_len)); /* we have to treat this specially as it could have * been reallocated when the insertion happened */ memcpy(string->str + search_idx, replace, replace_len); } else { /* just memcmp in the new string */ memcpy(tmp, replace, replace_len); } search_idx += replace_len; count++; } while (TRUE); return count; } 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); } /* bitfield */ for (guint64 i = 1; i < FWUPD_DEVICE_FLAG_UNKNOWN; i *= 2) { const gchar *tmp = fwupd_device_flag_to_string(i); if (tmp == NULL) break; g_assert_cmpint(fwupd_device_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i < FWUPD_PLUGIN_FLAG_UNKNOWN; i *= 2) { const gchar *tmp = fwupd_plugin_flag_to_string(i); if (tmp == NULL) break; g_assert_cmpint(fwupd_plugin_flag_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_get_enabled(remote)); 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_get_enabled(remote)); 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 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_get_enabled(remote)); 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_get_enabled(remote)); 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_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = 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_get_enabled(remote)); 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); } static void fwupd_release_func(void) { g_autoptr(FwupdRelease) release1 = NULL; g_autoptr(FwupdRelease) release2 = 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"); 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"); } 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_set_message(request, "foo"); fwupd_request_set_image(request, "bar"); str = fwupd_request_to_string(request); g_debug("%s", str); /* 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"); } 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(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; /* 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_icon(dev, "input-gaming"); fwupd_device_add_icon(dev, "input-mouse"); 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, " ", " "); ret = fu_test_compare_lines(str_ascii->str, "FwupdDevice:\n" " DeviceId: USB:foo\n" " Name: ColorHug2\n" " Guid: 2082b5e0-7a64-478a-b1b2-e3404fab6dad\n" " Guid: 00000000-0000-0000-0000-000000000000\n" " Flags: updatable|require-ac\n" " Checksum: SHA1(beefdead)\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(dev, 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); g_assert_nonnull(data); ret = fu_test_compare_lines(data, "{\n" " \"Name\" : \"ColorHug2\",\n" " \"DeviceId\" : \"USB:foo\",\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" " \"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); } 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_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); 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_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_true(ret); g_assert_no_error(error); 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_true(ret); g_assert_no_error(error); 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_security_attr_to_json_string(FwupdSecurityAttr *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); fwupd_security_attr_to_json(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 *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(GError) error = 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); 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_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); /* to JSON */ json = fwupd_security_attr_to_json_string(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); } int main(int argc, char **argv) { 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); 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/request", fwupd_request_func); g_test_add_func("/fwupd/device", fwupd_device_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); 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.7.5/libfwupd/fwupd-thread-test.c000066400000000000000000000060701420024370600202010ustar00rootroot00000000000000/* * 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_FLAGS_NONE); 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.7.5/libfwupd/fwupd-version.c000066400000000000000000000011001420024370600174270ustar00rootroot00000000000000/* * 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.7.5/libfwupd/fwupd-version.h.in000066400000000000000000000026521420024370600200560ustar00rootroot00000000000000/* * 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.7.5/libfwupd/fwupd.h000066400000000000000000000012621420024370600157620ustar00rootroot00000000000000/* * 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 #ifndef FWUPD_DISABLE_DEPRECATED #include #endif #undef __FWUPD_H_INSIDE__ fwupd-1.7.5/libfwupd/fwupd.map000066400000000000000000000454451420024370600163230ustar00rootroot00000000000000# 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; fwupd-1.7.5/libfwupd/meson.build000066400000000000000000000130041420024370600166230ustar00rootroot00000000000000fwupd_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-client.h', 'fwupd-client-sync.h', 'fwupd-common.h', 'fwupd-deprecated.h', 'fwupd-device.h', 'fwupd-enums.h', 'fwupd-error.h', 'fwupd-remote.h', 'fwupd-request.h', 'fwupd-security-attr.h', 'fwupd-release.h', 'fwupd-plugin.h', fwupd_version_h, ], subdir : 'fwupd-1/libfwupd', ) libfwupd_deps = [ giounix, gmodule, libjcat, libjsonglib, ] if get_option('curl') libfwupd_deps += libcurl endif 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-security-attr.c', 'fwupd-release.c', # fuzzing 'fwupd-plugin.c', 'fwupd-remote.c', '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 : 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 get_option('introspection') fwupd_gir_deps = [ giounix, ] if get_option('curl') fwupd_gir_deps += libcurl endif 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-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-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', 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: [ python3, join_paths(meson.source_root(), 'contrib', 'generate-version-script.py'), 'LIBFWUPD', '@INPUT@', '@OUTPUT@', ], ) test('fwupd-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), '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', sources : [ 'fwupd-self-test.c' ], include_directories : [ root_incdir, ], dependencies : [ libfwupd_deps, ], link_with : fwupd, c_args : [ '-DG_LOG_DOMAIN="Fwupd"', '-DLOCALSTATEDIR="' + localstatedir + '"', ], ) test('fwupd-self-test', e, timeout: 60, env : env) if 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.7.5/libfwupd/tests/000077500000000000000000000000001420024370600156255ustar00rootroot00000000000000fwupd-1.7.5/libfwupd/tests/dell-esrt.conf000077700000000000000000000000001420024370600271262../../plugins/dell-esrt/dell-esrt.confustar00rootroot00000000000000fwupd-1.7.5/libfwupd/tests/disabled.conf000066400000000000000000000000641420024370600202430ustar00rootroot00000000000000[fwupd Remote] Enabled=false Keyring=none Password= fwupd-1.7.5/libfwupd/tests/firmware-base-uri.conf000066400000000000000000000002471420024370600220200ustar00rootroot00000000000000[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.7.5/libfwupd/tests/firmware-nopath.conf000066400000000000000000000002011420024370600215700ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=jcat MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz fwupd-1.7.5/libfwupd/tests/remotes.d000077700000000000000000000000001420024370600227042../../data/remotes.d/ustar00rootroot00000000000000fwupd-1.7.5/libfwupd/tests/stable.conf000077700000000000000000000000001420024370600263412../../src/tests/remotes.d/stable.confustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/000077500000000000000000000000001420024370600157025ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/README.md000066400000000000000000000036671420024370600171750ustar00rootroot00000000000000# 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. Remember: Plugins should be upstream! ## Migrating from older API * Migrate from fu_common_is_cpu_intel() to fu_common_get_cpu_vendor() * Migrate from fu_firmware_strparse_uintXX() to fu_firmware_strparse_uintXX_safe() * Remove calls to fu_plugin_get_usb_context() and fu_plugin_set_usb_context() * Migrate from fu_plugin_runner_usb_device_added(), fu_plugin_runner_udev_device_added() and fu_plugin_runner_udev_device_changed() to fu_plugin_runner_backend_device_added() * Migrate from FuHidDevice->open() and FuHidDevice->close() to using the superclass helpers * Migrate from FuUsbDevice->probe(), FuUsbDevice->open() and FuUsbDevice->close() to using the superclass helpers * Migrate from FuUdevDevice->to_string(), FuUdevDevice->probe(), FuUdevDevice->open() and FuUdevDevice->close() to using the superclass helpers * Migrate from fu_device_get_protocol() to fu_device_get_protocols() and fu_device_set_protocol() to fu_device_add_protocol() * Migrate from fu_device_has_custom_flag() to fu_device_has_private_flag() * Migrate from fu_udev_device_set_readonly() to fu_udev_device_set_flags() * Migrate from fu_device_sleep_with_progress() to fu_progress_sleep() -- but be aware the unit of time has changed from *seconds* to *milliseconds* * Migrate from fu_device_get_status() to fu_progress_get_status() * Migrate from fu_device_set_status() to fu_progress_set_status() * Migrate from fu_device_get_progress() to fu_progress_get_percentage() * Migrate from fu_device_set_progress_full() to fu_progress_set_percentage_full() * Migrate from fu_device_set_progress() to fu_progress_set_steps(), fu_progress_add_step() and fu_progress_done -- see the FuProgress docs for more details! ## Planned API/ABI changes for next release * Nothing yet. fwupd-1.7.5/libfwupdplugin/fu-archive-firmware.c000066400000000000000000000033321420024370600217120ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-archive-firmware.h" #include "fu-archive.h" /** * FuArchiveFirmware: * * An archive firmware image, typically for nested firmware volumes. * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuArchiveFirmware, fu_archive_firmware, FU_TYPE_FIRMWARE) 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); fu_firmware_add_image(firmware, img); return TRUE; } static gboolean fu_archive_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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); } static void fu_archive_firmware_init(FuArchiveFirmware *self) { } static void fu_archive_firmware_class_init(FuArchiveFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_archive_firmware_parse; } /** * 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.7.5/libfwupdplugin/fu-archive-firmware.h000066400000000000000000000007721420024370600217240ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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); fwupd-1.7.5/libfwupdplugin/fu-archive.c000066400000000000000000000140111420024370600200740ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * 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; }; 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_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 none): 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 *fw; 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); fw = g_hash_table_lookup(self->entries, fn); if (fw == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no blob for %s", fn); } return fw; } /** * fu_archive_iterate: * @self: a #FuArchive * @callback: (scope call): 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) #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; 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); g_hash_table_insert(self->entries, g_steal_pointer(&fn_key), g_bytes_new_take(g_steal_pointer(&buf), bufsz)); } /* 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: 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. * * 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(data != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_archive_load(self, data, flags, error)) return NULL; return g_steal_pointer(&self); } fwupd-1.7.5/libfwupdplugin/fu-archive.h000066400000000000000000000025061420024370600201070ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; FuArchive * fu_archive_new(GBytes *data, FuArchiveFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_archive_lookup_by_fn(FuArchive *self, const gchar *fn, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_archive_iterate(FuArchive *self, FuArchiveIterateFunc callback, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-backend.c000066400000000000000000000263161420024370600200550ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include "fu-backend.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; GHashTable *devices; /* device_id : * FuDevice */ GThread *thread_init; } FuBackendPrivate; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; enum { PROP_0, PROP_NAME, 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 unset */ if (fu_device_get_context(device) == NULL) fu_device_set_context(device, priv->ctx); /* 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_setup: * @self: a #FuBackend * @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, 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, error)) { priv->enabled = FALSE; return FALSE; } } priv->done_setup = TRUE; return TRUE; } /** * fu_backend_coldplug: * @self: a #FuBackend * @error: (nullable): optional return location for an error * * Adds devices using the subclassed backend. If fu_backend_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, 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, error)) return FALSE; if (klass->coldplug == NULL) return TRUE; return klass->coldplug(self, 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 * @device_id: a DeviceID * * 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 *device_id) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_BACKEND(self), NULL); return g_hash_table_lookup(priv->devices, device_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_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_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: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.7.5/libfwupdplugin/fu-backend.h000066400000000000000000000025741420024370600200620ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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) struct _FuBackendClass { GObjectClass parent_class; /* signals */ gboolean (*setup)(FuBackend *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*coldplug)(FuBackend *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*registered)(FuBackend *self, FuDevice *device); /*< private >*/ gpointer padding[28]; }; const gchar * fu_backend_get_name(FuBackend *self); FuContext * fu_backend_get_context(FuBackend *self); gboolean fu_backend_get_enabled(FuBackend *self); void fu_backend_set_enabled(FuBackend *self, gboolean enabled); GPtrArray * fu_backend_get_devices(FuBackend *self); FuDevice * fu_backend_lookup_by_id(FuBackend *self, const gchar *device_id); gboolean fu_backend_setup(FuBackend *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_backend_coldplug(FuBackend *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_backend_device_added(FuBackend *self, FuDevice *device); void fu_backend_device_removed(FuBackend *self, FuDevice *device); void fu_backend_device_changed(FuBackend *self, FuDevice *device); void fu_backend_registered(FuBackend *self, FuDevice *device); fwupd-1.7.5/libfwupdplugin/fu-bluez-device.c000066400000000000000000000501201420024370600210320ustar00rootroot00000000000000/* * 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" #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, const 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); } if (vid != 0x0 && pid != 0x0 && rev != 0x0) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("BLUETOOTH\\VID_%04X&PID_%04X&REV_%04X", vid, pid, rev); fu_device_add_instance_id(FU_DEVICE(self), devid); } if (vid != 0x0 && pid != 0x0) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("BLUETOOTH\\VID_%04X&PID_%04X", vid, pid); fu_device_add_instance_id(FU_DEVICE(self), devid); } if (vid != 0x0) { g_autofree gchar *devid = NULL; g_autofree gchar *vendor_id = NULL; devid = g_strdup_printf("BLUETOOTH\\VID_%04X", vid); fu_device_add_instance_id_full(FU_DEVICE(self), devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); vendor_id = g_strdup_printf("BLUETOOTH:%04X", vid); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); } } 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_common_string_append_kv(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); 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.7.5/libfwupdplugin/fu-bluez-device.h000066400000000000000000000015441420024370600210450ustar00rootroot00000000000000/* * 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; gpointer __reserved[31]; }; GByteArray * fu_bluez_device_read(FuBluezDevice *self, const gchar *uuid, GError **error); gchar * fu_bluez_device_read_string(FuBluezDevice *self, const gchar *uuid, GError **error); gboolean fu_bluez_device_write(FuBluezDevice *self, const gchar *uuid, GByteArray *buf, GError **error); gboolean fu_bluez_device_notify_start(FuBluezDevice *self, const gchar *uuid, GError **error); gboolean fu_bluez_device_notify_stop(FuBluezDevice *self, const gchar *uuid, GError **error); fwupd-1.7.5/libfwupdplugin/fu-cabinet.c000066400000000000000000000757131420024370600201000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCabinet" #include "config.h" #include #include #include "fwupd-common.h" #include "fwupd-enums.h" #include "fwupd-error.h" #include "fu-cabinet.h" #include "fu-common.h" /** * FuCabinet: * * Cabinet archive parser and writer. * * See also: [class@FuArchive] */ struct _FuCabinet { GObject parent_instance; guint64 size_max; GCabCabinet *gcab_cabinet; gchar *container_checksum; XbBuilder *builder; XbSilo *silo; JcatContext *jcat_context; JcatFile *jcat_file; }; G_DEFINE_TYPE(FuCabinet, fu_cabinet, G_TYPE_OBJECT) 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_object_unref(self->gcab_cabinet); 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); object_class->finalize = fu_cabinet_finalize; } static void fu_cabinet_init(FuCabinet *self) { self->size_max = 1024 * 1024 * 100; self->gcab_cabinet = gcab_cabinet_new(); self->builder = xb_builder_new(); self->jcat_file = jcat_file_new(); self->jcat_context = jcat_context_new(); } /** * fu_cabinet_set_size_max: * @self: a #FuCabinet * @size_max: size in bytes * * Sets the maximum size of the decompressed cabinet file. * * Since: 1.4.0 **/ void fu_cabinet_set_size_max(FuCabinet *self, guint64 size_max) { g_return_if_fail(FU_IS_CABINET(self)); self->size_max = size_max; } /** * 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 * * 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) { g_return_val_if_fail(FU_IS_CABINET(self), NULL); if (self->silo == NULL) return NULL; return g_object_ref(self->silo); } static GCabFile * fu_cabinet_get_file_by_name(FuCabinet *self, const gchar *basename) { GPtrArray *folders = gcab_cabinet_get_folders(self->gcab_cabinet); for (guint i = 0; i < folders->len; i++) { GCabFolder *cabfolder = GCAB_FOLDER(g_ptr_array_index(folders, i)); GCabFile *cabfile = gcab_folder_get_file_by_name(cabfolder, basename); if (cabfile != NULL) return cabfile; } return NULL; } /** * 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) { GPtrArray *folders; GCabFile *gcab_file_old; g_autoptr(GCabFolder) gcab_folder = NULL; g_autoptr(GCabFile) gcab_file = NULL; g_return_if_fail(FU_IS_CABINET(self)); g_return_if_fail(basename != NULL); g_return_if_fail(data != NULL); /* existing file? */ gcab_file_old = fu_cabinet_get_file_by_name(self, basename); if (gcab_file_old != NULL) { #ifdef HAVE_GCAB_FILE_SET_BYTES gcab_file_set_bytes(gcab_file_old, data); #else g_object_set(gcab_file_old, "bytes", data, NULL); #endif return; } /* new file, in a possibly new folder */ folders = gcab_cabinet_get_folders(self->gcab_cabinet); if (folders->len == 0) { gcab_folder = gcab_folder_new(GCAB_COMPRESSION_NONE); gcab_cabinet_add_folder(self->gcab_cabinet, gcab_folder, NULL); } else { gcab_folder = g_object_ref(GCAB_FOLDER(g_ptr_array_index(folders, 0))); } gcab_file = gcab_file_new_with_bytes(basename, data); gcab_folder_add_file(gcab_folder, gcab_file, FALSE, NULL, NULL); } /** * 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) { GCabFile *cabfile; GBytes *blob; g_return_val_if_fail(FU_IS_CABINET(self), NULL); g_return_val_if_fail(basename != NULL, NULL); cabfile = fu_cabinet_get_file_by_name(self, basename); if (cabfile == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot find %s in archive", basename); return NULL; } blob = gcab_file_get_bytes(cabfile); if (blob == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile firmware"); return NULL; } return g_bytes_ref(blob); } /* sets the firmware and signature blobs on XbNode */ static gboolean fu_cabinet_parse_release(FuCabinet *self, XbNode *release, GError **error) { GCabFile *cabfile; GBytes *blob; const gchar *csum_filename = NULL; g_autofree gchar *basename = 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='binary']", 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); cabfile = fu_cabinet_get_file_by_name(self, basename); if (cabfile == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot find %s in archive", basename); return FALSE; } blob = gcab_file_get_bytes(cabfile); if (blob == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile firmware"); 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 = fu_common_strtoull(xb_node_get_text(nsize)); 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_debug("failed to verify payload %s: %s", basename, error_local->message); } else { g_debug("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; basename_sig = g_strdup_printf("%s.asc", basename); cabfile = fu_cabinet_get_file_by_name(self, basename_sig); if (cabfile != NULL) { GBytes *data_sig; g_autoptr(JcatResult) jcat_result = NULL; g_autoptr(JcatBlob) jcat_blob = NULL; g_autoptr(GError) error_local = NULL; data_sig = gcab_file_get_bytes(cabfile); if (data_sig == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile %s", basename_sig); 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_debug("failed to verify payload %s using detached: %s", basename, error_local->message); } else { g_debug("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) { 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) return g_object_ref(bc); } return NULL; } static gboolean fu_cabinet_set_container_checksum_cb(XbBuilderFixup *builder_fixup, XbBuilderNode *bn, gpointer user_data, GError **error) { FuCabinet *self = FU_CABINET(user_data); g_autoptr(XbBuilderNode) csum = NULL; /* not us */ if (g_strcmp0(xb_builder_node_get_element(bn), "release") != 0) return TRUE; /* verify it exists */ csum = _xb_builder_node_get_child_by_element_attr(bn, "checksum", "type", "container"); if (csum == NULL) { csum = xb_builder_node_insert(bn, "checksum", "target", "container", NULL); } /* verify it is correct */ if (g_strcmp0(xb_builder_node_get_text(csum), self->container_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), self->container_checksum); } xb_builder_node_set_text(csum, self->container_checksum, -1); } 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 LIBXMLB_CHECK_VERSION(0, 3, 4) if (xb_builder_node_get_first_child(bn) == NULL) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_STRIP_TEXT); #endif return TRUE; } /* adds each GCabFile to the silo */ static gboolean fu_cabinet_build_silo_file(FuCabinet *self, GCabFile *cabfile, FwupdReleaseFlags release_flags, GError **error) { GBytes *blob; 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(bn_info, "metadata_trust", NULL); xb_builder_node_insert_text(bn_info, "filename", gcab_file_get_name(cabfile), 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 = gcab_file_get_bytes(cabfile); if (blob == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GBytes from GCabFile"); 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, GCabFile *cabfile, GError **error) { FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE; const gchar *fn = gcab_file_get_extract_name(cabfile); 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_debug("failed to verify %s: no JcatItem", fn); } else { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) results = NULL; results = jcat_context_verify_item(self->jcat_context, gcab_file_get_bytes(cabfile), item, JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error_local); if (results == NULL) { g_debug("failed to verify %s: %s", fn, error_local->message); } else { g_debug("verified metadata %s: %u", fn, results->len); release_flags |= FWUPD_RELEASE_FLAG_TRUSTED_METADATA; } } /* actually parse the XML now */ g_debug("processing file: %s", fn); if (!fu_cabinet_build_silo_file(self, cabfile, release_flags, error)) { g_prefix_error(error, "%s could not be loaded: ", gcab_file_get_extract_name(cabfile)); return FALSE; } /* success */ return TRUE; } /* load the firmware.jcat files if included */ static gboolean fu_cabinet_build_jcat_folder(FuCabinet *self, GCabFolder *cabfolder, GError **error) { g_autoptr(GSList) cabfiles = gcab_folder_get_files(cabfolder); for (GSList *l = cabfiles; l != NULL; l = l->next) { GCabFile *cabfile = GCAB_FILE(l->data); const gchar *fn = gcab_file_get_extract_name(cabfile); if (g_str_has_suffix(fn, ".jcat")) { GBytes *data_jcat = gcab_file_get_bytes(cabfile); g_autoptr(GInputStream) istream = NULL; 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 GCabFolder to the silo */ static gboolean fu_cabinet_build_silo_folder(FuCabinet *self, GCabFolder *cabfolder, GError **error) { g_autoptr(GSList) cabfiles = gcab_folder_get_files(cabfolder); for (GSList *l = cabfiles; l != NULL; l = l->next) { GCabFile *cabfile = GCAB_FILE(l->data); const gchar *fn = gcab_file_get_extract_name(cabfile); if (!g_str_has_suffix(fn, ".metainfo.xml")) continue; if (!fu_cabinet_build_silo_metainfo(self, cabfile, error)) return FALSE; } return TRUE; } static gboolean fu_cabinet_build_silo(FuCabinet *self, GBytes *data, GError **error) { GPtrArray *folders; 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 */ folders = gcab_cabinet_get_folders(self->gcab_cabinet); if (self->jcat_context != NULL) { for (guint i = 0; i < folders->len; i++) { GCabFolder *cabfolder = GCAB_FOLDER(g_ptr_array_index(folders, i)); if (!fu_cabinet_build_jcat_folder(self, cabfolder, error)) return FALSE; } } /* adds each metainfo file to the silo */ for (guint i = 0; i < folders->len; i++) { GCabFolder *cabfolder = GCAB_FOLDER(g_ptr_array_index(folders, i)); if (!fu_cabinet_build_silo_folder(self, cabfolder, 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("SetContainerChecksum", fu_cabinet_set_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, #if LIBXMLB_CHECK_VERSION(0, 3, 4) XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT, #else XB_BUILDER_COMPILE_FLAG_NONE, #endif NULL, error); if (self->silo == NULL) return FALSE; /* success */ return TRUE; } typedef struct { FuCabinet *self; guint64 size_total; GError *error; } FuCabinetDecompressHelper; static gboolean fu_cabinet_decompress_file_cb(GCabFile *file, gpointer user_data) { FuCabinetDecompressHelper *helper = (FuCabinetDecompressHelper *)user_data; FuCabinet *self = FU_CABINET(helper->self); g_autofree gchar *basename = NULL; g_autofree gchar *name = NULL; /* already failed */ if (helper->error != NULL) return FALSE; /* check the size of the compressed file */ if (gcab_file_get_size(file) > self->size_max) { g_autofree gchar *sz_val = g_format_size(gcab_file_get_size(file)); g_autofree gchar *sz_max = g_format_size(self->size_max); g_set_error(&helper->error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file %s was too large (%s, limit %s)", gcab_file_get_name(file), sz_val, sz_max); return FALSE; } /* check the total size of all the compressed files */ helper->size_total += gcab_file_get_size(file); if (helper->size_total > self->size_max) { g_autofree gchar *sz_val = g_format_size(helper->size_total); g_autofree gchar *sz_max = g_format_size(self->size_max); g_set_error(&helper->error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "uncompressed data too large (%s, limit %s)", sz_val, sz_max); return FALSE; } /* convert to UNIX paths */ name = g_strdup(gcab_file_get_name(file)); g_strdelimit(name, "\\", '/'); /* ignore the dirname completely */ basename = g_path_get_basename(name); gcab_file_set_extract_name(file, basename); return TRUE; } static gboolean fu_cabinet_decompress(FuCabinet *self, GBytes *data, GError **error) { FuCabinetDecompressHelper helper = { .self = self, .size_total = 0, .error = NULL, }; g_autoptr(GError) error_local = NULL; g_autoptr(GInputStream) istream = NULL; /* load from a seekable stream */ istream = g_memory_input_stream_new_from_bytes(data); if (!gcab_cabinet_load(self->gcab_cabinet, istream, NULL, error)) return FALSE; /* check the size is sane */ if (gcab_cabinet_get_size(self->gcab_cabinet) > self->size_max) { g_autofree gchar *sz_val = g_format_size(gcab_cabinet_get_size(self->gcab_cabinet)); g_autofree gchar *sz_max = g_format_size(self->size_max); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "archive too large (%s, limit %s)", sz_val, sz_max); return FALSE; } /* decompress the file to memory */ if (!gcab_cabinet_extract_simple(self->gcab_cabinet, NULL, fu_cabinet_decompress_file_cb, &helper, NULL, &error_local)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return FALSE; } /* the file callback set an error */ if (helper.error != NULL) { g_propagate_error(error, helper.error); return FALSE; } /* success */ return TRUE; } /** * fu_cabinet_export: * @self: a #FuCabinet * @flags: export flags, e.g. %FU_CABINET_EXPORT_FLAG_NONE * @error: (nullable): optional return location for an error * * Exports the cabinet archive. * * Returns: (transfer full): a data blob * * Since: 1.6.0 **/ GBytes * fu_cabinet_export(FuCabinet *self, FuCabinetExportFlags flags, GError **error) { g_autoptr(GOutputStream) op = NULL; op = g_memory_output_stream_new_resizable(); if (!gcab_cabinet_write_simple(self->gcab_cabinet, op, NULL, NULL, /* progress */ NULL, error)) return NULL; if (!g_output_stream_close(op, NULL, error)) return NULL; return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(op)); } 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 = fu_cabinet_get_silo(self); /* get all the firmware referenced by the metainfo files */ 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 = fu_cabinet_get_silo(self); 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; } /** * fu_cabinet_parse: * @self: a #FuCabinet * @data: cabinet archive * @flags: parse flags, e.g. %FU_CABINET_PARSE_FLAG_NONE * @error: (nullable): optional return location for an error * * Parses the cabinet archive. * * Returns: %TRUE for success * * Since: 1.4.0 **/ gboolean fu_cabinet_parse(FuCabinet *self, GBytes *data, FuCabinetParseFlags flags, GError **error) { 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(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(self->silo == NULL, FALSE); /* decompress */ if (!fu_cabinet_decompress(self, data, error)) return FALSE; /* build xmlb silo */ self->container_checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data); if (!fu_cabinet_build_silo(self, data, error)) return FALSE; /* sanity check */ components = xb_silo_query(self->silo, "components/component[@type='firmware']", 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", #if LIBXMLB_CHECK_VERSION(0, 2, 0) XB_QUERY_FLAG_FORCE_NODE_CACHE, #else XB_QUERY_FLAG_NONE, #endif 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; 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_debug("processing release: %s", xb_node_get_attr(rel, "version")); if (!fu_cabinet_parse_release(self, rel, error)) return FALSE; } } /* success */ return TRUE; } /** * 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.7.5/libfwupdplugin/fu-cabinet.h000066400000000000000000000034471420024370600201000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #define FU_TYPE_CABINET (fu_cabinet_get_type()) G_DECLARE_FINAL_TYPE(FuCabinet, fu_cabinet, FU, CABINET, GObject) /** * FuCabinetParseFlags: * @FU_CABINET_PARSE_FLAG_NONE: No flags set * * The flags to use when loading the cabinet. **/ typedef enum { FU_CABINET_PARSE_FLAG_NONE = 0, /*< private >*/ FU_CABINET_PARSE_FLAG_LAST } FuCabinetParseFlags; /** * FuCabinetExportFlags: * @FU_CABINET_EXPORT_FLAG_NONE: No flags set * * The flags to use when exporting the archive. **/ typedef enum { FU_CABINET_EXPORT_FLAG_NONE = 0, /*< private >*/ FU_CABINET_EXPORT_FLAG_LAST } FuCabinetExportFlags; /** * 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_size_max(FuCabinet *self, guint64 size_max); void fu_cabinet_set_jcat_context(FuCabinet *self, JcatContext *jcat_context); gboolean fu_cabinet_parse(FuCabinet *self, GBytes *data, FuCabinetParseFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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); GBytes * fu_cabinet_export(FuCabinet *self, FuCabinetExportFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; XbSilo * fu_cabinet_get_silo(FuCabinet *self); fwupd-1.7.5/libfwupdplugin/fu-cfi-device.c000066400000000000000000000340001420024370600204510ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCfiDevice" #include "config.h" #include "fu-cfi-device.h" /** * FuCfiDevice: * * A chip conforming to the Common Flash Memory Interface, typically a SPI flash chip. * * 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)) static const gchar * fu_cfi_device_cmd_to_string(FuCfiDeviceCmd cmd) { if (cmd == FU_CFI_DEVICE_CMD_READ_ID) return "CfiDeviceCmdReadId"; if (cmd == FU_CFI_DEVICE_CMD_PAGE_PROG) return "CfiDeviceCmdPageProg"; if (cmd == FU_CFI_DEVICE_CMD_CHIP_ERASE) return "CfiDeviceCmdChipErase"; if (cmd == FU_CFI_DEVICE_CMD_READ_DATA) return "CfiDeviceCmdReadData"; if (cmd == FU_CFI_DEVICE_CMD_READ_STATUS) return "CfiDeviceCmdReadStatus"; if (cmd == FU_CFI_DEVICE_CMD_SECTOR_ERASE) return "CfiDeviceCmdSectorErase"; if (cmd == FU_CFI_DEVICE_CMD_WRITE_EN) return "CfiDeviceCmdWriteEn"; if (cmd == FU_CFI_DEVICE_CMD_WRITE_STATUS) return "CfiDeviceCmdWriteStatus"; if (cmd == FU_CFI_DEVICE_CMD_BLOCK_ERASE) return "CfiDeviceCmdBlockErase"; 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); } /* returns at most 4 chars from the ID, or %NULL if no different from existing ID */ static gchar * fu_cfi_device_get_flash_id_jedec(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); if (priv->flash_id == NULL) return NULL; if (strlen(priv->flash_id) <= 4) return NULL; return g_strndup(priv->flash_id, 4); } static gboolean fu_cfi_device_probe(FuDevice *device, GError **error) { FuCfiDevice *self = FU_CFI_DEVICE(device); FuCfiDevicePrivate *priv = GET_PRIVATE(self); /* load the parameters from quirks */ if (priv->flash_id != NULL) { g_autofree gchar *flash_id_jedec = NULL; g_autofree gchar *instance_id0 = NULL; g_autofree gchar *instance_id1 = NULL; /* least specific so adding first */ flash_id_jedec = fu_cfi_device_get_flash_id_jedec(self); if (flash_id_jedec != NULL) { instance_id1 = g_strdup_printf("CFI\\FLASHID_%s", flash_id_jedec); fu_device_add_instance_id_full(FU_DEVICE(self), instance_id1, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* this is most specific and can override keys of instance_id1 */ instance_id0 = g_strdup_printf("CFI\\FLASHID_%s", priv->flash_id); fu_device_add_instance_id_full(FU_DEVICE(self), instance_id0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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, or 0 if unknown * * 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, or 0 if unknown * * 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, or 0 if unknown * * 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, "CfiDeviceCmdReadId") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_ID] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdReadIdSz") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmd_read_id_sz = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdChipErase") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_CHIP_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdBlockErase") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_BLOCK_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdSectorErase") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_SECTOR_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdWriteStatus") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_STATUS] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdPageProg") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_PAGE_PROG] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdReadData") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_DATA] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdReadStatus") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_STATUS] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceCmdWriteEn") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_EN] = tmp; return TRUE; } if (g_strcmp0(key, "CfiDevicePageSize") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->page_size = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceSectorSize") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->sector_size = tmp; return TRUE; } if (g_strcmp0(key, "CfiDeviceBlockSize") == 0) { if (!fu_common_strtoull_full(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_common_string_append_kv(str, idt, "FlashId", priv->flash_id); for (guint i = 0; i < FU_CFI_DEVICE_CMD_LAST; i++) { fu_common_string_append_kx(str, idt, fu_cfi_device_cmd_to_string(i), priv->cmds[i]); } if (priv->page_size > 0) fu_common_string_append_kx(str, idt, "PageSize", priv->page_size); if (priv->sector_size > 0) fu_common_string_append_kx(str, idt, "SectorSize", priv->sector_size); if (priv->block_size > 0) fu_common_string_append_kx(str, idt, "BlockSize", priv->block_size); } static void fu_cfi_device_init(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); 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_set_summary(FU_DEVICE(self), "CFI flash chip"); } 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; klass_device->probe = fu_cfi_device_probe; klass_device->to_string = fu_cfi_device_to_string; klass_device->set_quirk_kv = fu_cfi_device_set_quirk_kv; /** * 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.7.5/libfwupdplugin/fu-cfi-device.h000066400000000000000000000040321420024370600204600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; gpointer __reserved[31]; }; /** * 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); const gchar * fu_cfi_device_get_flash_id(FuCfiDevice *self); void fu_cfi_device_set_flash_id(FuCfiDevice *self, const gchar *flash_id); guint64 fu_cfi_device_get_size(FuCfiDevice *self); void fu_cfi_device_set_size(FuCfiDevice *self, guint64 size); guint32 fu_cfi_device_get_page_size(FuCfiDevice *self); void fu_cfi_device_set_page_size(FuCfiDevice *self, guint32 page_size); guint32 fu_cfi_device_get_sector_size(FuCfiDevice *self); void fu_cfi_device_set_sector_size(FuCfiDevice *self, guint32 sector_size); guint32 fu_cfi_device_get_block_size(FuCfiDevice *self); void fu_cfi_device_set_block_size(FuCfiDevice *self, guint32 block_size); gboolean fu_cfi_device_get_cmd(FuCfiDevice *self, FuCfiDeviceCmd cmd, guint8 *value, GError **error); fwupd-1.7.5/libfwupdplugin/fu-cfu-common.c000066400000000000000000000054471420024370600205330ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cfu-common.h" /** * fu_cfu_device_reject_to_string: * @val: a enumerated value, e.g. %FU_CFU_DEVICE_REJECT_OLD_FIRMWARE * * Converts an enumerated reject type to a string. * * Returns: a string, or `unknown` for invalid * * Since: 1.7.0 **/ const gchar * fu_cfu_device_reject_to_string(guint8 val) { if (val == FU_CFU_DEVICE_REJECT_OLD_FIRMWARE) return "old-firmware"; if (val == FU_CFU_DEVICE_REJECT_INV_COMPONENT) return "inv-component"; if (val == FU_CFU_DEVICE_REJECT_SWAP_PENDING) return "swap-pending"; if (val == FU_CFU_DEVICE_REJECT_WRONG_BANK) return "wrong-bank"; if (val == FU_CFU_DEVICE_REJECT_SIGN_RULE) return "sign-rule"; if (val == FU_CFU_DEVICE_REJECT_VER_RELEASE_DEBUG) return "ver-release-debug"; if (val == FU_CFU_DEVICE_REJECT_DEBUG_SAME_VERSION) return "debug-same-version"; return "unknown"; } /** * fu_cfu_device_offer_to_string: * @val: a enumerated value, e.g. %FU_CFU_DEVICE_OFFER_ACCEPT * * Converts an enumerated offer type to a string. * * Returns: a string, or `unknown` for invalid * * Since: 1.7.0 **/ const gchar * fu_cfu_device_offer_to_string(guint8 val) { if (val == FU_CFU_DEVICE_OFFER_SKIP) return "skip"; if (val == FU_CFU_DEVICE_OFFER_ACCEPT) return "accept"; if (val == FU_CFU_DEVICE_OFFER_REJECT) return "reject"; if (val == FU_CFU_DEVICE_OFFER_BUSY) return "busy"; if (val == FU_CFU_DEVICE_OFFER_COMMAND) return "command"; if (val == FU_CFU_DEVICE_OFFER_NOT_SUPPORTED) return "not-supported"; return "unknown"; } /** * fu_cfu_device_status_to_string: * @val: a enumerated value, e.g. %FU_CFU_DEVICE_OFFER_ACCEPT * * Converts an enumerated status type to a string. * * Returns: a string, or `unknown` for invalid * * Since: 1.7.0 **/ const gchar * fu_cfu_device_status_to_string(guint8 val) { if (val == FU_CFU_DEVICE_STATUS_SUCCESS) return "success"; if (val == FU_CFU_DEVICE_STATUS_ERROR_PREPARE) return "error-prepare"; if (val == FU_CFU_DEVICE_STATUS_ERROR_WRITE) return "error-write"; if (val == FU_CFU_DEVICE_STATUS_ERROR_COMPLETE) return "error-complete"; if (val == FU_CFU_DEVICE_STATUS_ERROR_VERIFY) return "error-verify"; if (val == FU_CFU_DEVICE_STATUS_ERROR_CRC) return "error-crc"; if (val == FU_CFU_DEVICE_STATUS_ERROR_SIGNATURE) return "error-signature"; if (val == FU_CFU_DEVICE_STATUS_ERROR_VERSION) return "error-version"; if (val == FU_CFU_DEVICE_STATUS_SWAP_PENDING) return "swap-pending"; if (val == FU_CFU_DEVICE_STATUS_ERROR_INVALID_ADDR) return "error-invalid-address"; if (val == FU_CFU_DEVICE_STATUS_ERROR_NO_OFFER) return "error-no-offer"; if (val == FU_CFU_DEVICE_STATUS_ERROR_INVALID) return "error-invalid"; return "unknown"; } fwupd-1.7.5/libfwupdplugin/fu-cfu-common.h000066400000000000000000000030521420024370600205260ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_CFU_DEVICE_OFFER_SKIP 0x00 #define FU_CFU_DEVICE_OFFER_ACCEPT 0x01 #define FU_CFU_DEVICE_OFFER_REJECT 0x02 #define FU_CFU_DEVICE_OFFER_BUSY 0x03 #define FU_CFU_DEVICE_OFFER_COMMAND 0x04 #define FU_CFU_DEVICE_OFFER_NOT_SUPPORTED 0xFF #define FU_CFU_DEVICE_FLAG_FIRST_BLOCK 0x80 #define FU_CFU_DEVICE_FLAG_LAST_BLOCK 0x40 #define FU_CFU_DEVICE_STATUS_SUCCESS 0x00 #define FU_CFU_DEVICE_STATUS_ERROR_PREPARE 0x01 #define FU_CFU_DEVICE_STATUS_ERROR_WRITE 0x02 #define FU_CFU_DEVICE_STATUS_ERROR_COMPLETE 0x03 #define FU_CFU_DEVICE_STATUS_ERROR_VERIFY 0x04 #define FU_CFU_DEVICE_STATUS_ERROR_CRC 0x05 #define FU_CFU_DEVICE_STATUS_ERROR_SIGNATURE 0x06 #define FU_CFU_DEVICE_STATUS_ERROR_VERSION 0x07 #define FU_CFU_DEVICE_STATUS_SWAP_PENDING 0x08 #define FU_CFU_DEVICE_STATUS_ERROR_INVALID_ADDR 0x09 #define FU_CFU_DEVICE_STATUS_ERROR_NO_OFFER 0x0A #define FU_CFU_DEVICE_STATUS_ERROR_INVALID 0x0B #define FU_CFU_DEVICE_REJECT_OLD_FIRMWARE 0x00 #define FU_CFU_DEVICE_REJECT_INV_COMPONENT 0x01 #define FU_CFU_DEVICE_REJECT_SWAP_PENDING 0x02 #define FU_CFU_DEVICE_REJECT_WRONG_BANK 0x04 #define FU_CFU_DEVICE_REJECT_SIGN_RULE 0xE0 #define FU_CFU_DEVICE_REJECT_VER_RELEASE_DEBUG 0xE1 #define FU_CFU_DEVICE_REJECT_DEBUG_SAME_VERSION 0xE2 const gchar * fu_cfu_device_reject_to_string(guint8 val); const gchar * fu_cfu_device_status_to_string(guint8 val); const gchar * fu_cfu_device_offer_to_string(guint8 val); fwupd-1.7.5/libfwupdplugin/fu-cfu-offer.c000066400000000000000000000343251420024370600203410ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-cfu-offer.h" #include "fu-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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guint8 tmp = 0; guint32 tmp32 = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* component info */ if (!fu_common_read_uint8_safe(buf, bufsz, 0x0, &priv->segment_number, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, 0x1, &tmp, error)) return FALSE; priv->force_ignore_version = (tmp & 0b1) > 0; priv->force_immediate_reset = (tmp & 0b10) > 0; if (!fu_common_read_uint8_safe(buf, bufsz, 0x2, &priv->component_id, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, 0x3, &priv->token, error)) return FALSE; /* version */ if (!fu_common_read_uint32_safe(buf, bufsz, 0x4, &tmp32, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_version_raw(firmware, tmp32); if (!fu_common_read_uint32_safe(buf, bufsz, 0x8, &priv->hw_variant, G_LITTLE_ENDIAN, error)) return FALSE; /* product info */ if (!fu_common_read_uint8_safe(buf, bufsz, 0xC, &tmp, error)) return FALSE; priv->protocol_revision = (tmp >> 4) & 0b1111; priv->bank = (tmp >> 2) & 0b11; if (!fu_common_read_uint8_safe(buf, bufsz, 0xD, &tmp, error)) return FALSE; priv->milestone = (tmp >> 5) & 0b111; if (!fu_common_read_uint16_safe(buf, bufsz, 0xE, &priv->product_id, G_LITTLE_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static GBytes * fu_cfu_offer_write(FuFirmware *firmware, GError **error) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); /* component info */ fu_byte_array_append_uint8(buf, priv->segment_number); fu_byte_array_append_uint8(buf, priv->force_ignore_version | (priv->force_immediate_reset << 1)); fu_byte_array_append_uint8(buf, priv->component_id); fu_byte_array_append_uint8(buf, priv->token); /* version */ fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, priv->hw_variant, G_LITTLE_ENDIAN); /* product info */ fu_byte_array_append_uint8(buf, (priv->protocol_revision << 4) | (priv->bank << 2)); fu_byte_array_append_uint8(buf, priv->milestone << 5); fu_byte_array_append_uint16(buf, priv->product_id, G_LITTLE_ENDIAN); /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "segment_number", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->segment_number = tmp; tmp = xb_node_query_text_as_uint(n, "force_immediate_reset", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->force_immediate_reset = tmp; tmp = xb_node_query_text_as_uint(n, "force_ignore_version", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->force_ignore_version = tmp; 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); } 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.7.5/libfwupdplugin/fu-cfu-offer.h000066400000000000000000000032521420024370600203410ustar00rootroot00000000000000/* * 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); gboolean fu_cfu_offer_get_force_immediate_reset(FuCfuOffer *self); gboolean fu_cfu_offer_get_force_ignore_version(FuCfuOffer *self); guint8 fu_cfu_offer_get_component_id(FuCfuOffer *self); guint8 fu_cfu_offer_get_token(FuCfuOffer *self); guint32 fu_cfu_offer_get_hw_variant(FuCfuOffer *self); guint8 fu_cfu_offer_get_protocol_revision(FuCfuOffer *self); guint8 fu_cfu_offer_get_bank(FuCfuOffer *self); guint8 fu_cfu_offer_get_milestone(FuCfuOffer *self); guint16 fu_cfu_offer_get_product_id(FuCfuOffer *self); void fu_cfu_offer_set_segment_number(FuCfuOffer *self, guint8 segment_number); void fu_cfu_offer_set_force_immediate_reset(FuCfuOffer *self, gboolean force_immediate_reset); void fu_cfu_offer_set_force_ignore_version(FuCfuOffer *self, gboolean force_ignore_version); void fu_cfu_offer_set_component_id(FuCfuOffer *self, guint8 component_id); void fu_cfu_offer_set_token(FuCfuOffer *self, guint8 token); void fu_cfu_offer_set_hw_variant(FuCfuOffer *self, guint32 hw_variant); void fu_cfu_offer_set_protocol_revision(FuCfuOffer *self, guint8 protocol_revision); void fu_cfu_offer_set_bank(FuCfuOffer *self, guint8 bank); void fu_cfu_offer_set_milestone(FuCfuOffer *self, guint8 milestone); void fu_cfu_offer_set_product_id(FuCfuOffer *self, guint16 product_id); fwupd-1.7.5/libfwupdplugin/fu-cfu-payload.c000066400000000000000000000053561420024370600206730ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { guint32 offset = 0; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* process into chunks */ while (offset < bufsz) { guint32 chunk_addr = 0; guint8 chunk_size = 0; g_autoptr(FuChunk) chk = NULL; g_autoptr(GBytes) blob = NULL; /* read chunk header */ if (!fu_common_read_uint32_safe(buf, bufsz, offset, &chunk_addr, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x4, &chunk_size, error)) return FALSE; offset += 0x5; blob = fu_common_bytes_new_offset(fw, offset, chunk_size, error); if (blob == NULL) return FALSE; chk = fu_chunk_bytes_new(blob); fu_chunk_set_address(chk, chunk_addr); fu_firmware_add_chunk(firmware, chk); /* next! */ offset += chunk_size; } /* success */ return TRUE; } static GBytes * 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); fu_byte_array_append_uint32(buf, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, fu_chunk_get_data_sz(chk)); g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } return g_byte_array_free_to_bytes(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.7.5/libfwupdplugin/fu-cfu-payload.h000066400000000000000000000006071420024370600206720ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-chunk-private.h000066400000000000000000000005251420024370600212450ustar00rootroot00000000000000/* * 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); gboolean fu_chunk_build(FuChunk *self, XbNode *n, GError **error); fwupd-1.7.5/libfwupdplugin/fu-chunk.c000066400000000000000000000310561420024370600175730ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuChunk" #include "config.h" #include #include "fu-chunk-private.h" #include "fu-common.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_common_strsafe((const gchar *)self->data, MIN(self->data_sz, 16)); } 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 | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif 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 | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif 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)); } return chunks; } /** * fu_chunk_array_new_from_bytes: * @blob: data * @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_from_bytes(GBytes *blob, guint32 addr_start, guint32 page_sz, guint32 packet_sz) { GPtrArray *chunks; gsize sz; const guint8 *data = g_bytes_get_data(blob, &sz); chunks = fu_chunk_array_new(data, (guint32)sz, addr_start, page_sz, packet_sz); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); chk->bytes = fu_common_bytes_new_offset(blob, chk->data - data, chk->data_sz, NULL); } 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.7.5/libfwupdplugin/fu-chunk.h000066400000000000000000000026421420024370600175770ustar00rootroot00000000000000/* * 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); guint32 fu_chunk_get_idx(FuChunk *self); void fu_chunk_set_page(FuChunk *self, guint32 page); guint32 fu_chunk_get_page(FuChunk *self); void fu_chunk_set_address(FuChunk *self, guint32 address); guint32 fu_chunk_get_address(FuChunk *self); const guint8 * fu_chunk_get_data(FuChunk *self); guint8 * fu_chunk_get_data_out(FuChunk *self); guint32 fu_chunk_get_data_sz(FuChunk *self); void fu_chunk_set_bytes(FuChunk *self, GBytes *bytes); GBytes * fu_chunk_get_bytes(FuChunk *self); FuChunk * fu_chunk_new(guint32 idx, guint32 page, guint32 address, const guint8 *data, guint32 data_sz); gchar * fu_chunk_to_string(FuChunk *self); gchar * fu_chunk_array_to_string(GPtrArray *chunks); 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); GPtrArray * fu_chunk_array_new_from_bytes(GBytes *blob, guint32 addr_start, guint32 page_sz, guint32 packet_sz); fwupd-1.7.5/libfwupdplugin/fu-common-cab.c000066400000000000000000000016471420024370600205010ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommonCab" #include "config.h" #include "fu-cabinet.h" #include "fu-common-cab.h" /** * fu_common_cab_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_common_cab_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_cabinet_set_size_max(cabinet, size_max); if (!fu_cabinet_parse(cabinet, blob, FU_CABINET_PARSE_FLAG_NONE, error)) return NULL; return fu_cabinet_get_silo(cabinet); } fwupd-1.7.5/libfwupdplugin/fu-common-cab.h000066400000000000000000000003721420024370600205000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include XbSilo * fu_common_cab_build_silo(GBytes *blob, guint64 size_max, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-common-freebsd.c000066400000000000000000000062601420024370600213620ustar00rootroot00000000000000/* * Copyright (C) 2021 Sergii Dmytruk * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #include #include #include #ifdef HAVE_FNMATCH_H #include #endif #include "fu-common-private.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); } gboolean fu_common_fnmatch_impl(const gchar *pattern, const gchar *str) { #ifdef HAVE_FNMATCH_H return fnmatch(pattern, str, FNM_NOESCAPE) == 0; #else return g_strcmp0(pattern, str) == 0; #endif } guint64 fu_common_get_memory_size_impl(void) { return (guint64)sysconf(_SC_PHYS_PAGES) * (guint64)sysconf(_SC_PAGE_SIZE); } fwupd-1.7.5/libfwupdplugin/fu-common-guid.c000066400000000000000000000011741420024370600206770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "fu-common-guid.h" #include /** * 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.7.5/libfwupdplugin/fu-common-guid.h000066400000000000000000000003111420024370600206740ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-common-linux.c000066400000000000000000000051421420024370600211050ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #include #include #ifdef HAVE_FNMATCH_H #include #endif #include "fu-common-private.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); } gboolean fu_common_fnmatch_impl(const gchar *pattern, const gchar *str) { #ifdef HAVE_FNMATCH_H return fnmatch(pattern, str, FNM_NOESCAPE) == 0; #else return g_strcmp0(pattern, str) == 0; #endif } guint64 fu_common_get_memory_size_impl(void) { return (guint64)sysconf(_SC_PHYS_PAGES) * (guint64)sysconf(_SC_PAGE_SIZE); } fwupd-1.7.5/libfwupdplugin/fu-common-private.h000066400000000000000000000012611420024370600214230ustar00rootroot00000000000000/* * 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); gboolean fu_common_fnmatch_impl(const gchar *pattern, const gchar *str); guint64 fu_common_get_memory_size_impl(void); /* for self tests */ const gchar * fu_common_convert_to_gpt_type(const gchar *type); fwupd-1.7.5/libfwupdplugin/fu-common-version.c000066400000000000000000000343501420024370600214360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "fu-common-version.h" #include #include #include "fwupd-enums.h" #include "fwupd-error.h" #define FU_COMMON_VERSION_DECODE_BCD(val) ((((val) >> 4) & 0x0f) * 10 + ((val)&0x0f)) /** * fu_common_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.3.6 **/ gchar * fu_common_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_common_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.2.0 **/ gchar * fu_common_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_common_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.2.0 **/ gchar * fu_common_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_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_common_vercmp_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_common_vercmp_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_common_vercmp_char(str1[i], str2[i]); if (rc != 0) return rc; } return fu_common_vercmp_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; } /** * 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 * * Since: 1.2.9 */ 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_common_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; } } /* found no digits */ if (digit_cnt == 0) return NULL; return g_string_free(g_steal_pointer(&version_safe), FALSE); } /** * fu_common_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.3.3 */ gchar * fu_common_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_common_version_from_uint32((guint32)tmp, fmt); } /** * fu_common_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.2.0 */ FwupdVersionFormat fu_common_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_common_version_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_common_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.2.9 **/ gboolean fu_common_version_verify_format(const gchar *version, FwupdVersionFormat fmt, GError **error) { FwupdVersionFormat fmt_base = fu_common_version_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_common_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_common_vercmp_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_common_vercmp_chunk(endptr_a, endptr_b); if (rc < 0) return -1; if (rc > 0) return 1; } } /* we really shouldn't get here */ return 0; } /** * fu_common_vercmp_full: * @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.3.9 */ gint fu_common_vercmp_full(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_common_version_parse_from_format(version_a, fmt); hex_b = fu_common_version_parse_from_format(version_b, fmt); return fu_common_vercmp_safe(hex_a, hex_b); } return fu_common_vercmp_safe(version_a, version_b); } fwupd-1.7.5/libfwupdplugin/fu-common-version.h000066400000000000000000000015331420024370600214400ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include gint fu_common_vercmp_full(const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt); gchar * fu_common_version_from_uint64(guint64 val, FwupdVersionFormat kind); gchar * fu_common_version_from_uint32(guint32 val, FwupdVersionFormat kind); gchar * fu_common_version_from_uint16(guint16 val, FwupdVersionFormat kind); gchar * fu_common_version_parse_from_format(const gchar *version, FwupdVersionFormat fmt); gchar * fu_common_version_ensure_semver(const gchar *version); FwupdVersionFormat fu_common_version_guess_format(const gchar *version); gboolean fu_common_version_verify_format(const gchar *version, FwupdVersionFormat fmt, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-common-windows.c000066400000000000000000000014321420024370600214360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #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, "not supported"); return NULL; } gboolean fu_common_fnmatch_impl(const gchar *pattern, const gchar *str) { g_return_val_if_fail(pattern != NULL, FALSE); g_return_val_if_fail(str != NULL, FALSE); return PathMatchSpecA(str, pattern); } guint64 fu_common_get_memory_size_impl(void) { MEMORYSTATUSEX status; status.dwLength = sizeof(status); GlobalMemoryStatusEx(&status); return (guint64)status.ullTotalPhys; } fwupd-1.7.5/libfwupdplugin/fu-common.c000066400000000000000000003236451420024370600177630ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include #ifdef HAVE_GIO_UNIX #include #endif #include #ifdef HAVE_KENV_H #include #endif #ifdef HAVE_CPUID_H #include #endif #ifdef HAVE_UTSNAME_H #include #endif #ifdef HAVE_LIBARCHIVE #include #include #endif #include #include #include #include #include #include "fwupd-error.h" #include "fu-common-private.h" #include "fu-common-version.h" #include "fu-firmware.h" #include "fu-volume-private.h" /** * fu_common_rmtree: * @directory: a directory name * @error: (nullable): optional return location for an error * * Recursively removes a directory. * * Returns: %TRUE for success, %FALSE otherwise * * Since: 0.9.7 **/ gboolean fu_common_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_common_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_common_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_DIR)) { if (!fu_common_get_file_list_internal(files, src, error)) return FALSE; } else { g_ptr_array_add(files, g_steal_pointer(&src)); } } return TRUE; } /** * fu_common_get_files_recursive: * @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.0.6 **/ GPtrArray * fu_common_get_files_recursive(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_common_get_file_list_internal(files, path, error)) return NULL; return g_steal_pointer(&files); } /** * fu_common_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.7.1 **/ gboolean fu_common_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_common_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: 0.9.7 **/ gboolean fu_common_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_common_mkdir(parent, error); } /** * fu_common_set_contents_bytes: * @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: 0.9.5 **/ gboolean fu_common_set_contents_bytes(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 %" G_GSIZE_FORMAT " bytes", filename, size); return g_file_set_contents(filename, data, size, error); } /** * fu_common_get_contents_bytes: * @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: 0.9.7 **/ GBytes * fu_common_get_contents_bytes(const gchar *filename, GError **error) { gchar *data = NULL; gsize len = 0; g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!g_file_get_contents(filename, &data, &len, error)) return NULL; g_debug("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len); return g_bytes_new_take(data, len); } /** * fu_common_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: 0.9.5 **/ GBytes * fu_common_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_common_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_common_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.7.4 **/ GBytes * fu_common_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_byte_array_free_to_bytes(g_steal_pointer(&buf)); } #ifdef HAVE_LIBARCHIVE static gboolean fu_common_extract_archive_entry(struct archive_entry *entry, const gchar *dir) { const gchar *tmp; g_autofree gchar *buf = NULL; /* no output file */ if (archive_entry_pathname(entry) == NULL) return FALSE; /* update output path */ tmp = archive_entry_pathname(entry); buf = g_build_filename(dir, tmp, NULL); archive_entry_update_pathname_utf8(entry, buf); return TRUE; } #endif /** * fu_common_extract_archive: * @blob: data archive as a blob * @dir: a directory name to extract to * @error: (nullable): optional return location for an error * * Extracts an archive to a directory. * * Returns: %TRUE for success * * Since: 0.9.7 **/ gboolean fu_common_extract_archive(GBytes *blob, const gchar *dir, GError **error) { #ifdef HAVE_LIBARCHIVE gboolean ret = TRUE; int r; struct archive *arch = NULL; struct archive_entry *entry; g_return_val_if_fail(blob != NULL, FALSE); g_return_val_if_fail(dir != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* decompress anything matching either glob */ g_debug("decompressing into %s", dir); arch = archive_read_new(); 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) { ret = FALSE; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot open: %s", archive_error_string(arch)); goto out; } for (;;) { gboolean valid; r = archive_read_next_header(arch, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { ret = FALSE; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot read header: %s", archive_error_string(arch)); goto out; } /* only extract if valid */ valid = fu_common_extract_archive_entry(entry, dir); if (!valid) continue; r = archive_read_extract(arch, entry, 0); if (r != ARCHIVE_OK) { ret = FALSE; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot extract: %s", archive_error_string(arch)); goto out; } } out: if (arch != NULL) { archive_read_close(arch); archive_read_free(arch); } return ret; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing libarchive support"); return FALSE; #endif } static void fu_common_add_argv(GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3); static void fu_common_add_argv(GPtrArray *argv, const gchar *fmt, ...) { va_list args; g_autofree gchar *tmp = NULL; g_auto(GStrv) split = NULL; va_start(args, fmt); tmp = g_strdup_vprintf(fmt, args); va_end(args); split = g_strsplit(tmp, " ", -1); for (guint i = 0; split[i] != NULL; i++) g_ptr_array_add(argv, g_strdup(split[i])); } /** * fu_common_find_program_in_path: * @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.1.2 **/ gchar * fu_common_find_program_in_path(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; } static gboolean fu_common_test_namespace_support(GError **error) { /* test if CONFIG_USER_NS is valid */ if (!g_file_test("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing CONFIG_USER_NS in kernel"); return FALSE; } if (g_file_test("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) { g_autofree gchar *clone = NULL; if (!g_file_get_contents("/proc/sys/kernel/unprivileged_userns_clone", &clone, NULL, error)) return FALSE; if (g_ascii_strtoll(clone, NULL, 10) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unprivileged user namespace clones disabled by distro"); return FALSE; } } return TRUE; } /** * fu_common_firmware_builder: * @bytes: the data to use * @script_fn: Name of the script to run in the tarball, e.g. `startup.sh` * @output_fn: Name of the generated firmware, e.g. `firmware.bin` * @error: (nullable): optional return location for an error * * Builds a firmware file using tools from the host session in a bubblewrap * jail. Several things happen during build: * * 1. The @bytes data is untarred to a temporary location * 2. A bubblewrap container is set up * 3. The startup.sh script is run inside the container * 4. The firmware.bin is extracted from the container * 5. The temporary location is deleted * * Returns: a new #GBytes, or %NULL for error * * Since: 0.9.7 **/ GBytes * fu_common_firmware_builder(GBytes *bytes, const gchar *script_fn, const gchar *output_fn, GError **error) { gint rc = 0; g_autofree gchar *argv_str = NULL; g_autofree gchar *bwrap_fn = NULL; g_autofree gchar *localstatebuilderdir = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *output2_fn = NULL; g_autofree gchar *standard_error = NULL; g_autofree gchar *standard_output = NULL; g_autofree gchar *tmpdir = NULL; g_autoptr(GBytes) firmware_blob = NULL; g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(script_fn != NULL, NULL); g_return_val_if_fail(output_fn != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find bwrap in the path */ bwrap_fn = fu_common_find_program_in_path("bwrap", error); if (bwrap_fn == NULL) return NULL; /* test if CONFIG_USER_NS is valid */ if (!fu_common_test_namespace_support(error)) return NULL; /* untar file to temp location */ tmpdir = g_dir_make_tmp("fwupd-gen-XXXXXX", error); if (tmpdir == NULL) return NULL; if (!fu_common_extract_archive(bytes, tmpdir, error)) return NULL; /* this is shared with the plugins */ localstatedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); localstatebuilderdir = g_build_filename(localstatedir, "builder", NULL); /* launch bubblewrap and generate firmware */ g_ptr_array_add(argv, g_steal_pointer(&bwrap_fn)); fu_common_add_argv(argv, "--die-with-parent"); fu_common_add_argv(argv, "--ro-bind /usr /usr"); fu_common_add_argv(argv, "--ro-bind /lib /lib"); fu_common_add_argv(argv, "--ro-bind-try /lib64 /lib64"); fu_common_add_argv(argv, "--ro-bind /bin /bin"); fu_common_add_argv(argv, "--ro-bind /sbin /sbin"); fu_common_add_argv(argv, "--dir /tmp"); fu_common_add_argv(argv, "--dir /var"); fu_common_add_argv(argv, "--bind %s /tmp", tmpdir); if (g_file_test(localstatebuilderdir, G_FILE_TEST_EXISTS)) fu_common_add_argv(argv, "--ro-bind %s /boot", localstatebuilderdir); fu_common_add_argv(argv, "--dev /dev"); fu_common_add_argv(argv, "--chdir /tmp"); fu_common_add_argv(argv, "--unshare-all"); fu_common_add_argv(argv, "/tmp/%s", script_fn); g_ptr_array_add(argv, NULL); argv_str = g_strjoinv(" ", (gchar **)argv->pdata); g_debug("running '%s' in %s", argv_str, tmpdir); if (!g_spawn_sync("/tmp", (gchar **)argv->pdata, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, /* child_setup */ &standard_output, &standard_error, &rc, error)) { g_prefix_error(error, "failed to run '%s': ", argv_str); return NULL; } if (standard_output != NULL && standard_output[0] != '\0') g_debug("console output was: %s", standard_output); if (rc != 0) { FwupdError code = FWUPD_ERROR_INTERNAL; if (errno == ENOTTY) code = FWUPD_ERROR_PERMISSION_DENIED; g_set_error(error, FWUPD_ERROR, code, "failed to build firmware: %s", standard_error); return NULL; } /* get generated file */ output2_fn = g_build_filename(tmpdir, output_fn, NULL); firmware_blob = fu_common_get_contents_bytes(output2_fn, error); if (firmware_blob == NULL) return NULL; /* cleanup temp directory */ if (!fu_common_rmtree(tmpdir, error)) return NULL; /* success */ return g_steal_pointer(&firmware_blob); } typedef struct { FuOutputHandler handler_cb; gpointer handler_user_data; GMainLoop *loop; GSource *source; GInputStream *stream; GCancellable *cancellable; guint timeout_id; } FuCommonSpawnHelper; static void fu_common_spawn_create_pollable_source(FuCommonSpawnHelper *helper); static gboolean fu_common_spawn_source_pollable_cb(GObject *stream, gpointer user_data) { FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *)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->handler_user_data); } } /* set up the source for the next read */ fu_common_spawn_create_pollable_source(helper); return G_SOURCE_REMOVE; } static void fu_common_spawn_create_pollable_source(FuCommonSpawnHelper *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_common_spawn_source_pollable_cb, helper, NULL); } static void fu_common_spawn_helper_free(FuCommonSpawnHelper *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(FuCommonSpawnHelper, fu_common_spawn_helper_free) #pragma clang diagnostic pop #ifndef _WIN32 static gboolean fu_common_spawn_timeout_cb(gpointer user_data) { FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *)user_data; g_cancellable_cancel(helper->cancellable); g_main_loop_quit(helper->loop); helper->timeout_id = 0; return G_SOURCE_REMOVE; } static void fu_common_spawn_cancelled_cb(GCancellable *cancellable, FuCommonSpawnHelper *helper) { /* just propagate */ g_cancellable_cancel(helper->cancellable); } #endif /** * fu_common_spawn_sync: * @argv: the argument list to run * @handler_cb: (scope call) (nullable): optional #FuOutputHandler * @handler_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_common_spawn_sync(const gchar *const *argv, FuOutputHandler handler_cb, gpointer handler_user_data, guint timeout_ms, GCancellable *cancellable, GError **error) { g_autoptr(FuCommonSpawnHelper) 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(FuCommonSpawnHelper, 1); helper->handler_cb = handler_cb; helper->handler_user_data = handler_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_common_spawn_cancelled_cb), helper, NULL); } /* allow timeout */ if (timeout_ms > 0) { helper->timeout_id = g_timeout_add(timeout_ms, fu_common_spawn_timeout_cb, helper); } fu_common_spawn_create_pollable_source(helper); g_main_loop_run(helper->loop); g_cancellable_disconnect(cancellable, cancellable_id); if (g_cancellable_set_error_if_cancelled(helper->cancellable, error)) return FALSE; #endif return g_subprocess_wait_check(subprocess, cancellable, error); } /** * fu_common_write_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.0.3 **/ void fu_common_write_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: g_assert_not_reached(); } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_common_write_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.0.3 **/ void fu_common_write_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: g_assert_not_reached(); } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_common_write_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.5.8 **/ void fu_common_write_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: g_assert_not_reached(); } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_common_read_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.0.3 **/ guint16 fu_common_read_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: g_assert_not_reached(); } return val_native; } /** * fu_common_read_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.0.3 **/ guint32 fu_common_read_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: g_assert_not_reached(); } return val_native; } /** * fu_common_read_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.5.8 **/ guint64 fu_common_read_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: g_assert_not_reached(); } return val_native; } /** * fu_common_strtoull: * @str: a string, e.g. `0x1234` * * Converts a string value to an integer. Values are assumed base 10, unless * prefixed with "0x" where they are parsed as base 16. * * Returns: integer value, or 0x0 for error * * Since: 1.1.2 **/ guint64 fu_common_strtoull(const gchar *str) { guint base = 10; if (str == NULL) return 0x0; if (g_str_has_prefix(str, "0x")) { str += 2; base = 16; } return g_ascii_strtoull(str, NULL, base); } /** * fu_common_strtoull_full: * @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.7.3 **/ gboolean fu_common_strtoull_full(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)) { 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_common_strstrip: * @str: a string, e.g. ` test ` * * Removes leading and trailing whitespace from a constant string. * * Returns: newly allocated string * * Since: 1.1.2 **/ gchar * fu_common_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); } static const GError * fu_common_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_common_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_common_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_common_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 * * Since: 1.0.8 **/ GError * fu_common_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_common_error_array_count(errors, FWUPD_ERROR_VERSION_SAME) > 1 && fu_common_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_common_error_array_count(errors, FWUPD_ERROR_VERSION_NEWER) > 1 && fu_common_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_common_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"); } /** * fu_common_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.7.4 **/ static gchar * fu_common_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_common_get_path: * @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.0.8 **/ gchar * fu_common_get_path(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_USER_DATA"); 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/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_USER_DATA"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_SYSCONFDIR, NULL); basedir = fu_common_get_win32_basedir(); if (basedir != NULL) return g_build_filename(basedir, FWUPD_SYSCONFDIR, NULL); return g_strdup(FWUPD_SYSCONFDIR); /* /usr/lib//fwupd-plugins-3 */ case FU_PATH_KIND_PLUGINDIR_PKG: tmp = g_getenv("FWUPD_PLUGINDIR"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_PLUGINDIR, NULL); basedir = fu_common_get_win32_basedir(); if (basedir != NULL) return g_build_filename(basedir, FWUPD_PLUGINDIR, NULL); return g_build_filename(FWUPD_PLUGINDIR, 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_common_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/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_common_get_path(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_common_get_path(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_common_get_path(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_common_get_path(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_common_get_path(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_common_get_path(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_common_get_path(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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename(basedir, "etc", PACKAGE_NAME, NULL); /* /run/lock */ case FU_PATH_KIND_LOCKDIR: return g_strdup("/run/lock"); /* /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_common_get_win32_basedir(); /* this shouldn't happen */ default: g_warning("cannot build path for unknown kind %u", path_kind); } return NULL; } /** * fu_common_string_replace: * @string: the #GString to operate on * @search: the text to search for * @replace: the text to use for substitutions * * Performs multiple search and replace operations on the given string. * * Returns: the number of replacements done, or 0 if @search is not found. * * Since: 1.2.0 **/ guint fu_common_string_replace(GString *string, const gchar *search, const gchar *replace) { gchar *tmp; guint count = 0; gsize search_idx = 0; gsize replace_len; gsize search_len; g_return_val_if_fail(string != NULL, 0); g_return_val_if_fail(search != NULL, 0); g_return_val_if_fail(replace != NULL, 0); /* nothing to do */ if (string->len == 0) return 0; search_len = strlen(search); replace_len = strlen(replace); do { tmp = g_strstr_len(string->str + search_idx, -1, search); if (tmp == NULL) break; /* advance the counter in case @replace contains @search */ search_idx = (gsize)(tmp - string->str); /* reallocate the string if required */ if (search_len > replace_len) { g_string_erase(string, (gssize)search_idx, (gssize)(search_len - replace_len)); memcpy(tmp, replace, replace_len); } else if (search_len < replace_len) { g_string_insert_len(string, (gssize)search_idx, replace, (gssize)(replace_len - search_len)); /* we have to treat this specially as it could have * been reallocated when the insertion happened */ memcpy(string->str + search_idx, replace, replace_len); } else { /* just memcmp in the new string */ memcpy(tmp, replace, replace_len); } search_idx += replace_len; count++; } while (TRUE); return count; } /** * fu_common_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.3.2 **/ gsize fu_common_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_common_string_append_kv: * @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.2.4 */ void fu_common_string_append_kv(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_common_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_common_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.2.4 */ void fu_common_string_append_ku(GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf("%" G_GUINT64_FORMAT, value); fu_common_string_append_kv(str, idt, key, tmp); } /** * fu_common_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.2.4 */ void fu_common_string_append_kx(GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf("0x%x", (guint)value); fu_common_string_append_kv(str, idt, key, tmp); } /** * fu_common_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.2.4 */ void fu_common_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value) { fu_common_string_append_kv(str, idt, key, value ? "true" : "false"); } /** * fu_common_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.2.4 **/ void fu_common_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); /* 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_common_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.2.2 **/ void fu_common_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_common_dump_full(log_domain, title, data, len, 32, flags); } /** * fu_common_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.2.2 **/ void fu_common_dump_bytes(const gchar *log_domain, const gchar *title, GBytes *bytes) { gsize len = 0; const guint8 *data = g_bytes_get_data(bytes, &len); fu_common_dump_raw(log_domain, title, data, len); } /** * fu_common_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.2.4 **/ GBytes * fu_common_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_common_bytes_is_empty: * @bytes: data blob * * Checks if a byte array are just empty (0xff) bytes. * * Returns: %TRUE if @bytes is empty * * Since: 1.2.6 **/ gboolean fu_common_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_common_bytes_compare_raw: * @buf1: a buffer * @bufsz1: sizeof @buf1 * @buf2: another buffer * @bufsz2: sizeof @buf2 * @error: (nullable): optional return location for an error * * Compares the buffers for equality. * * Returns: %TRUE if @buf1 and @buf2 are identical * * Since: 1.3.2 **/ gboolean fu_common_bytes_compare_raw(const guint8 *buf1, gsize bufsz1, const guint8 *buf2, gsize bufsz2, 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); /* not the same length */ if (bufsz1 != bufsz2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got %" G_GSIZE_FORMAT " bytes, expected " "%" G_GSIZE_FORMAT, bufsz1, bufsz2); return FALSE; } /* check matches */ for (guint i = 0x0; i < bufsz1; i++) { if (buf1[i] != buf2[i]) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got 0x%02x, expected 0x%02x @ 0x%04x", buf1[i], buf2[i], i); return FALSE; } } /* success */ return TRUE; } /** * fu_common_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.2.6 **/ gboolean fu_common_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_common_bytes_compare_raw(buf1, bufsz1, buf2, bufsz2, error); } /** * fu_common_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.3.1 **/ GBytes * fu_common_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); 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_common_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.5.4 **/ GBytes * fu_common_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); /* sanity check */ if (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_common_realpath: * @filename: a filename * @error: (nullable): optional return location for an error * * Finds the canonicalized absolute filename for a path. * * Returns: a filename, or %NULL if invalid or not found * * Since: 1.2.6 **/ gchar * fu_common_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", 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); } /** * fu_common_fnmatch: * @pattern: a glob pattern, e.g. `*foo*` * @str: a string to match against the pattern, e.g. `bazfoobar` * * Matches a string against a glob pattern. * * Returns: %TRUE if the string matched * * Since: 1.3.5 **/ gboolean fu_common_fnmatch(const gchar *pattern, const gchar *str) { g_return_val_if_fail(pattern != NULL, FALSE); g_return_val_if_fail(str != NULL, FALSE); return fu_common_fnmatch_impl(pattern, str); } static gint fu_common_filename_glob_sort_cb(gconstpointer a, gconstpointer b) { return g_strcmp0(*(const gchar **)a, *(const gchar **)b); } /** * fu_common_filename_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.5.0 **/ GPtrArray * fu_common_filename_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 (!fu_common_fnmatch(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_common_filename_glob_sort_cb); return g_steal_pointer(&files); } /** * fu_common_strnsplit: * @str: a string to split * @sz: size of @str * @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.3.1 **/ gchar ** fu_common_strnsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) { 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_common_strnsplit_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): a #FuCommonStrsplitFunc. * @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_common_strnsplit() 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.7.0 */ gboolean fu_common_strnsplit_full(const gchar *str, gssize sz, const gchar *delimiter, FuCommonStrsplitFunc 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_common_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.5.5 **/ gchar * fu_common_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_common_strjoin_array: * @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.5.6 **/ gchar * fu_common_strjoin_array(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_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.3.1 **/ 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 (n == 0) return TRUE; if (n > src_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "attempted to read 0x%02x bytes from buffer of 0x%02x", (guint)n, (guint)src_sz); return FALSE; } if (n + src_offset > src_sz) { 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)src_offset, (guint)src_sz); return FALSE; } if (n > dst_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "attempted to write 0x%02x bytes to buffer of 0x%02x", (guint)n, (guint)dst_sz); return FALSE; } if (n + dst_offset > dst_sz) { 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)dst_offset, (guint)dst_sz); return FALSE; } /* phew! */ 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.7.4 **/ 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: 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.5.6 **/ 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_common_read_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.3.3 **/ gboolean fu_common_read_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_common_read_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.3.3 **/ gboolean fu_common_read_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_common_read_uint16(dst, endian); return TRUE; } /** * fu_common_read_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.3.3 **/ gboolean fu_common_read_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_common_read_uint32(dst, endian); return TRUE; } /** * fu_common_read_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.5.8 **/ gboolean fu_common_read_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_common_read_uint64(dst, endian); return TRUE; } /** * fu_common_write_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.5.8 **/ gboolean fu_common_write_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_common_write_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.5.8 **/ gboolean fu_common_write_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_common_write_uint16(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * fu_common_write_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.5.8 **/ gboolean fu_common_write_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_common_write_uint32(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * fu_common_write_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.5.8 **/ gboolean fu_common_write_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_common_write_uint64(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * 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_common_write_uint16(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_common_write_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_common_write_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_full: * @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.6.0 **/ void fu_byte_array_set_size_full(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_set_size: * @array: a #GByteArray * @length: the new size of the GByteArray * * Sets the size of the GByteArray, expanding it with NULs if necessary. * * Since: 1.5.0 **/ void fu_byte_array_set_size(GByteArray *array, guint length) { return fu_byte_array_set_size_full(array, length, 0x0); } /** * 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_full(array, fu_common_align_up(array->len, alignment), data); } /** * fu_common_kernel_locked_down: * * Determines if kernel lockdown in effect * * Since: 1.3.8 **/ gboolean fu_common_kernel_locked_down(void) { #ifdef __linux__ gsize len = 0; g_autofree gchar *dir = fu_common_get_path(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_common_check_kernel_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.6.2 **/ gboolean fu_common_check_kernel_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_common_vercmp_full(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_common_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.5.0 **/ gboolean fu_common_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_common_get_cpu_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.5.5 **/ FuCpuVendor fu_common_get_cpu_vendor(void) { #ifdef HAVE_CPUID_H guint ebx = 0; guint ecx = 0; guint edx = 0; if (fu_common_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 == 0) return FALSE; tokens = fu_common_strnsplit(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(); } const gchar * fu_common_convert_to_gpt_type(const gchar *type) { struct { const gchar *gpt; const gchar *mbrs[4]; } typeguids[] = {{"c12a7328-f81f-11d2-ba4b-00a0c93ec93b", /* esp */ {"0xef", "efi", NULL}}, {"ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", /* fat32 */ {"0x0b", "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(type, typeguids[i].mbrs[j]) == 0) return typeguids[i].gpt; } } return type; } /** * 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_volumes_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.4.6 **/ GPtrArray * fu_common_get_volumes_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_autoptr(FuVolume) vol = NULL; g_autoptr(GDBusProxy) proxy_part = NULL; g_autoptr(GDBusProxy) proxy_fs = NULL; g_autoptr(GVariant) val = NULL; 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; } val = g_dbus_proxy_get_cached_property(proxy_part, "Type"); if (val == NULL) continue; g_variant_get(val, "&s", &type_str); 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); if (proxy_fs == NULL) { g_prefix_error(error, "failed to initialize d-bus proxy %s: ", g_dbus_proxy_get_object_path(proxy_blk)); return NULL; } vol = g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, "proxy-filesystem", proxy_fs, NULL); /* convert reported type to GPT type */ type_str = fu_common_convert_to_gpt_type(type_str); if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *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_common_get_volume_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.5.1 **/ FuVolume * fu_common_get_volume_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(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); return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, "proxy-filesystem", proxy_fs, NULL); } } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes for device %s", device); return NULL; } /** * fu_common_get_volume_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.5.1 **/ FuVolume * fu_common_get_volume_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_common_get_esp_default: * @error: (nullable): optional return location for an error * * Gets the platform default ESP * * Returns: (transfer full): a volume, or %NULL if the ESP was not found * * Since: 1.4.6 **/ FuVolume * fu_common_get_esp_default(GError **error) { const gchar *path_tmp; gboolean has_internal = FALSE; g_autoptr(GPtrArray) volumes_fstab = g_ptr_array_new(); g_autoptr(GPtrArray) volumes_mtab = g_ptr_array_new(); g_autoptr(GPtrArray) volumes_vfat = g_ptr_array_new(); g_autoptr(GPtrArray) volumes = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* for the test suite use local directory for ESP */ path_tmp = g_getenv("FWUPD_UEFI_ESP_PATH"); if (path_tmp != NULL) return fu_volume_new_from_mount_path(path_tmp); volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_ESP, &error_local); if (volumes == NULL) { g_debug("%s, falling back to %s", error_local->message, FU_VOLUME_KIND_BDP); volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_BDP, error); if (volumes == NULL) { g_prefix_error(error, "%s: ", error_local->message); return NULL; } } /* are there _any_ internal vfat partitions? * remember HintSystem is just that -- a hint! */ for (guint i = 0; i < volumes->len; i++) { FuVolume *vol = g_ptr_array_index(volumes, i); g_autofree gchar *type = fu_volume_get_id_type(vol); if (g_strcmp0(type, "vfat") == 0 && fu_volume_is_internal(vol)) { has_internal = TRUE; break; } } /* filter to vfat partitions */ for (guint i = 0; i < volumes->len; i++) { FuVolume *vol = g_ptr_array_index(volumes, i); g_autofree gchar *type = fu_volume_get_id_type(vol); if (type == NULL) continue; if (has_internal && !fu_volume_is_internal(vol)) continue; if (g_strcmp0(type, "vfat") == 0) g_ptr_array_add(volumes_vfat, vol); } if (volumes_vfat->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "No ESP found"); return NULL; } for (guint i = 0; i < volumes_vfat->len; i++) { FuVolume *vol = g_ptr_array_index(volumes_vfat, i); g_ptr_array_add(fu_volume_is_mounted(vol) ? volumes_mtab : volumes_fstab, vol); } if (volumes_mtab->len == 1) { FuVolume *vol = g_ptr_array_index(volumes_mtab, 0); return g_object_ref(vol); } if (volumes_mtab->len == 0 && volumes_fstab->len == 1) { FuVolume *vol = g_ptr_array_index(volumes_fstab, 0); return g_object_ref(vol); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "More than one available ESP"); return NULL; } /** * fu_common_get_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.4.6 **/ FuVolume * fu_common_get_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_common_get_volumes_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_error(error, g_steal_pointer(&error_local)); 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 *vol_basename = g_path_get_basename(fu_volume_get_mount_point(vol)); if (g_strcmp0(basename, vol_basename) == 0) return g_object_ref(vol); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "No ESP with path %s", esp_path); return NULL; } /** * fu_common_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.7.1 **/ guint8 fu_common_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_common_crc8: * @buf: memory buffer * @bufsz: size of @buf * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.5.0 **/ guint8 fu_common_crc8(const guint8 *buf, gsize bufsz) { return fu_common_crc8_full(buf, bufsz, 0x00, 0x07); } /** * fu_common_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.6.2 **/ guint16 fu_common_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_common_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.5.0 **/ guint16 fu_common_crc16(const guint8 *buf, gsize bufsz) { return fu_common_crc16_full(buf, bufsz, 0xFFFF, 0xA001); } /** * fu_common_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.5.0 **/ guint32 fu_common_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_common_crc32: * @buf: memory buffer * @bufsz: size of @buf * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.5.0 **/ guint32 fu_common_crc32(const guint8 *buf, gsize bufsz) { return fu_common_crc32_full(buf, bufsz, 0xFFFFFFFF, 0xEDB88320); } /** * fu_common_sum8: * @buf: memory buffer * @bufsz: size of @buf * * Returns the arithmetic sum of all bytes in @buf. * * Returns: sum value * * Since: 1.7.3 **/ guint8 fu_common_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_common_sum8_bytes: * @blob: a #GBytes * * Returns the arithmetic sum of all bytes in @blob. * * Returns: sum value * * Since: 1.7.3 **/ guint8 fu_common_sum8_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT8); return fu_common_sum8(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_common_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.7.3 **/ guint16 fu_common_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_common_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.7.3 **/ guint16 fu_common_sum16_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT16); return fu_common_sum16(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_common_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.7.3 **/ guint16 fu_common_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_common_read_uint16(&buf[i], endian); return checksum; } /** * fu_common_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.7.3 **/ guint16 fu_common_sum16w_bytes(GBytes *blob, FuEndianType endian) { g_return_val_if_fail(blob != NULL, G_MAXUINT16); return fu_common_sum16w(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), endian); } /** * fu_common_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.7.3 **/ guint32 fu_common_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_common_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.7.3 **/ guint32 fu_common_sum32_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT32); return fu_common_sum32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_common_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.7.3 **/ guint32 fu_common_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_common_read_uint32(&buf[i], endian); return checksum; } /** * fu_common_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.7.3 **/ guint32 fu_common_sum32w_bytes(GBytes *blob, FuEndianType endian) { g_return_val_if_fail(blob != NULL, G_MAXUINT32); return fu_common_sum32w(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), endian); } /** * fu_common_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` * * Since: 1.5.6 **/ gchar * fu_common_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); } /** * 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; guint32 mask = 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_battery_state_to_string: * @battery_state: a battery state, e.g. %FU_BATTERY_STATE_FULLY_CHARGED * * Converts an enumerated type to a string. * * Returns: a string, or %NULL for invalid * * Since: 1.6.0 **/ const gchar * fu_battery_state_to_string(FuBatteryState battery_state) { if (battery_state == FU_BATTERY_STATE_UNKNOWN) return "unknown"; if (battery_state == FU_BATTERY_STATE_CHARGING) return "charging"; if (battery_state == FU_BATTERY_STATE_DISCHARGING) return "discharging"; if (battery_state == FU_BATTERY_STATE_EMPTY) return "empty"; if (battery_state == FU_BATTERY_STATE_FULLY_CHARGED) return "fully-charged"; return NULL; } /** * fu_lid_state_to_string: * @lid_state: a battery 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_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; } /** * 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); } /** * fu_common_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.6.2 **/ gchar * fu_common_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_common_get_path(FU_PATH_KIND_FIRMWARE_SEARCH); if (!g_file_get_contents(sys_fw_search_path, &contents, &sz, error)) return NULL; /* remove newline character */ if (contents != NULL && sz > 0 && 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_common_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.6.2 **/ gboolean fu_common_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_common_get_path(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_common_get_path(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_common_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.6.2 **/ gboolean fu_common_reset_firmware_search_path(GError **error) { const gchar *contents = " "; return fu_common_set_firmware_search_path(contents, error); } fwupd-1.7.5/libfwupdplugin/fu-common.h000066400000000000000000000365531420024370600177670ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-volume.h" /** * FuAppFlags: * @FU_APP_FLAGS_NONE: No flags set * @FU_APP_FLAGS_NO_IDLE_SOURCES: Disallow idle sources * * The flags to use when loading an application. **/ typedef enum { FU_APP_FLAGS_NONE = 0, FU_APP_FLAGS_NO_IDLE_SOURCES = 1 << 0, /*< private >*/ FU_APP_FLAGS_LAST } FuAppFlags; /** * 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; /** * FuEndianType: * * The endian type, e.g. %G_LITTLE_ENDIAN **/ typedef guint FuEndianType; /** * 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_PLUGINDIR_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) * * 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_PLUGINDIR_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, /*< private >*/ FU_PATH_KIND_LAST } FuPathKind; /** * 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; /** * FU_BATTERY_VALUE_INVALID: * * This value signifies the battery level is either unset, or the value cannot * be discovered. */ #define FU_BATTERY_VALUE_INVALID 101 /** * FuBatteryState: * @FU_BATTERY_STATE_UNKNOWN: Unknown * @FU_BATTERY_STATE_CHARGING: Charging * @FU_BATTERY_STATE_DISCHARGING: Discharging * @FU_BATTERY_STATE_EMPTY: Empty * @FU_BATTERY_STATE_FULLY_CHARGED: Fully charged * * The device battery state. **/ typedef enum { FU_BATTERY_STATE_UNKNOWN, FU_BATTERY_STATE_CHARGING, FU_BATTERY_STATE_DISCHARGING, FU_BATTERY_STATE_EMPTY, FU_BATTERY_STATE_FULLY_CHARGED, /*< private >*/ FU_BATTERY_STATE_LAST } FuBatteryState; /** * 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; /** * FuOutputHandler: * @line: text data * @user_data: user data * * The process spawn iteration callback. */ typedef void (*FuOutputHandler)(const gchar *line, gpointer user_data); gboolean fu_common_spawn_sync(const gchar *const *argv, FuOutputHandler handler_cb, gpointer handler_user_data, guint timeout_ms, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar * fu_common_get_path(FuPathKind path_kind); gchar * fu_common_realpath(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fu_common_filename_glob(const gchar *directory, const gchar *pattern, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_fnmatch(const gchar *pattern, const gchar *str); gboolean fu_common_rmtree(const gchar *directory, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fu_common_get_files_recursive(const gchar *path, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_mkdir(const gchar *dirname, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_mkdir_parent(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_set_contents_bytes(const gchar *filename, GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_common_get_contents_bytes(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_common_get_contents_stream(GInputStream *stream, gsize count, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_common_get_contents_fd(gint fd, gsize count, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_extract_archive(GBytes *blob, const gchar *dir, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_common_firmware_builder(GBytes *bytes, const gchar *script_fn, const gchar *output_fn, GError **error) G_GNUC_WARN_UNUSED_RESULT; GError * fu_common_error_array_get_best(GPtrArray *errors); guint64 fu_common_strtoull(const gchar *str); gboolean fu_common_strtoull_full(const gchar *str, guint64 *value, guint64 min, guint64 max, GError **error); gchar * fu_common_find_program_in_path(const gchar *basename, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar * fu_common_strstrip(const gchar *str); void fu_common_dump_raw(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len); void fu_common_dump_full(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len, guint columns, FuDumpFlags flags); void fu_common_dump_bytes(const gchar *log_domain, const gchar *title, GBytes *bytes); GBytes * fu_common_bytes_align(GBytes *bytes, gsize blksz, gchar padval); const guint8 * fu_bytes_get_data_safe(GBytes *bytes, gsize *bufsz, GError **error); gboolean fu_common_bytes_is_empty(GBytes *bytes); gboolean fu_common_bytes_compare(GBytes *bytes1, GBytes *bytes2, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_bytes_compare_raw(const guint8 *buf1, gsize bufsz1, const guint8 *buf2, gsize bufsz2, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_common_bytes_pad(GBytes *bytes, gsize sz); GBytes * fu_common_bytes_new_offset(GBytes *bytes, gsize offset, gsize length, GError **error) G_GNUC_WARN_UNUSED_RESULT; gsize fu_common_strwidth(const gchar *text); guint8 * fu_memdup_safe(const guint8 *src, gsize n, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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; 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; gboolean fu_common_read_uint8_safe(const guint8 *buf, gsize bufsz, gsize offset, guint8 *value, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_read_uint16_safe(const guint8 *buf, gsize bufsz, gsize offset, guint16 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_read_uint32_safe(const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_read_uint64_safe(const guint8 *buf, gsize bufsz, gsize offset, guint64 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_write_uint8_safe(guint8 *buf, gsize bufsz, gsize offset, guint8 value, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_write_uint16_safe(guint8 *buf, gsize bufsz, gsize offset, guint16 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_write_uint32_safe(guint8 *buf, gsize bufsz, gsize offset, guint32 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_write_uint64_safe(guint8 *buf, gsize bufsz, gsize offset, guint64 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_byte_array_set_size(GByteArray *array, guint length); void fu_byte_array_set_size_full(GByteArray *array, guint length, guint8 data); void fu_byte_array_align_up(GByteArray *array, guint8 alignment, guint8 data); void fu_byte_array_append_uint8(GByteArray *array, guint8 data); void fu_byte_array_append_uint16(GByteArray *array, guint16 data, FuEndianType endian); void fu_byte_array_append_uint32(GByteArray *array, guint32 data, FuEndianType endian); void fu_byte_array_append_uint64(GByteArray *array, guint64 data, FuEndianType endian); void fu_byte_array_append_bytes(GByteArray *array, GBytes *bytes); void fu_common_write_uint16(guint8 *buf, guint16 val_native, FuEndianType endian); void fu_common_write_uint32(guint8 *buf, guint32 val_native, FuEndianType endian); void fu_common_write_uint64(guint8 *buf, guint64 val_native, FuEndianType endian); guint16 fu_common_read_uint16(const guint8 *buf, FuEndianType endian); guint32 fu_common_read_uint32(const guint8 *buf, FuEndianType endian); guint64 fu_common_read_uint64(const guint8 *buf, FuEndianType endian); guint fu_common_string_replace(GString *string, const gchar *search, const gchar *replace); void fu_common_string_append_kv(GString *str, guint idt, const gchar *key, const gchar *value); void fu_common_string_append_ku(GString *str, guint idt, const gchar *key, guint64 value); void fu_common_string_append_kx(GString *str, guint idt, const gchar *key, guint64 value); void fu_common_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value); gchar ** fu_common_strnsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens); /** * FuCommonStrsplitFunc: * @token: a #GString * @token_idx: the token number * @user_data: user data * @error: a #GError or NULL * * The fu_common_strnsplit_full() iteration callback. */ typedef gboolean (*FuCommonStrsplitFunc)(GString *token, guint token_idx, gpointer user_data, GError **error); gboolean fu_common_strnsplit_full(const gchar *str, gssize sz, const gchar *delimiter, FuCommonStrsplitFunc callback, gpointer user_data, GError **error); gchar * fu_common_strsafe(const gchar *str, gsize maxsz); gchar * fu_common_strjoin_array(const gchar *separator, GPtrArray *array); gboolean fu_common_kernel_locked_down(void); gboolean fu_common_check_kernel_version(const gchar *minimum_kernel, GError **error); gboolean fu_common_cpuid(guint32 leaf, guint32 *eax, guint32 *ebx, guint32 *ecx, guint32 *edx, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuCpuVendor fu_common_get_cpu_vendor(void); gboolean fu_common_is_live_media(void); guint64 fu_common_get_memory_size(void); GPtrArray * fu_common_get_volumes_by_kind(const gchar *kind, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuVolume * fu_common_get_volume_by_device(const gchar *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuVolume * fu_common_get_volume_by_devnum(guint32 devnum, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuVolume * fu_common_get_esp_for_path(const gchar *esp_path, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuVolume * fu_common_get_esp_default(GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_common_check_full_disk_encryption(GError **error); guint8 fu_common_crc8(const guint8 *buf, gsize bufsz); guint8 fu_common_crc8_full(const guint8 *buf, gsize bufsz, guint8 crc_init, guint8 polynomial); guint16 fu_common_crc16(const guint8 *buf, gsize bufsz); guint16 fu_common_crc16_full(const guint8 *buf, gsize bufsz, guint16 crc, guint16 polynomial); guint32 fu_common_crc32(const guint8 *buf, gsize bufsz); guint32 fu_common_crc32_full(const guint8 *buf, gsize bufsz, guint32 crc, guint32 polynomial); guint8 fu_common_sum8(const guint8 *buf, gsize bufsz); guint8 fu_common_sum8_bytes(GBytes *blob); guint16 fu_common_sum16(const guint8 *buf, gsize bufsz); guint16 fu_common_sum16_bytes(GBytes *blob); guint16 fu_common_sum16w(const guint8 *buf, gsize bufsz, FuEndianType endian); guint16 fu_common_sum16w_bytes(GBytes *blob, FuEndianType endian); guint32 fu_common_sum32(const guint8 *buf, gsize bufsz); guint32 fu_common_sum32_bytes(GBytes *blob); guint32 fu_common_sum32w(const guint8 *buf, gsize bufsz, FuEndianType endian); guint32 fu_common_sum32w_bytes(GBytes *blob, FuEndianType endian); gchar * fu_common_uri_get_scheme(const gchar *uri); gsize fu_common_align_up(gsize value, guint8 alignment); gchar * fu_common_get_firmware_search_path(GError **error); gboolean fu_common_set_firmware_search_path(const gchar *path, GError **error); gboolean fu_common_reset_firmware_search_path(GError **error); const gchar * fu_battery_state_to_string(FuBatteryState battery_state); const gchar * fu_lid_state_to_string(FuLidState lid_state); void fu_xmlb_builder_insert_kv(XbBuilderNode *bn, const gchar *key, const gchar *value); void fu_xmlb_builder_insert_kx(XbBuilderNode *bn, const gchar *key, guint64 value); void fu_xmlb_builder_insert_kb(XbBuilderNode *bn, const gchar *key, gboolean value); fwupd-1.7.5/libfwupdplugin/fu-context-private.h000066400000000000000000000016231420024370600216210ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-context.h" #include "fu-hwids.h" #include "fu-quirks.h" FuContext * fu_context_new(void); gboolean fu_context_load_hwinfo(FuContext *self, GError **error); gboolean fu_context_load_quirks(FuContext *self, FuQuirksLoadFlags flags, GError **error); void fu_context_set_runtime_versions(FuContext *self, GHashTable *runtime_versions); void fu_context_set_compile_versions(FuContext *self, GHashTable *compile_versions); void fu_context_add_firmware_gtype(FuContext *self, const gchar *id, GType gtype); GPtrArray * fu_context_get_firmware_gtype_ids(FuContext *self); GType fu_context_get_firmware_gtype_by_id(FuContext *self, const gchar *id); void fu_context_add_udev_subsystem(FuContext *self, const gchar *subsystem); GPtrArray * fu_context_get_udev_subsystems(FuContext *self); fwupd-1.7.5/libfwupdplugin/fu-context.c000066400000000000000000000601471420024370600201520ustar00rootroot00000000000000/* * 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.h" #include "fu-smbios-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 { FuHwids *hwids; FuSmbios *smbios; FuQuirks *quirks; GHashTable *runtime_versions; GHashTable *compile_versions; GPtrArray *udev_subsystems; GHashTable *firmware_gtypes; GHashTable *hwid_flags; /* str: */ FuBatteryState battery_state; FuLidState lid_state; guint battery_level; guint battery_threshold; } FuContextPrivate; enum { SIGNAL_SECURITY_CHANGED, SIGNAL_LAST }; enum { PROP_0, PROP_BATTERY_STATE, PROP_LID_STATE, PROP_BATTERY_LEVEL, PROP_BATTERY_THRESHOLD, 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)) /** * 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 * * 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) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return fu_smbios_get_string(priv->smbios, structure_type, offset, NULL); } /** * fu_context_get_smbios_data: * @self: a #FuContext * @structure_type: a SMBIOS structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * * Gets a hardware SMBIOS data. * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.6.0 **/ GBytes * fu_context_get_smbios_data(FuContext *self, guint8 structure_type) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return fu_smbios_get_data(priv->smbios, structure_type, NULL); } /** * fu_context_get_smbios_integer: * @self: a #FuContext * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: a structure offset * * 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) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), G_MAXUINT); return fu_smbios_get_integer(priv->smbios, type, offset, NULL); } /** * 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); 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); 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); 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); 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_set_runtime_versions: * @self: a #FuContext * @runtime_versions: (element-type utf8 utf8): dictionary of versions * * Sets the runtime versions for a plugin. * * Since: 1.6.0 **/ void fu_context_set_runtime_versions(FuContext *self, GHashTable *runtime_versions) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(runtime_versions != NULL); priv->runtime_versions = g_hash_table_ref(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_set_compile_versions: * @self: a #FuContext * @compile_versions: (element-type utf8 utf8): dictionary of versions * * Sets the compile time versions for a plugin. * * Since: 1.6.0 **/ void fu_context_set_compile_versions(FuContext *self, GHashTable *compile_versions) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(compile_versions != NULL); priv->compile_versions = g_hash_table_ref(compile_versions); } /** * fu_context_add_udev_subsystem: * @self: a #FuContext * @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.0 **/ void fu_context_add_udev_subsystem(FuContext *self, const gchar *subsystem) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(subsystem != NULL); for (guint i = 0; i < priv->udev_subsystems->len; i++) { const gchar *subsystem_tmp = g_ptr_array_index(priv->udev_subsystems, i); if (g_strcmp0(subsystem_tmp, subsystem) == 0) return; } g_debug("added udev subsystem watch of %s", subsystem); g_ptr_array_add(priv->udev_subsystems, g_strdup(subsystem)); } /** * fu_context_get_udev_subsystems: * @self: a #FuContext * * Gets the udev subsystems required by all plugins. * * Returns: (transfer none) (element-type utf8): List of subsystems * * Since: 1.6.0 **/ GPtrArray * fu_context_get_udev_subsystems(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->udev_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 none) (element-type utf8): List of subsystems * * 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_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); } /** * fu_context_lookup_quirk_by_id_iter: * @self: a #FuContext * @guid: GUID to lookup * @iter_cb: (scope async): 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, FuContextLookupIter iter_cb, gpointer user_data) { FuContextPrivate *priv = GET_PRIVATE(self); 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, (FuQuirksIter)iter_cb, user_data); } /** * 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); } /** * fu_context_load_hwinfo: * @self: a #FuContext * @error: (nullable): optional return location for an error * * Loads all hardware information parts of the context. * * Returns: %TRUE for success * * Since: 1.6.0 **/ gboolean fu_context_load_hwinfo(FuContext *self, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); GPtrArray *guids; g_autoptr(GError) error_smbios = NULL; g_autoptr(GError) error_hwids = NULL; g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_smbios_setup(priv->smbios, &error_smbios)) g_warning("Failed to load SMBIOS: %s", error_smbios->message); if (!fu_hwids_setup(priv->hwids, priv->smbios, &error_hwids)) g_warning("Failed to load HWIDs: %s", error_hwids->message); /* 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); const gchar *value; /* does prefixed quirk exist */ value = fu_context_lookup_quirk_by_id(self, guid, FU_QUIRKS_FLAGS); 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])); } } /* 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_battery_state: * @self: a #FuContext * * Gets if the system is on battery power, e.g. UPS or laptop battery. * * Returns: a battery state, e.g. %FU_BATTERY_STATE_DISCHARGING * * Since: 1.6.0 **/ FuBatteryState fu_context_get_battery_state(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return priv->battery_state; } /** * fu_context_set_battery_state: * @self: a #FuContext * @battery_state: a battery state, e.g. %FU_BATTERY_STATE_DISCHARGING * * Sets if the system is on battery power, e.g. UPS or laptop battery. * * Since: 1.6.0 **/ void fu_context_set_battery_state(FuContext *self, FuBatteryState battery_state) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); if (priv->battery_state == battery_state) return; priv->battery_state = battery_state; g_debug("battery state now %s", fu_battery_state_to_string(battery_state)); g_object_notify(G_OBJECT(self), "battery-state"); } /** * fu_context_get_lid_state: * @self: a #FuContext * * Gets the laptop lid state, if applicable. * * Returns: a battery 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 battery 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_debug("lid state now %s", fu_lid_state_to_string(lid_state)); g_object_notify(G_OBJECT(self), "lid-state"); } /** * fu_context_get_battery_level: * @self: a #FuContext * * Gets the system battery level in percent. * * Returns: percentage value, or %FU_BATTERY_VALUE_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 <= FU_BATTERY_VALUE_INVALID); if (priv->battery_level == battery_level) return; priv->battery_level = battery_level; g_debug("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 %FU_BATTERY_VALUE_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 <= FU_BATTERY_VALUE_INVALID); if (priv->battery_threshold == battery_threshold) return; priv->battery_threshold = battery_threshold; g_debug("battery threshold now %u", battery_threshold); g_object_notify(G_OBJECT(self), "battery-threshold"); } 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_BATTERY_STATE: g_value_set_uint(value, priv->battery_state); break; case PROP_LID_STATE: g_value_set_uint(value, priv->lid_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 fu_context_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuContext *self = FU_CONTEXT(object); switch (prop_id) { case PROP_BATTERY_STATE: fu_context_set_battery_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_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; 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->runtime_versions != NULL) g_hash_table_unref(priv->runtime_versions); if (priv->compile_versions != NULL) g_hash_table_unref(priv->compile_versions); g_object_unref(priv->hwids); g_hash_table_unref(priv->hwid_flags); g_object_unref(priv->quirks); g_object_unref(priv->smbios); g_hash_table_unref(priv->firmware_gtypes); g_ptr_array_unref(priv->udev_subsystems); 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:battery-state: * * The system battery state. * * Since: 1.6.0 */ pspec = g_param_spec_uint("battery-state", NULL, NULL, FU_BATTERY_STATE_UNKNOWN, FU_BATTERY_STATE_LAST, FU_BATTERY_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_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:battery-level: * * The system battery level in percent. * * Since: 1.6.0 */ pspec = g_param_spec_uint("battery-level", NULL, NULL, 0, FU_BATTERY_VALUE_INVALID, FU_BATTERY_VALUE_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, FU_BATTERY_VALUE_INVALID, FU_BATTERY_VALUE_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_THRESHOLD, 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->battery_level = FU_BATTERY_VALUE_INVALID; priv->battery_threshold = FU_BATTERY_VALUE_INVALID; priv->smbios = fu_smbios_new(); priv->hwids = fu_hwids_new(); priv->hwid_flags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); priv->udev_subsystems = g_ptr_array_new_with_free_func(g_free); priv->firmware_gtypes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); priv->quirks = fu_quirks_new(); } /** * 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.7.5/libfwupdplugin/fu-context.h000066400000000000000000000047161420024370600201570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-common.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); /*< private >*/ gpointer padding[30]; }; /** * 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); const gchar * fu_context_get_smbios_string(FuContext *self, guint8 structure_type, guint8 offset); guint fu_context_get_smbios_integer(FuContext *self, guint8 type, guint8 offset); GBytes * fu_context_get_smbios_data(FuContext *self, guint8 structure_type); gboolean fu_context_has_hwid_guid(FuContext *self, const gchar *guid); GPtrArray * fu_context_get_hwid_guids(FuContext *self); gboolean fu_context_has_hwid_flag(FuContext *self, const gchar *flag); const gchar * fu_context_get_hwid_value(FuContext *self, const gchar *key); gchar * fu_context_get_hwid_replace_value(FuContext *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_context_add_runtime_version(FuContext *self, const gchar *component_id, const gchar *version); void fu_context_add_compile_version(FuContext *self, const gchar *component_id, const gchar *version); const gchar * fu_context_lookup_quirk_by_id(FuContext *self, const gchar *guid, const gchar *key); gboolean fu_context_lookup_quirk_by_id_iter(FuContext *self, const gchar *guid, FuContextLookupIter iter_cb, gpointer user_data); void fu_context_add_quirk_key(FuContext *self, const gchar *key); void fu_context_security_changed(FuContext *self); FuBatteryState fu_context_get_battery_state(FuContext *self); void fu_context_set_battery_state(FuContext *self, FuBatteryState battery_state); FuLidState fu_context_get_lid_state(FuContext *self); void fu_context_set_lid_state(FuContext *self, FuLidState lid_state); guint fu_context_get_battery_level(FuContext *self); void fu_context_set_battery_level(FuContext *self, guint battery_level); guint fu_context_get_battery_threshold(FuContext *self); void fu_context_set_battery_threshold(FuContext *self, guint battery_threshold); fwupd-1.7.5/libfwupdplugin/fu-deprecated.h000066400000000000000000000002541420024370600205640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once G_BEGIN_DECLS /* indeed, nothing */ G_END_DECLS fwupd-1.7.5/libfwupdplugin/fu-device-locker.c000066400000000000000000000132051420024370600211730ustar00rootroot00000000000000/* * 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; } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } #else g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; #endif } 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.7.5/libfwupdplugin/fu-device-locker.h000066400000000000000000000015301420024370600211760ustar00rootroot00000000000000/* * 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; FuDeviceLocker * fu_device_locker_new_full(gpointer device, FuDeviceLockerFunc open_func, FuDeviceLockerFunc close_func, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_locker_close(FuDeviceLocker *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-device-metadata.h000066400000000000000000000024471420024370600215070ustar00rootroot00000000000000/* * 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" fwupd-1.7.5/libfwupdplugin/fu-device-private.h000066400000000000000000000032651420024370600214000ustar00rootroot00000000000000/* * 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); gboolean fu_device_has_parent_guid(FuDevice *self, const gchar *guid); GPtrArray * fu_device_get_parent_physical_ids(FuDevice *self); gboolean fu_device_has_parent_physical_id(FuDevice *self, const gchar *physical_id); void fu_device_set_parent(FuDevice *self, FuDevice *parent); gint fu_device_get_order(FuDevice *self); void fu_device_set_order(FuDevice *self, gint order); void fu_device_set_alternate(FuDevice *self, FuDevice *alternate); gboolean fu_device_ensure_id(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_device_incorporate_from_component(FuDevice *device, XbNode *component); void fu_device_convert_instance_ids(FuDevice *self); gchar * fu_device_get_guids_as_str(FuDevice *self); GPtrArray * fu_device_get_possible_plugins(FuDevice *self); void fu_device_add_possible_plugin(FuDevice *self, const gchar *plugin); guint fu_device_get_request_cnt(FuDevice *self, FwupdRequestKind request_kind); guint64 fu_device_get_private_flags(FuDevice *self); void fu_device_set_private_flags(FuDevice *self, guint64 flag); void fu_device_set_progress(FuDevice *self, FuProgress *progress); FuDeviceInternalFlags fu_device_get_internal_flags(FuDevice *self); void fu_device_set_internal_flags(FuDevice *self, FuDeviceInternalFlags flags); fwupd-1.7.5/libfwupdplugin/fu-device.c000066400000000000000000004110221420024370600177150ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDevice" #include "config.h" #include #include #include #include "fwupd-common.h" #include "fwupd-device-private.h" #include "fu-common-version.h" #include "fu-common.h" #include "fu-device-private.h" #include "fu-mutex.h" #include "fu-quirks.h" #define FU_DEVICE_RETRY_OPEN_COUNT 5 #define FU_DEVICE_RETRY_OPEN_DELAY 500 /* ms */ #define FU_DEVICE_DEFAULT_BATTERY_THRESHOLD 10 /* % */ /** * 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); typedef struct { gchar *alternate_id; gchar *equivalent_id; gchar *physical_id; gchar *logical_id; gchar *backend_id; gchar *proxy_guid; FuDevice *alternate; FuDevice *proxy; /* noref */ FuContext *ctx; GHashTable *inhibits; /* (nullable) */ GHashTable *metadata; /* (nullable) */ GRWLock metadata_mutex; GPtrArray *parent_guids; GRWLock parent_guids_mutex; GPtrArray *parent_physical_ids; /* (nullable) */ guint remove_delay; /* ms */ guint battery_level; guint battery_threshold; guint request_cnts[FWUPD_REQUEST_KIND_LAST]; gint order; guint priority; guint poll_id; gboolean done_probe; gboolean done_setup; gboolean device_id_valid; guint64 size_min; guint64 size_max; gint open_refcount; /* atomic */ GType specialized_gtype; GType firmware_gtype; GPtrArray *possible_plugins; 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; } FuDevicePrivate; typedef struct { GQuark domain; gint code; FuDeviceRetryFunc recovery_func; } FuDeviceRetryRecovery; typedef struct { gchar *inhibit_id; gchar *reason; } FuDeviceInhibit; enum { PROP_0, PROP_BATTERY_LEVEL, PROP_BATTERY_THRESHOLD, PROP_PHYSICAL_ID, PROP_LOGICAL_ID, PROP_BACKEND_ID, PROP_CONTEXT, PROP_PROXY, PROP_PARENT, 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 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_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_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; 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_BATTERY_LEVEL: fu_device_set_battery_level(self, g_value_get_uint(value)); break; case PROP_BATTERY_THRESHOLD: fu_device_set_battery_threshold(self, g_value_get_uint(value)); break; 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; 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_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"; 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-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; 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)); priv->internal_flags |= flag; } /** * 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)); priv->internal_flags &= ~flag; } /** * 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; } /** * 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)); priv->private_flags |= flag; } /** * 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)); priv->private_flags &= ~flag; } /** * 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); 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); 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; } /** * 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 tyically 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 GLIB_CHECK_VERSION(2, 54, 3) if (g_ptr_array_find_with_equal_func(priv->possible_plugins, plugin, g_str_equal, NULL)) return; #endif 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): 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 && delay > 0) g_usleep(delay * 1000); /* 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_debug("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_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "device recovery not possible"); return FALSE; } } } } /* success */ return TRUE; } /** * fu_device_retry: * @self: a #FuDevice * @func: (scope async): 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_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; 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_debug("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"); } /** * 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. * * 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)); 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. * * 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(child, 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 %u as child is greater than %u", remove_delay, priv->remove_delay); priv->remove_delay = remove_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 (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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->parent_guids_mutex); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(locker != NULL, 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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->parent_guids_mutex); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(locker != 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_autoptr(GRWLockWriterLocker) locker = NULL; 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; locker = g_rw_lock_writer_locker_new(&priv->parent_guids_mutex); g_return_if_fail(locker != NULL); 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)); } 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_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; if (g_strcmp0(key, FU_QUIRKS_PLUGIN) == 0) { fu_device_add_possible_plugin(self, value); 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) { fu_device_add_vendor_id(self, value); 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_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) { fu_device_add_icon(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_GUID) == 0) { fu_device_add_guid(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_COUNTERPART_GUID) == 0) { fu_device_add_counterpart_guid(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PARENT_GUID) == 0) { fu_device_add_parent_guid(self, value); 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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(value, &tmp, 0, G_MAXUINT, error)) return FALSE; fu_device_set_remove_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) { if (value != NULL) fu_device_inhibit(self, "quirk", value); else fu_device_uninhibit(self, "quirk"); 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); 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); 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_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, 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_ONLY_QUIRKS) == 0) fwupd_device_add_guid(FWUPD_DEVICE(self), guid); if ((flags & FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS) == 0) 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); } /** * 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_NO_QUIRKS) == 0) fu_device_add_guid_quirks(self, guid); if ((flags & FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS) == 0) fwupd_device_add_instance_id(FWUPD_DEVICE(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_NONE); } /** * 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_NONE); } /** * 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_guids_as_str: * @self: a #FuDevice * * Gets the device GUIDs as a joined string, which may be useful for error * messages. * * Returns: a string, which may be empty length but not %NULL * * Since: 1.0.8 **/ gchar * fu_device_get_guids_as_str(FuDevice *self) { GPtrArray *guids; g_autofree gchar **tmp = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); guids = fu_device_get_guids(self); tmp = g_new0(gchar *, guids->len + 1); for (guint i = 0; i < guids->len; i++) tmp[i] = g_ptr_array_index(guids, i); return g_strjoinv(",", tmp); } /** * 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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(locker != 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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(locker != 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: a string 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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex); g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT); g_return_val_if_fail(key != NULL, G_MAXUINT); g_return_val_if_fail(locker != 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_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->metadata_mutex); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_return_if_fail(locker != 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_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->metadata_mutex); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_return_if_fail(locker != 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_common_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) { fwupd_device_set_vendor(FWUPD_DEVICE(self), vendor); 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); fu_common_string_replace(new, "(TM)", "™"); fu_common_string_replace(new, "(R)", ""); 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 (g_strcmp0(value_safe, fu_device_get_name(self)) == 0) { const gchar *id = fu_device_get_id(self); g_debug("%s device overwriting same name value: %s", id != NULL ? id : "unknown", value_safe); 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) { /* 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); } /** * 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_common_version_ensure_semver(version); 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_common_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_common_version_ensure_semver(version); 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_common_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_common_version_ensure_semver(version); 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_common_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); } } 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); 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 update error */ for (GList *l = values; l != NULL; l = l->next) { FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data; g_ptr_array_add(reasons, inhibit->reason); } reasons_str = fu_common_strjoin_array(", ", 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); } /* enable */ if (priv->notify_flags_handler_id != 0) g_signal_handler_unblock(self, priv->notify_flags_handler_id); } /** * 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) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceInhibit *inhibit; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(inhibit_id != NULL); /* 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); } /* already exists */ inhibit = g_hash_table_lookup(priv->inhibits, inhibit_id); if (inhibit != NULL) return; /* create new */ inhibit = g_new0(FuDeviceInhibit, 1); inhibit->inhibit_id = g_strdup(inhibit_id); inhibit->reason = g_strdup(reason); 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_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_proxy_guid: * @self: a #FuDevice * * Gets the proxy GUID device, which 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"); } /** * 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); /* 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"); } typedef struct { guint64 value; gchar *value_str; } FuDevicePrivateFlagItem; 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. * * 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); /* 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); 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; FuDevicePrivate *priv = GET_PRIVATE(self); /* 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) { priv->private_flags &= ~item->value; return; } g_debug("no registered custom flag %s on %s", hint + 1, G_OBJECT_TYPE_NAME(self)); 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) { priv->private_flags |= item->value; return; } g_debug("no registered custom flag %s on %s", hint, G_OBJECT_TYPE_NAME(self)); } /** * 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 delay value * * 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_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) { FuDevicePrivate *priv = GET_PRIVATE(self); if (priv->battery_level == FU_BATTERY_VALUE_INVALID || priv->battery_level >= fu_device_get_battery_threshold(self)) { fu_device_uninhibit(self, "battery"); return; } fu_device_inhibit(self, "battery", "Battery level is 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) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FU_BATTERY_VALUE_INVALID); /* use the parent if the child is unset */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) && priv->battery_level == FU_BATTERY_VALUE_INVALID) { FuDevice *parent = fu_device_get_parent(self); if (parent != NULL) return fu_device_get_battery_level(parent); } return priv->battery_level; } /** * fu_device_set_battery_level: * @self: a #FuDevice * @battery_level: the percentage value * * Sets the battery level, or %FU_BATTERY_VALUE_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) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(battery_level <= FU_BATTERY_VALUE_INVALID); if (priv->battery_level == battery_level) return; priv->battery_level = battery_level; g_object_notify(G_OBJECT(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) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FU_BATTERY_VALUE_INVALID); /* use the parent if the child is unset */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) && priv->battery_threshold == FU_BATTERY_VALUE_INVALID) { FuDevice *parent = fu_device_get_parent(self); if (parent != NULL) return fu_device_get_battery_threshold(parent); } /* default value */ if (priv->battery_threshold == FU_BATTERY_VALUE_INVALID) return FU_DEVICE_DEFAULT_BATTERY_THRESHOLD; return priv->battery_threshold; } /** * fu_device_set_battery_threshold: * @self: a #FuDevice * @battery_threshold: the percentage value * * Sets the battery level, or %FU_BATTERY_VALUE_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) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(battery_threshold <= FU_BATTERY_VALUE_INVALID); if (priv->battery_threshold == battery_threshold) return; priv->battery_threshold = battery_threshold; g_object_notify(G_OBJECT(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; g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex); g_return_if_fail(locker != NULL); tmp = fwupd_device_to_string(FWUPD_DEVICE(self)); if (tmp != NULL && tmp[0] != '\0') g_string_append(str, tmp); if (priv->alternate_id != NULL) fu_common_string_append_kv(str, idt + 1, "AlternateId", priv->alternate_id); if (priv->equivalent_id != NULL) fu_common_string_append_kv(str, idt + 1, "EquivalentId", priv->equivalent_id); if (priv->physical_id != NULL) fu_common_string_append_kv(str, idt + 1, "PhysicalId", priv->physical_id); if (priv->logical_id != NULL) fu_common_string_append_kv(str, idt + 1, "LogicalId", priv->logical_id); if (priv->backend_id != NULL) fu_common_string_append_kv(str, idt + 1, "BackendId", priv->backend_id); if (priv->proxy != NULL) fu_common_string_append_kv(str, idt + 1, "ProxyId", fu_device_get_id(priv->proxy)); if (priv->proxy_guid != NULL) fu_common_string_append_kv(str, idt + 1, "ProxyGuid", priv->proxy_guid); if (priv->remove_delay != 0) fu_common_string_append_ku(str, idt + 1, "RemoveDelay", priv->remove_delay); if (priv->custom_flags != NULL) fu_common_string_append_kv(str, idt + 1, "CustomFlags", priv->custom_flags); if (priv->battery_level != FU_BATTERY_VALUE_INVALID) fu_common_string_append_ku(str, idt + 1, "BatteryLevel", priv->battery_level); if (priv->battery_threshold != FU_BATTERY_VALUE_INVALID) fu_common_string_append_ku(str, idt + 1, "BatteryThreshold", priv->battery_threshold); if (priv->size_min > 0) { g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_min); fu_common_string_append_kv(str, idt + 1, "FirmwareSizeMin", sz); } if (priv->size_max > 0) { g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_max); fu_common_string_append_kv(str, idt + 1, "FirmwareSizeMax", sz); } if (priv->order != G_MAXINT) { g_autofree gchar *order = g_strdup_printf("%i", priv->order); fu_common_string_append_kv(str, idt + 1, "Order", order); } if (priv->priority > 0) fu_common_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_common_string_append_kv(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_common_string_append_kv(str, idt + 1, "PossiblePlugin", name); } if (priv->parent_physical_ids != NULL && priv->parent_physical_ids->len > 0) { g_autofree gchar *flags = fu_common_strjoin_array(",", priv->parent_physical_ids); fu_common_string_append_kv(str, idt + 1, "ParentPhysicalIds", 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_common_string_append_kv(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_common_strjoin_array(",", tmpv); fu_common_string_append_kv(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_common_string_append_kv(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)); 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_release_default: * @self: a #FuDevice * * Gets the default release for the device, creating one if not found. * * Returns: (transfer none): the #FwupdRelease object * * Since: 1.0.5 **/ FwupdRelease * fu_device_get_release_default(FuDevice *self) { g_autoptr(FwupdRelease) rel = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); if (fwupd_device_get_release_default(FWUPD_DEVICE(self)) != NULL) return fwupd_device_get_release_default(FWUPD_DEVICE(self)); rel = fwupd_release_new(); fwupd_device_add_release(FWUPD_DEVICE(self), rel); return rel; } /** * 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, "not supported"); 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, "not supported"); 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_debug("installing onto %s:\n%s", fu_device_get_id(self), str); /* call vfunc */ 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) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_POST); 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)); fu_device_emit_request(self, request); } /* 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 %04x bytes larger than the allowed " "maximum size of %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); 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, "not supported"); return NULL; } /* call vfunc */ 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); 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, "not supported"); return NULL; } /* proxy */ 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); 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 */ 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); 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 */ 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 * @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, FwupdInstallFlags flags, 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->prepare == NULL) return TRUE; /* call vfunc */ return klass->prepare(self, flags, error); } /** * fu_device_cleanup: * @self: a #FuDevice * @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, FwupdInstallFlags flags, 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->cleanup == NULL) return TRUE; /* call vfunc */ return klass->cleanup(self, 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); } 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); } 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; /* subclassed */ if (klass->probe != NULL) { if (!klass->probe(self, error)) return FALSE; } priv->done_probe = TRUE; return TRUE; } /** * 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)); 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; } /* 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); 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) { 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); g_return_if_fail(FU_IS_DEVICE(self)); priv->done_probe = FALSE; priv->done_setup = FALSE; } /** * 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, "not supported"); 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, "not supported"); return FALSE; } /* subclass */ return klass->unbind_driver(self, error); } /** * 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); 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 */ if (priv->alternate_id == NULL) fu_device_set_alternate_id(self, fu_device_get_alternate_id(donor)); if (priv->equivalent_id == 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->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)); g_rw_lock_reader_lock(&priv_donor->parent_guids_mutex); for (guint i = 0; i < parent_guids->len; i++) fu_device_add_parent_guid(self, g_ptr_array_index(parent_guids, i)); g_rw_lock_reader_unlock(&priv_donor->parent_guids_mutex); 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); } } g_rw_lock_reader_lock(&priv_donor->metadata_mutex); if (priv->metadata != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(priv_donor->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; if (g_hash_table_lookup(priv->metadata, key) == NULL) { const gchar *value = g_hash_table_lookup(priv_donor->metadata, key); fu_device_set_metadata(self, key, value); } } } g_rw_lock_reader_unlock(&priv_donor->metadata_mutex); /* now the base class, where all the interesting bits are */ fwupd_device_incorporate(FWUPD_DEVICE(self), FWUPD_DEVICE(donor)); /* 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_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): * @device: 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); } /** * fu_device_emit_request: * @self: a device * @request: a request * * Emit a request from a plugin to the client. * * Since: 1.6.2 **/ void fu_device_emit_request(FuDevice *self, FwupdRequest *request) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FWUPD_IS_REQUEST(request)); /* sanity check */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_UNKNOWN) { g_critical("a request must have an assigned kind"); return; } if (fwupd_request_get_id(request) == NULL) { g_critical("a request must have an assigned ID"); return; } /* 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 */ g_signal_emit(self, signals[SIGNAL_REQUEST], 0, request); priv->request_cnts[fwupd_request_get_kind(request)]++; } 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); } 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:battery-level: * * The device battery level in percent. * * Since: 1.5.8 */ pspec = g_param_spec_uint("battery-level", NULL, NULL, 0, FU_BATTERY_VALUE_INVALID, FU_BATTERY_VALUE_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_LEVEL, pspec); /** * FuDevice:battery-threshold: * * The device battery threshold in percent. * * Since: 1.5.8 */ pspec = g_param_spec_uint("battery-threshold", NULL, NULL, 0, FU_BATTERY_VALUE_INVALID, FU_BATTERY_VALUE_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_THRESHOLD, 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); } static void fu_device_init(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); priv->order = G_MAXINT; priv->battery_level = FU_BATTERY_VALUE_INVALID; priv->battery_threshold = FU_BATTERY_VALUE_INVALID; 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->retry_recs = g_ptr_array_new_with_free_func(g_free); g_rw_lock_init(&priv->parent_guids_mutex); g_rw_lock_init(&priv->metadata_mutex); 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); g_rw_lock_clear(&priv->metadata_mutex); g_rw_lock_clear(&priv->parent_guids_mutex); if (priv->alternate != NULL) g_object_unref(priv->alternate); if (priv->proxy != NULL) 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->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->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->proxy_guid); g_free(priv->custom_flags); G_OBJECT_CLASS(fu_device_parent_class)->finalize(object); } /** * fu_device_new: * * Creates a new #Fudevice * * Since: 0.1.0 **/ FuDevice * fu_device_new(void) { return fu_device_new_with_context(NULL); } /** * fu_device_new_with_context: * * Creates a new #Fudevice * * Since: 1.6.2 **/ FuDevice * fu_device_new_with_context(FuContext *ctx) { FuDevice *self = g_object_new(FU_TYPE_DEVICE, "context", ctx, NULL); return FU_DEVICE(self); } fwupd-1.7.5/libfwupdplugin/fu-device.h000066400000000000000000000557521420024370600177400ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-common-version.h" #include "fu-context.h" #include "fu-firmware.h" #include "fu-progress.h" #include "fu-security-attrs.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); 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, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*cleanup)(FuDevice *self, 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); /*< private >*/ gpointer padding[16]; #endif }; /** * FuDeviceInstanceFlags: * @FU_DEVICE_INSTANCE_FLAG_NONE: No flags set * @FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS: Only use instance ID for quirk matching * @FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS: Do no quirk matching * * The flags to use when interacting with a device instance **/ typedef enum { FU_DEVICE_INSTANCE_FLAG_NONE = 0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS = 1 << 0, FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS = 1 << 1, /*< 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 /** * 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 /** * FuDeviceRetryFunc: * @self: a #FuDevice * @user_data: 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(void); FuDevice * fu_device_new_with_context(FuContext *ctx); /* helpful casting macros */ #define fu_device_has_flag(d, v) fwupd_device_has_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_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_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_raw(d, v) fwupd_device_set_version_raw(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_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)) /** * 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 clildren 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 (1llu << 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) /* accessors */ gchar * fu_device_to_string(FuDevice *self); void fu_device_add_string(FuDevice *self, guint idt, GString *str); const gchar * fu_device_get_alternate_id(FuDevice *self); void fu_device_set_alternate_id(FuDevice *self, const gchar *alternate_id); const gchar * fu_device_get_equivalent_id(FuDevice *self); void fu_device_set_equivalent_id(FuDevice *self, const gchar *equivalent_id); void fu_device_add_guid(FuDevice *self, const gchar *guid); void fu_device_add_guid_full(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags); gboolean fu_device_has_guid(FuDevice *self, const gchar *guid); void fu_device_add_instance_id(FuDevice *self, const gchar *instance_id); void fu_device_add_instance_id_full(FuDevice *self, const gchar *instance_id, FuDeviceInstanceFlags flags); FuDevice * fu_device_get_alternate(FuDevice *self); FuDevice * fu_device_get_root(FuDevice *self); FuDevice * fu_device_get_parent(FuDevice *self); GPtrArray * fu_device_get_children(FuDevice *self); void fu_device_add_child(FuDevice *self, FuDevice *child); void fu_device_remove_child(FuDevice *self, FuDevice *child); void fu_device_add_parent_guid(FuDevice *self, const gchar *guid); void fu_device_add_parent_physical_id(FuDevice *self, const gchar *physical_id); void fu_device_add_counterpart_guid(FuDevice *self, const gchar *guid); FuDevice * fu_device_get_proxy(FuDevice *self); void fu_device_set_proxy(FuDevice *self, FuDevice *proxy); FuDevice * fu_device_get_proxy_with_fallback(FuDevice *self); const gchar * fu_device_get_metadata(FuDevice *self, const gchar *key); gboolean fu_device_get_metadata_boolean(FuDevice *self, const gchar *key); guint fu_device_get_metadata_integer(FuDevice *self, const gchar *key); void fu_device_remove_metadata(FuDevice *self, const gchar *key); void fu_device_set_metadata(FuDevice *self, const gchar *key, const gchar *value); void fu_device_set_metadata_boolean(FuDevice *self, const gchar *key, gboolean value); void fu_device_set_metadata_integer(FuDevice *self, const gchar *key, guint value); void fu_device_set_id(FuDevice *self, const gchar *id); void fu_device_set_version_format(FuDevice *self, FwupdVersionFormat fmt); void fu_device_set_version(FuDevice *self, const gchar *version); void fu_device_set_version_lowest(FuDevice *self, const gchar *version); void fu_device_set_version_bootloader(FuDevice *self, const gchar *version); void fu_device_inhibit(FuDevice *self, const gchar *inhibit_id, const gchar *reason); void fu_device_uninhibit(FuDevice *self, const gchar *inhibit_id); const gchar * fu_device_get_physical_id(FuDevice *self); void fu_device_set_physical_id(FuDevice *self, const gchar *physical_id); const gchar * fu_device_get_logical_id(FuDevice *self); void fu_device_set_logical_id(FuDevice *self, const gchar *logical_id); const gchar * fu_device_get_backend_id(FuDevice *self); void fu_device_set_backend_id(FuDevice *self, const gchar *backend_id); const gchar * fu_device_get_proxy_guid(FuDevice *self); void fu_device_set_proxy_guid(FuDevice *self, const gchar *proxy_guid); guint fu_device_get_priority(FuDevice *self); void fu_device_set_priority(FuDevice *self, guint priority); void fu_device_add_flag(FuDevice *self, FwupdDeviceFlags flag); void fu_device_remove_flag(FuDevice *self, FwupdDeviceFlags flag); const gchar * fu_device_get_custom_flags(FuDevice *self); void fu_device_set_custom_flags(FuDevice *self, const gchar *custom_flags); void fu_device_set_name(FuDevice *self, const gchar *value); void fu_device_set_vendor(FuDevice *self, const gchar *vendor); guint fu_device_get_remove_delay(FuDevice *self); void fu_device_set_remove_delay(FuDevice *self, guint remove_delay); void fu_device_set_firmware_size(FuDevice *self, guint64 size); void fu_device_set_firmware_size_min(FuDevice *self, guint64 size_min); void fu_device_set_firmware_size_max(FuDevice *self, guint64 size_max); guint64 fu_device_get_firmware_size_min(FuDevice *self); guint64 fu_device_get_firmware_size_max(FuDevice *self); guint fu_device_get_battery_level(FuDevice *self); void fu_device_set_battery_level(FuDevice *self, guint battery_level); guint fu_device_get_battery_threshold(FuDevice *self); void fu_device_set_battery_threshold(FuDevice *self, guint battery_threshold); void fu_device_set_update_state(FuDevice *self, FwupdUpdateState update_state); void fu_device_set_context(FuDevice *self, FuContext *ctx); FuContext * fu_device_get_context(FuDevice *self); FwupdRelease * fu_device_get_release_default(FuDevice *self); GType fu_device_get_specialized_gtype(FuDevice *self); GType fu_device_get_firmware_gtype(FuDevice *self); void fu_device_set_firmware_gtype(FuDevice *self, GType firmware_gtype); void fu_device_add_internal_flag(FuDevice *self, FuDeviceInternalFlags flag); void fu_device_remove_internal_flag(FuDevice *self, FuDeviceInternalFlags flag); gboolean fu_device_has_internal_flag(FuDevice *self, FuDeviceInternalFlags flag); gboolean fu_device_get_results(FuDevice *self, GError **error); gboolean fu_device_write_firmware(FuDevice *self, GBytes *fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuFirmware * fu_device_prepare_firmware(FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuFirmware * fu_device_read_firmware(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_device_dump_firmware(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_attach(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_detach(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_attach_full(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_detach_full(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_reload(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_prepare(FuDevice *self, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_cleanup(FuDevice *self, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_device_incorporate(FuDevice *self, FuDevice *donor); void fu_device_incorporate_flag(FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag); gboolean fu_device_open(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_close(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_probe(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_setup(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_rescan(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_activate(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_device_probe_invalidate(FuDevice *self); gboolean fu_device_poll(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_device_set_poll_interval(FuDevice *self, guint interval); void fu_device_retry_set_delay(FuDevice *self, guint delay); void fu_device_retry_add_recovery(FuDevice *self, GQuark domain, gint code, FuDeviceRetryFunc func); gboolean fu_device_retry(FuDevice *self, FuDeviceRetryFunc func, guint count, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_retry_full(FuDevice *self, FuDeviceRetryFunc func, guint count, guint delay, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_bind_driver(FuDevice *self, const gchar *subsystem, const gchar *driver, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_device_unbind_driver(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; GHashTable * fu_device_report_metadata_pre(FuDevice *self); GHashTable * fu_device_report_metadata_post(FuDevice *self); void fu_device_add_security_attrs(FuDevice *self, FuSecurityAttrs *attrs); void fu_device_register_private_flag(FuDevice *self, guint64 value, const gchar *value_str); void fu_device_add_private_flag(FuDevice *self, guint64 flag); void fu_device_remove_private_flag(FuDevice *self, guint64 flag); gboolean fu_device_has_private_flag(FuDevice *self, guint64 flag); void fu_device_emit_request(FuDevice *self, FwupdRequest *request); fwupd-1.7.5/libfwupdplugin/fu-dfu-firmware-private.h000066400000000000000000000006741420024370600225320ustar00rootroot00000000000000/* * 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); GBytes * fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error); gboolean fu_dfu_firmware_parse_footer(FuDfuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error); fwupd-1.7.5/libfwupdplugin/fu-dfu-firmware.c000066400000000000000000000234251420024370600210540ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-common.h" #include "fu-dfu-firmware-private.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; } typedef struct __attribute__((packed)) { guint16 release; guint16 pid; guint16 vid; guint16 ver; guint8 sig[3]; guint8 len; guint32 crc; } FuDfuFirmwareFooter; gboolean fu_dfu_firmware_parse_footer(FuDfuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); FuDfuFirmwareFooter ftr; gsize len; guint32 crc; guint32 crc_new; guint8 *data; /* check data size */ data = (guint8 *)g_bytes_get_data(fw, &len); if (len < sizeof(FuDfuFirmwareFooter)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "size check failed, too small"); return FALSE; } /* check for DFU signature */ if (memcmp(&data[len - G_STRUCT_OFFSET(FuDfuFirmwareFooter, sig)], "UFD", sizeof(ftr.sig)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DFU signature"); return FALSE; } /* verify the checksum */ if (!fu_memcpy_safe((guint8 *)&ftr, sizeof(FuDfuFirmwareFooter), 0x0, /* dst */ data, len, len - sizeof(FuDfuFirmwareFooter), /* src */ sizeof(FuDfuFirmwareFooter), error)) return FALSE; crc = GUINT32_FROM_LE(ftr.crc); if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { crc_new = ~fu_common_crc32(data, len - 4); if (crc != crc_new) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CRC failed, expected %04x, got %04x", crc_new, GUINT32_FROM_LE(ftr.crc)); return FALSE; } } /* set from footer */ priv->vid = GUINT16_FROM_LE(ftr.vid); priv->pid = GUINT16_FROM_LE(ftr.pid); priv->release = GUINT16_FROM_LE(ftr.release); priv->dfu_version = GUINT16_FROM_LE(ftr.ver); priv->footer_len = ftr.len; /* check reported length */ if (priv->footer_len > len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "reported footer size %04x larger than file %04x", (guint)priv->footer_len, (guint)len); return FALSE; } /* success */ return TRUE; } static gboolean fu_dfu_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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_common_bytes_new_offset(fw, 0, len - priv->footer_len, error); if (contents == NULL) return FALSE; fu_firmware_set_bytes(firmware, contents); return TRUE; } GBytes * fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); GByteArray *buf = g_byte_array_new(); const guint8 *blob; gsize blobsz = 0; /* add the raw firmware data */ blob = g_bytes_get_data(contents, &blobsz); g_byte_array_append(buf, blob, blobsz); /* append footer */ fu_byte_array_append_uint16(buf, priv->release, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, priv->pid, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, priv->vid, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, priv->dfu_version, G_LITTLE_ENDIAN); g_byte_array_append(buf, (const guint8 *)"UFD", 3); fu_byte_array_append_uint8(buf, sizeof(FuDfuFirmwareFooter)); fu_byte_array_append_uint32(buf, ~fu_common_crc32(buf->data, buf->len), G_LITTLE_ENDIAN); return g_byte_array_free_to_bytes(buf); } static GBytes * 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->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.7.5/libfwupdplugin/fu-dfu-firmware.h000066400000000000000000000033361420024370600210600ustar00rootroot00000000000000/* * 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); guint16 fu_dfu_firmware_get_pid(FuDfuFirmware *self); guint16 fu_dfu_firmware_get_release(FuDfuFirmware *self); guint16 fu_dfu_firmware_get_version(FuDfuFirmware *self); void fu_dfu_firmware_set_vid(FuDfuFirmware *self, guint16 vid); void fu_dfu_firmware_set_pid(FuDfuFirmware *self, guint16 pid); void fu_dfu_firmware_set_release(FuDfuFirmware *self, guint16 release); void fu_dfu_firmware_set_version(FuDfuFirmware *self, guint16 version); fwupd-1.7.5/libfwupdplugin/fu-dfuse-firmware.c000066400000000000000000000221121420024370600213740ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-dfu-firmware-private.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) /* firmware: LE */ typedef struct __attribute__((packed)) { guint8 sig[5]; guint8 ver; guint32 image_size; guint8 targets; } DfuSeHdr; /* image: LE */ typedef struct __attribute__((packed)) { guint8 sig[6]; guint8 alt_setting; guint32 target_named; gchar target_name[255]; guint32 target_size; guint32 chunks; } DfuSeImageHdr; /* element: LE */ typedef struct __attribute__((packed)) { guint32 address; guint32 size; } DfuSeElementHdr; G_STATIC_ASSERT(sizeof(DfuSeHdr) == 11); G_STATIC_ASSERT(sizeof(DfuSeImageHdr) == 274); G_STATIC_ASSERT(sizeof(DfuSeElementHdr) == 8); static FuChunk * fu_firmware_image_chunk_parse(FuDfuseFirmware *self, GBytes *bytes, gsize *offset, GError **error) { DfuSeElementHdr hdr = {0x0}; 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(GBytes) blob = NULL; /* check size */ if (!fu_memcpy_safe((guint8 *)&hdr, sizeof(hdr), 0x0, /* dst */ buf, bufsz - ftrlen, *offset, /* src */ sizeof(hdr), error)) return NULL; /* create new chunk */ *offset += sizeof(hdr); blob = fu_common_bytes_new_offset(bytes, *offset, GUINT32_FROM_LE(hdr.size), error); if (blob == NULL) return NULL; chk = fu_chunk_bytes_new(blob); fu_chunk_set_address(chk, GUINT32_FROM_LE(hdr.address)); *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) { DfuSeImageHdr hdr = {0x0}; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(bytes, &bufsz); g_autoptr(FuFirmware) image = fu_firmware_new(); /* verify image signature */ if (!fu_memcpy_safe((guint8 *)&hdr, sizeof(hdr), 0x0, /* dst */ buf, bufsz, *offset, /* src */ sizeof(hdr), error)) return NULL; if (memcmp(hdr.sig, "Target", sizeof(hdr.sig)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid DfuSe target signature"); return NULL; } /* set properties */ fu_firmware_set_idx(image, hdr.alt_setting); if (GUINT32_FROM_LE(hdr.target_named) == 0x01) { g_autofree gchar *img_id = NULL; img_id = g_strndup(hdr.target_name, sizeof(hdr.target_name)); fu_firmware_set_id(image, img_id); } /* no chunks */ if (hdr.chunks == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "DfuSe image has no chunks"); return NULL; } /* parse chunks */ *offset += sizeof(hdr); for (guint j = 0; j < GUINT32_FROM_LE(hdr.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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE(firmware); DfuSeHdr hdr = {0x0}; gsize bufsz = 0; gsize offset = 0; const guint8 *buf; /* DFU footer first */ if (!fu_dfu_firmware_parse_footer(dfu_firmware, fw, flags, error)) return FALSE; /* check the prefix */ buf = (const guint8 *)g_bytes_get_data(fw, &bufsz); if (!fu_memcpy_safe((guint8 *)&hdr, sizeof(hdr), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(hdr), error)) return FALSE; if (memcmp(hdr.sig, "DfuSe", sizeof(hdr.sig)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe prefix"); return FALSE; } /* check the version */ if (hdr.ver != 0x01) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe version, got %02x", hdr.ver); return FALSE; } /* check image size */ if (GUINT32_FROM_LE(hdr.image_size) != 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, GUINT32_FROM_LE(hdr.image_size), bufsz - fu_dfu_firmware_get_footer_len(dfu_firmware)); return FALSE; } /* parse the image targets */ offset += sizeof(hdr); for (guint i = 0; i < hdr.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; fu_firmware_add_image(firmware, image); } return TRUE; } static GBytes * fu_firmware_chunk_write(FuChunk *chk) { DfuSeElementHdr hdr = {0x0}; const guint8 *data = fu_chunk_get_data(chk); gsize length = fu_chunk_get_data_sz(chk); g_autoptr(GByteArray) buf = NULL; buf = g_byte_array_sized_new(sizeof(DfuSeElementHdr) + length); hdr.address = GUINT32_TO_LE(fu_chunk_get_address(chk)); hdr.size = GUINT32_TO_LE(length); g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); g_byte_array_append(buf, data, length); return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static GBytes * fu_dfuse_firmware_write_image(FuFirmware *image, GError **error) { DfuSeImageHdr hdr = {0x0}; gsize totalsz = 0; g_autoptr(GByteArray) buf = NULL; 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(chk); g_ptr_array_add(blobs, bytes); totalsz += g_bytes_get_size(bytes); } /* mutable output buffer */ buf = g_byte_array_sized_new(sizeof(DfuSeImageHdr) + totalsz); /* add prefix */ memcpy(hdr.sig, "Target", 6); hdr.alt_setting = fu_firmware_get_idx(image); if (fu_firmware_get_id(image) != NULL) { hdr.target_named = GUINT32_TO_LE(0x01); g_strlcpy((gchar *)&hdr.target_name, fu_firmware_get_id(image), sizeof(hdr.target_name)); } hdr.target_size = GUINT32_TO_LE(totalsz); hdr.chunks = GUINT32_TO_LE(chunks->len); g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); /* copy data */ for (guint i = 0; i < blobs->len; i++) { GBytes *blob = g_ptr_array_index(blobs, i); fu_byte_array_append_bytes(buf, blob); } return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static GBytes * fu_dfuse_firmware_write(FuFirmware *firmware, GError **error) { DfuSeHdr hdr = {0x0}; gsize totalsz = 0; g_autoptr(GByteArray) buf = NULL; 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(img, error); if (blob == NULL) return NULL; totalsz += g_bytes_get_size(blob); g_ptr_array_add(blobs, g_steal_pointer(&blob)); } buf = g_byte_array_sized_new(sizeof(DfuSeHdr) + totalsz); /* DfuSe header */ memcpy(hdr.sig, "DfuSe", 5); hdr.ver = 0x01; hdr.image_size = GUINT32_TO_LE(sizeof(hdr) + 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; } hdr.targets = (guint8)images->len; g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); /* copy images */ for (guint i = 0; i < blobs->len; i++) { GBytes *blob = g_ptr_array_index(blobs, i); fu_byte_array_append_bytes(buf, blob); } /* return blob */ blob_noftr = g_byte_array_free_to_bytes(g_steal_pointer(&buf)); 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); } static void fu_dfuse_firmware_class_init(FuDfuseFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); 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.7.5/libfwupdplugin/fu-dfuse-firmware.h000066400000000000000000000006461420024370600214110ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-efi-common.c000066400000000000000000000042371420024370600205150ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-efi-common.h" #include /** * 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_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.7.5/libfwupdplugin/fu-efi-common.h000066400000000000000000000032401420024370600205130ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-efi-firmware-common.c000066400000000000000000000056401420024370600223260ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_LZMA #include #endif #include #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-section.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_common_bytes_new_offset(fw, offset, bufsz - offset, error); if (blob == NULL) return FALSE; /* parse section */ if (!fu_firmware_parse(img, blob, flags, error)) return FALSE; fu_firmware_set_offset(img, offset); fu_firmware_add_image(firmware, img); /* next! */ offset += fu_firmware_get_size(img); } if (offset != g_bytes_get_size(fw)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI sections overflow 0x%x of 0x%x", (guint)offset, (guint)g_bytes_get_size(fw)); return FALSE; } /* success */ return TRUE; } /** * fu_efi_firmware_decompress_lzma: * @blob: data * @error: (nullable): optional return location for an error * * Decompresses a LZMA stream. * * Returns: decompressed data * * Since: 1.6.2 **/ GBytes * fu_efi_firmware_decompress_lzma(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_byte_array_free_to_bytes(g_steal_pointer(&buf)); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "missing lzma support"); return NULL; #endif } fwupd-1.7.5/libfwupdplugin/fu-efi-firmware-common.h000066400000000000000000000005561420024370600223340ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_efi_firmware_parse_sections(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error); GBytes * fu_efi_firmware_decompress_lzma(GBytes *blob, GError **error); fwupd-1.7.5/libfwupdplugin/fu-efi-firmware-file.c000066400000000000000000000277501420024370600217630ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-common.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-file.h" #include "fu-efi-firmware-section.h" 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_ATTRIB_NONE 0x00 #define FU_EFI_FIRMWARE_FILE_ATTRIB_LARGE_FILE 0x01 #define FU_EFI_FIRMWARE_FILE_ATTRIB_DATA_ALIGNMENT_2 0x02 #define FU_EFI_FIRMWARE_FILE_ATTRIB_FIXED 0x04 #define FU_EFI_FIRMWARE_FILE_ATTRIB_DATA_ALIGNMENT 0x38 #define FU_EFI_FIRMWARE_FILE_ATTRIB_CHECKSUM 0x40 #define FU_EFI_FIRMWARE_FILE_TYPE_ALL 0x00 #define FU_EFI_FIRMWARE_FILE_TYPE_RAW 0x01 #define FU_EFI_FIRMWARE_FILE_TYPE_FREEFORM 0x02 #define FU_EFI_FIRMWARE_FILE_TYPE_SECURITY_CORE 0x03 #define FU_EFI_FIRMWARE_FILE_TYPE_PEI_CORE 0x04 #define FU_EFI_FIRMWARE_FILE_TYPE_DXE_CORE 0x05 #define FU_EFI_FIRMWARE_FILE_TYPE_PEIM 0x06 #define FU_EFI_FIRMWARE_FILE_TYPE_DRIVER 0x07 #define FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_PEIM_DRIVER 0x08 #define FU_EFI_FIRMWARE_FILE_TYPE_APPLICATION 0x09 #define FU_EFI_FIRMWARE_FILE_TYPE_MM 0x0A #define FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE 0x0B #define FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_MM_DXE 0x0C #define FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE 0x0D #define FU_EFI_FIRMWARE_FILE_TYPE_MM_STANDALONE 0x0E #define FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE_STANDALONE 0x0F #define FU_EFI_FIRMWARE_FILE_TYPE_FFS_PAD 0xF0 #define FU_EFI_FIRMWARE_FILE_OFFSET_NAME 0x00 #define FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM 0x10 #define FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM 0x11 #define FU_EFI_FIRMWARE_FILE_OFFSET_TYPE 0x12 #define FU_EFI_FIRMWARE_FILE_OFFSET_ATTRS 0x13 #define FU_EFI_FIRMWARE_FILE_OFFSET_SIZE 0x14 #define FU_EFI_FIRMWARE_FILE_OFFSET_STATE 0x17 #define FU_EFI_FIRMWARE_FILE_SIZE 0x18 static const gchar * fu_efi_firmware_file_type_to_string(guint8 type) { if (type == FU_EFI_FIRMWARE_FILE_TYPE_ALL) return "all"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_RAW) return "raw"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_FREEFORM) return "freeform"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_SECURITY_CORE) return "security-core"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_PEI_CORE) return "pei-core"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_DXE_CORE) return "dxe-core"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_PEIM) return "peim"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_DRIVER) return "driver"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_PEIM_DRIVER) return "combined-peim-driver"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_APPLICATION) return "application"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM) return "mm"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE) return "firmware-volume-image"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_COMBINED_MM_DXE) return "combined-mm-dxe"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE) return "mm-core"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_STANDALONE) return "mm-standalone"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_MM_CORE_STANDALONE) return "core-standalone"; if (type == FU_EFI_FIRMWARE_FILE_TYPE_FFS_PAD) return "ffs-pad"; return NULL; } 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_firmware_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_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM) continue; if (i == FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM) continue; if (i == FU_EFI_FIRMWARE_FILE_OFFSET_STATE) continue; checksum += buf[i]; } return 0x100 - checksum; } static gboolean fu_efi_firmware_file_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE(firmware); FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guint32 size = 0x0; guint8 data_checksum = 0x0; guint8 hdr_checksum = 0x0; guint8 img_state = 0x0; fwupd_guid_t guid = {0x0}; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *guid_str = NULL; g_autoptr(GBytes) blob = NULL; /* header */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_NAME, /* src */ sizeof(guid), error)) return FALSE; guid_str = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_firmware_set_id(firmware, guid_str); if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_STATE, &img_state, error)) return FALSE; if (img_state != 0xF8) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "state invalid, got 0x%x, expected 0x%x", img_state, (guint)0xF8); return FALSE; } if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM, &hdr_checksum, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_DATA_CHECKSUM, &data_checksum, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_TYPE, &priv->type, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_FILE_OFFSET_ATTRS, &priv->attrib, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, /* uint24_t! */ FU_EFI_FIRMWARE_FILE_OFFSET_SIZE, &size, G_LITTLE_ENDIAN, error)) return FALSE; size &= 0xFFFFFF; if (size < FU_EFI_FIRMWARE_FILE_SIZE) { 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) { g_autoptr(GBytes) hdr_blob = g_bytes_new_from_bytes(fw, 0x0, FU_EFI_FIRMWARE_FILE_SIZE); guint8 hdr_checksum_verify = fu_efi_firmware_file_hdr_checksum8(hdr_blob); if (hdr_checksum_verify != hdr_checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", hdr_checksum_verify, hdr_checksum); return FALSE; } } /* add simple blob */ blob = fu_common_bytes_new_offset(fw, FU_EFI_FIRMWARE_FILE_SIZE, size - FU_EFI_FIRMWARE_FILE_SIZE, error); if (blob == NULL) return FALSE; /* add fv-image */ if (priv->type == FU_EFI_FIRMWARE_FILE_TYPE_FIRMWARE_VOLUME_IMAGE) { if (!fu_efi_firmware_parse_sections(firmware, blob, flags, error)) return FALSE; } else { fu_firmware_set_bytes(firmware, blob); } /* verify data checksum */ if ((priv->attrib & FU_EFI_FIRMWARE_FILE_ATTRIB_CHECKSUM) > 0 && (flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 data_checksum_verify = 0x100 - fu_common_sum8_bytes(blob); if (data_checksum_verify != data_checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", data_checksum_verify, data_checksum); 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_get_alignment(img), 0xFF); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static GBytes * 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) buf = g_byte_array_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; /* header */ if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; g_byte_array_append(buf, (guint8 *)&guid, sizeof(guid)); fu_byte_array_append_uint8(buf, 0x0); /* hdr_checksum */ fu_byte_array_append_uint8(buf, 0x100 - fu_common_sum8_bytes(blob)); fu_byte_array_append_uint8(buf, priv->type); /* data_checksum */ fu_byte_array_append_uint8(buf, priv->attrib); /* data_checksum */ fu_byte_array_append_uint32(buf, g_bytes_get_size(blob) + FU_EFI_FIRMWARE_FILE_SIZE, G_LITTLE_ENDIAN); buf->data[FU_EFI_FIRMWARE_FILE_OFFSET_STATE] = 0xF8; /* overwrite the LSB of size */ /* fix up header checksum */ hdr_blob = g_bytes_new(buf->data, buf->len); buf->data[FU_EFI_FIRMWARE_FILE_OFFSET_HDR_CHECKSUM] = fu_efi_firmware_file_hdr_checksum8(hdr_blob); /* payload */ fu_byte_array_append_bytes(buf, blob); /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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_FIRMWARE_FILE_ATTRIB_NONE; priv->type = FU_EFI_FIRMWARE_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.7.5/libfwupdplugin/fu-efi-firmware-file.h000066400000000000000000000006571420024370600217650ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/libfwupdplugin/fu-efi-firmware-filesystem.c000066400000000000000000000057471420024370600232320ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) static gboolean fu_efi_firmware_filesystem_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize offset = 0; 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_common_bytes_new_offset(fw, offset, bufsz - offset, error); if (fw_tmp == NULL) return FALSE; if (!fu_firmware_parse(img, fw_tmp, flags, error)) { g_prefix_error(error, "failed to parse EFI file at 0x%x: ", (guint)offset); return FALSE; } fu_firmware_set_offset(firmware, offset); fu_firmware_add_image(firmware, img); /* next! */ offset += fu_firmware_get_size(img); } /* success */ return TRUE; } static GBytes * 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); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_efi_firmware_filesystem_init(FuEfiFirmwareFilesystem *self) { 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.7.5/libfwupdplugin/fu-efi-firmware-filesystem.h000066400000000000000000000007511420024370600232250ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/libfwupdplugin/fu-efi-firmware-section.c000066400000000000000000000217611420024370600225040ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-common.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-section.h" #include "fu-efi-firmware-volume.h" 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)) #define FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE 0x00 #define FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE 0x03 #define FU_EFI_FIRMWARE_SECTION_SIZE 0x04 /* only GUID defined */ #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_NAME 0x04 #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_DATA_OFFSET 0x14 #define FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_ATTR 0x16 #define FU_EFI_FIRMWARE_SECTION_TYPE_COMPRESSION 0x01 #define FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED 0x02 #define FU_EFI_FIRMWARE_SECTION_TYPE_DISPOSABLE 0x03 #define FU_EFI_FIRMWARE_SECTION_TYPE_PE32 0x10 #define FU_EFI_FIRMWARE_SECTION_TYPE_PIC 0x11 #define FU_EFI_FIRMWARE_SECTION_TYPE_TE 0x12 #define FU_EFI_FIRMWARE_SECTION_TYPE_DXE_DEPEX 0x13 #define FU_EFI_FIRMWARE_SECTION_TYPE_VERSION 0x14 #define FU_EFI_FIRMWARE_SECTION_TYPE_USER_INTERFACE 0x15 #define FU_EFI_FIRMWARE_SECTION_TYPE_COMPATIBILITY16 0x16 #define FU_EFI_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE 0x17 #define FU_EFI_FIRMWARE_SECTION_TYPE_FREEFORM_SUBTYPE_GUID 0x18 #define FU_EFI_FIRMWARE_SECTION_TYPE_RAW 0x19 #define FU_EFI_FIRMWARE_SECTION_TYPE_PEI_DEPEX 0x1B #define FU_EFI_FIRMWARE_SECTION_TYPE_MM_DEPEX 0x1C static const gchar * fu_efi_firmware_section_type_to_string(guint8 type) { if (type == FU_EFI_FIRMWARE_SECTION_TYPE_COMPRESSION) return "compression"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) return "guid-defined"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_DISPOSABLE) return "disposable"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PE32) return "pe32"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PIC) return "pic"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_TE) return "te"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_DXE_DEPEX) return "dxe-depex"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_VERSION) return "version"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_USER_INTERFACE) return "user-interface"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_COMPATIBILITY16) return "compatibility16"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE) return "volume-image"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_FREEFORM_SUBTYPE_GUID) return "freeform-subtype-guid"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_RAW) return "raw"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_PEI_DEPEX) return "pei-depex"; if (type == FU_EFI_FIRMWARE_SECTION_TYPE_MM_DEPEX) return "mm-depex"; return NULL; } 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_firmware_section_type_to_string(priv->type)); } } static gboolean fu_efi_firmware_section_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION(firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guint16 attr = 0x0; guint16 offset = FU_EFI_FIRMWARE_SECTION_SIZE; guint32 size = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GBytes) blob = NULL; if (!fu_common_read_uint32_safe(buf, bufsz, /* uint24_t! */ FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE, &size, G_LITTLE_ENDIAN, error)) return FALSE; size &= 0xFFFFFF; if (size < FU_EFI_FIRMWARE_SECTION_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint)size); return FALSE; } if (!fu_common_read_uint8_safe(buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE, &priv->type, error)) return FALSE; /* name */ if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) { fwupd_guid_t guid = {0x0}; g_autofree gchar *guid_str = NULL; if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_NAME, /* src */ sizeof(guid), error)) return FALSE; guid_str = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_firmware_set_id(firmware, guid_str); if (!fu_common_read_uint16_safe(buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_DATA_OFFSET, &offset, G_LITTLE_ENDIAN, error)) return FALSE; if (offset < FU_EFI_FIRMWARE_SECTION_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint)size); return FALSE; } if (!fu_common_read_uint16_safe(buf, bufsz, FU_EFI_FIRMWARE_SECTION_OFFSET_GUID_ATTR, &attr, G_LITTLE_ENDIAN, error)) return FALSE; } /* create blob */ blob = fu_common_bytes_new_offset(fw, offset, size - offset, error); if (blob == NULL) 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_FIRMWARE_SECTION_TYPE_VOLUME_IMAGE) { g_autoptr(FuFirmware) img = fu_efi_firmware_volume_new(); if (!fu_firmware_parse(img, blob, flags, error)) return FALSE; fu_firmware_add_image(firmware, img); /* LZMA */ } else if (priv->type == FU_EFI_FIRMWARE_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_efi_firmware_decompress_lzma(blob, error); if (blob_uncomp == NULL) return FALSE; if (!fu_efi_firmware_parse_sections(firmware, blob_uncomp, flags, error)) return FALSE; } /* success */ return TRUE; } static GBytes * 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 = g_byte_array_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 */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* will fixup */ if (priv->type == FU_EFI_FIRMWARE_SECTION_TYPE_GUID_DEFINED) { fwupd_guid_t guid = {0x0}; if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; g_byte_array_append(buf, (guint8 *)&guid, sizeof(guid)); fu_byte_array_append_uint16(buf, buf->len + 0x4, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); } /* correct size and type in common header */ if (!fu_common_write_uint32_safe(buf->data, buf->len, /* uint24_t! */ FU_EFI_FIRMWARE_SECTION_OFFSET_SIZE, buf->len + g_bytes_get_size(blob), G_LITTLE_ENDIAN, error)) return NULL; buf->data[FU_EFI_FIRMWARE_SECTION_OFFSET_TYPE] = priv->type; /* blob */ fu_byte_array_append_bytes(buf, blob); return g_byte_array_free_to_bytes(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_FIRMWARE_SECTION_TYPE_RAW; // 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.7.5/libfwupdplugin/fu-efi-firmware-section.h000066400000000000000000000007241420024370600225050ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/libfwupdplugin/fu-efi-firmware-volume.c000066400000000000000000000272641420024370600223530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-common.h" #include "fu-efi-firmware-filesystem.h" #include "fu-efi-firmware-volume.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)) #define FU_EFI_FIRMWARE_VOLUME_SIGNATURE 0x4856465F #define FU_EFI_FIRMWARE_VOLUME_REVISION 0x02 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_ZERO_VECTOR 0x00 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_GUID 0x10 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_LENGTH 0x20 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_SIGNATURE 0x28 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_ATTRS 0x2C #define FU_EFI_FIRMWARE_VOLUME_OFFSET_HDR_LEN 0x30 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_CHECKSUM 0x32 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_EXT_HDR 0x34 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_RESERVED 0x36 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_REVISION 0x37 #define FU_EFI_FIRMWARE_VOLUME_OFFSET_BLOCK_MAP 0x38 #define FU_EFI_FIRMWARE_VOLUME_SIZE 0x40 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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareVolume *self = FU_EFI_FIRMWARE_VOLUME(firmware); FuEfiFirmwareVolumePrivate *priv = GET_PRIVATE(self); fwupd_guid_t guid = {0x0}; gsize blockmap_sz = 0; gsize bufsz = 0; gsize offset = 0; guint16 checksum = 0; guint16 ext_hdr = 0; guint16 hdr_length = 0; guint32 attrs = 0; guint32 sig = 0; guint64 fv_length = 0; guint8 alignment; guint8 revision = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *guid_str = NULL; g_autoptr(GBytes) blob = NULL; /* sanity check */ if (!fu_common_read_uint32_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_SIGNATURE, &sig, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read signature: "); return FALSE; } if (sig != FU_EFI_FIRMWARE_VOLUME_SIGNATURE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI FV signature invalid, got 0x%x, expected 0x%x", sig, (guint)FU_EFI_FIRMWARE_VOLUME_SIGNATURE); return FALSE; } /* guid */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_GUID, /* src */ sizeof(guid), error)) { g_prefix_error(error, "failed to read GUID: "); return FALSE; } guid_str = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str)); /* length */ if (!fu_common_read_uint64_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_LENGTH, &fv_length, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read length: "); return FALSE; } 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); if (!fu_common_read_uint32_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_ATTRS, &attrs, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read attrs: "); return FALSE; } 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; if (!fu_common_read_uint16_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_HDR_LEN, &hdr_length, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read hdr_length: "); return FALSE; } if (hdr_length < FU_EFI_FIRMWARE_VOLUME_SIZE || hdr_length > fv_length) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid volume header length"); return FALSE; } if (!fu_common_read_uint16_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_CHECKSUM, &checksum, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read checksum: "); return FALSE; } if (!fu_common_read_uint16_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_EXT_HDR, &ext_hdr, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read ext_hdr: "); return FALSE; } if (!fu_common_read_uint8_safe(buf, bufsz, offset + FU_EFI_FIRMWARE_VOLUME_OFFSET_REVISION, &revision, error)) return FALSE; if (revision != FU_EFI_FIRMWARE_VOLUME_REVISION) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "revision invalid, got 0x%x, expected 0x%x", revision, (guint)FU_EFI_FIRMWARE_VOLUME_REVISION); return FALSE; } /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint16 checksum_verify = 0; for (guint j = 0; j < hdr_length; j += sizeof(guint16)) { guint16 checksum_tmp = 0; if (!fu_common_read_uint16_safe(buf, bufsz, offset + j, &checksum_tmp, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to hdr checksum 0x%x: ", j); return FALSE; } checksum_verify += checksum_tmp; } if (checksum_verify != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", checksum_verify, checksum); return FALSE; } } /* add image */ blob = fu_common_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, error)) return FALSE; fu_firmware_add_image(firmware, img); } else { fu_firmware_set_bytes(firmware, blob); } /* skip the blockmap */ offset += FU_EFI_FIRMWARE_VOLUME_OFFSET_BLOCK_MAP; while (offset < bufsz) { guint32 num_blocks = 0; guint32 length = 0; if (!fu_common_read_uint32_safe(buf, bufsz, offset, &num_blocks, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, offset + sizeof(guint32), &length, G_LITTLE_ENDIAN, error)) return FALSE; offset += 2 * sizeof(guint32); 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 GBytes * 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 = g_byte_array_new(); fwupd_guid_t guid = {0x0}; guint16 checksum = 0; 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; } /* zero vector */ for (guint i = 0; i < 0x10; i++) fu_byte_array_append_uint8(buf, 0x0); /* 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; g_byte_array_append(buf, (const guint8 *)&guid, sizeof(guid)); /* 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; } } fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob), fu_firmware_get_alignment(firmware)); fu_byte_array_append_uint64(buf, fv_length, G_LITTLE_ENDIAN); /* signature */ fu_byte_array_append_uint32(buf, FU_EFI_FIRMWARE_VOLUME_SIGNATURE, G_LITTLE_ENDIAN); /* attributes */ fu_byte_array_append_uint32(buf, priv->attrs | ((guint32)fu_firmware_get_alignment(firmware) << 16), G_LITTLE_ENDIAN); /* header length */ fu_byte_array_append_uint16(buf, hdr_length, G_LITTLE_ENDIAN); /* checksum (will fixup) */ fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); /* ext header offset */ fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ fu_byte_array_append_uint8(buf, 0x0); /* revision */ fu_byte_array_append_uint8(buf, FU_EFI_FIRMWARE_VOLUME_REVISION); /* blockmap */ fu_byte_array_append_uint32(buf, fv_length, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x1, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* fix up checksum */ for (guint j = buf->len - hdr_length; j < buf->len; j += sizeof(guint16)) { guint16 checksum_tmp = 0; if (!fu_common_read_uint16_safe(buf->data, buf->len, j, &checksum_tmp, G_LITTLE_ENDIAN, error)) return NULL; checksum += checksum_tmp; } if (!fu_common_write_uint16_safe(buf->data, buf->len, buf->len - 0x16, 0x10000 - checksum, G_LITTLE_ENDIAN, error)) return NULL; /* pad contents to alignment */ fu_byte_array_append_bytes(buf, img_blob); fu_byte_array_set_size_full(buf, fv_length, 0xFF); /* success */ return g_byte_array_free_to_bytes(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->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.7.5/libfwupdplugin/fu-efi-firmware-volume.h000066400000000000000000000007151420024370600223500ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/libfwupdplugin/fu-efi-signature-list.c000066400000000000000000000167611420024370600222040ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-common.h" #include "fu-efi-signature-list.h" #include "fu-efi-signature-private.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) static gboolean fu_efi_signature_list_parse_item(FuEfiSignatureList *self, FuEfiSignatureKind sig_kind, const guint8 *buf, gsize bufsz, gsize offset, guint32 sig_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 (sig_size <= sizeof(fwupd_guid_t)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureSize invalid: 0x%x", (guint)sig_size); return FALSE; } sig_datasz = sig_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); fu_firmware_add_image(FU_FIRMWARE(self), FU_FIRMWARE(sig)); return TRUE; } 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; fwupd_guid_t guid; gsize offset_tmp; guint32 sig_header_size = 0; guint32 sig_list_size = 0; guint32 sig_size = 0; g_autofree gchar *sig_type = NULL; /* read EFI_SIGNATURE_LIST */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, *offset, /* src */ sizeof(guid), error)) { g_prefix_error(error, "failed to read GUID header: "); 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) { 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; } if (!fu_common_read_uint32_safe(buf, bufsz, *offset + 0x10, &sig_list_size, G_LITTLE_ENDIAN, error)) return FALSE; if (sig_list_size < 0x1c || sig_list_size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureListSize invalid: 0x%x", sig_list_size); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, *offset + 0x14, &sig_header_size, G_LITTLE_ENDIAN, error)) return FALSE; if (sig_header_size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureHeaderSize invalid: 0x%x", sig_size); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, *offset + 0x18, &sig_size, G_LITTLE_ENDIAN, error)) return FALSE; if (sig_size < sizeof(fwupd_guid_t) || sig_size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureSize invalid: 0x%x", sig_size); return FALSE; } /* header is typically unused */ offset_tmp = *offset + 0x1c + sig_header_size; for (guint i = 0; i < (sig_list_size - 0x1c) / sig_size; i++) { if (!fu_efi_signature_list_parse_item(self, sig_kind, buf, bufsz, offset_tmp, sig_size, error)) return FALSE; offset_tmp += sig_size; } *offset += sig_list_size; return TRUE; } static gchar * fu_efi_signature_list_get_version(FuEfiSignatureList *self) { guint csum_cnt = 0; const gchar *ignored_guids[] = {FU_EFI_SIGNATURE_GUID_OVMF, FU_EFI_SIGNATURE_GUID_OVMF_LEGACY, NULL}; g_autoptr(GPtrArray) sigs = NULL; 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) continue; if (g_strv_contains(ignored_guids, fu_efi_signature_get_owner(sig))) continue; csum_cnt++; } return g_strdup_printf("%u", csum_cnt); } static gboolean fu_efi_signature_list_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEfiSignatureList *self = FU_EFI_SIGNATURE_LIST(firmware); gsize bufsz = 0; gsize offset_fs = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *version_str = NULL; /* this allows us to skip the efi permissions uint32_t or even the * Microsoft PKCS-7 signature */ if ((flags & FWUPD_INSTALL_FLAG_NO_SEARCH) == 0) { if (bufsz < 5) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "signature invalid: 0x%x", (guint)bufsz); return FALSE; } for (gsize i = 0; i < bufsz - 5; i++) { if (memcmp(buf + i, "\x26\x16\xc4\xc1\x4c", 5) == 0) { g_debug("found EFI_SIGNATURE_LIST @0x%x", (guint)i); offset_fs = i; break; } } } /* parse each EFI_SIGNATURE_LIST */ for (gsize offset = offset_fs; 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 GBytes * fu_efi_signature_list_write(FuFirmware *firmware, GError **error) { GByteArray *buf = g_byte_array_new(); /* SignatureType */ for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8(buf, 0x0); /* SignatureListSize */ fu_byte_array_append_uint32(buf, 16 + 4 + 4 + 4 + 16 + 32, G_LITTLE_ENDIAN); /* SignatureHeaderSize */ fu_byte_array_append_uint32(buf, 0, G_LITTLE_ENDIAN); /* SignatureSize */ fu_byte_array_append_uint32(buf, 16 + 32, G_LITTLE_ENDIAN); /* SignatureOwner */ for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8(buf, '1'); /* SignatureData */ for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8(buf, '2'); return g_byte_array_free_to_bytes(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->parse = fu_efi_signature_list_parse; klass_firmware->write = fu_efi_signature_list_write; } static void fu_efi_signature_list_init(FuEfiSignatureList *self) { } fwupd-1.7.5/libfwupdplugin/fu-efi-signature-list.h000066400000000000000000000005551420024370600222030ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-efi-signature-private.h000066400000000000000000000003551420024370600227000ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-efi-signature.c000066400000000000000000000064571420024370600212340ustar00rootroot00000000000000/* * 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_kind_to_string: * @kind: A #FuEfiSignatureKind, e.g. %FU_EFI_SIGNATURE_KIND_X509 * * Converts the signature kind to a text representation. * * Returns: text, e.g. `x509_cert` * * Since: 1.5.5 **/ const gchar * fu_efi_signature_kind_to_string(FuEfiSignatureKind kind) { if (kind == FU_EFI_SIGNATURE_KIND_SHA256) return "sha256"; if (kind == FU_EFI_SIGNATURE_KIND_X509) return "x509_cert"; return "unknown"; } /** * 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.7.5/libfwupdplugin/fu-efi-signature.h000066400000000000000000000023051420024370600212250ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) /** * FuEfiSignatureKind: * @FU_EFI_SIGNATURE_KIND_UNKNOWN: No signature * @FU_EFI_SIGNATURE_KIND_SHA256: SHA256 hash * @FU_EFI_SIGNATURE_KIND_X509: X509 certificate * * The kind of EFI signature. **/ typedef enum { FU_EFI_SIGNATURE_KIND_UNKNOWN, FU_EFI_SIGNATURE_KIND_SHA256, FU_EFI_SIGNATURE_KIND_X509, /*< private >*/ FU_EFI_SIGNATURE_KIND_LAST } FuEfiSignatureKind; #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" const gchar * fu_efi_signature_kind_to_string(FuEfiSignatureKind kind); FuEfiSignatureKind fu_efi_signature_get_kind(FuEfiSignature *self); const gchar * fu_efi_signature_get_owner(FuEfiSignature *self); fwupd-1.7.5/libfwupdplugin/fu-efivar-freebsd.c000066400000000000000000000077071420024370600213550ustar00rootroot00000000000000/* * 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 (!fu_common_fnmatch(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.7.5/libfwupdplugin/fu-efivar-impl.h000066400000000000000000000020011420024370600206670ustar00rootroot00000000000000/* * 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); GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error); gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error); gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error); gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error); gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error); GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error); fwupd-1.7.5/libfwupdplugin/fu-efivar-linux.c000066400000000000000000000246461420024370600211030ustar00rootroot00000000000000/* * 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" static gchar * fu_efivar_get_path(void) { g_autofree gchar *sysfsfwdir = fu_common_get_path(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", 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", 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", 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 (fu_common_fnmatch(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(); /* 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, 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.7.5/libfwupdplugin/fu-efivar-windows.c000066400000000000000000000044351420024370600214300ustar00rootroot00000000000000/* * 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" gboolean fu_efivar_supported_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on Windows"); 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 Windows"); 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 Windows"); 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 Windows"); 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 Windows"); 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 Windows"); 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 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_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on Windows"); return FALSE; } fwupd-1.7.5/libfwupdplugin/fu-efivar.c000066400000000000000000000165431420024370600177430ustar00rootroot00000000000000/* * 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_full: * @error: (nullable): optional return location for an error * * Determines if secure boot was enabled * * Returns: %TRUE on success * * Since: 1.5.0 **/ gboolean fu_efivar_secure_boot_enabled_full(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; } /** * fu_efivar_secure_boot_enabled: * * Determines if secure boot was enabled * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_secure_boot_enabled(void) { return fu_efivar_secure_boot_enabled_full(NULL); } fwupd-1.7.5/libfwupdplugin/fu-efivar.h000066400000000000000000000045551420024370600177500ustar00rootroot00000000000000/* * 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_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); GFileMonitor * fu_efivar_get_monitor(const gchar *guid, const gchar *name, GError **error); 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; 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; gboolean fu_efivar_set_data_bytes(const gchar *guid, const gchar *name, GBytes *bytes, guint32 attr, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_efivar_delete(const gchar *guid, const gchar *name, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_efivar_delete_with_glob(const gchar *guid, const gchar *name_glob, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fu_efivar_get_names(const gchar *guid, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_efivar_secure_boot_enabled(void); gboolean fu_efivar_secure_boot_enabled_full(GError **error); fwupd-1.7.5/libfwupdplugin/fu-firmware-common.c000066400000000000000000000147261420024370600215720ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "fu-firmware-common.h" #include #include #include "fu-common.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 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_common_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 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_common_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 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_common_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 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_common_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 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_common_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.7.5/libfwupdplugin/fu-firmware-common.h000066400000000000000000000015121420024370600215640ustar00rootroot00000000000000/* * 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); gboolean fu_firmware_strparse_uint8_safe(const gchar *data, gsize datasz, gsize offset, guint8 *value, GError **error); gboolean fu_firmware_strparse_uint16_safe(const gchar *data, gsize datasz, gsize offset, guint16 *value, GError **error); gboolean fu_firmware_strparse_uint24_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error); gboolean fu_firmware_strparse_uint32_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error); fwupd-1.7.5/libfwupdplugin/fu-firmware.c000066400000000000000000001352331420024370600203010ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-firmware.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; GPtrArray *images; /* FuFirmware */ gchar *version; guint64 version_raw; GBytes *bytes; guint8 alignment; gchar *id; gchar *filename; guint64 idx; guint64 addr; guint64 offset; gsize size; 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)) /** * 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"; 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; 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_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_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); g_return_if_fail(priv->bytes == NULL); 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_byte_array_free_to_bytes(g_steal_pointer(&buf)); } /** * 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_parse_full: * @self: a #FuFirmware * @fw: firmware blob * @addr_start: start address, useful for ignoring a bootloader * @addr_end: end address, useful for ignoring config bytes * @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_full(FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, 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); /* 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; } /* 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 (klass->parse != NULL) return klass->parse(self, fw, addr_start, addr_end, flags, error); /* just add entire blob */ fu_firmware_set_bytes(self, fw); fu_firmware_set_size(self, g_bytes_get_size(fw)); 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, 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, "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_common_get_contents_bytes(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 && 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_firmware_set_bytes(self, blob); } else if (data != NULL) { g_autoptr(GBytes) blob = NULL; blob = g_bytes_new(NULL, 0); fu_firmware_set_bytes(self, blob); } /* 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_build(img, xb_image, error)) return FALSE; fu_firmware_add_image(self, img); } } /* 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) return klass->write(self, error); /* 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_common_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_common_bytes_new_offset(priv->bytes, offset, chunk_left, error); } /* check chunk */ return fu_common_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_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; 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: * @self: a #FuPlugin * @img: a child firmware image * * Adds an image to the firmware. * * 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) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(FU_IS_FIRMWARE(img)); /* 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; } } } g_ptr_array_add(priv->images, g_object_ref(img)); } /** * 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_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, "size", priv->size); 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_common_strsafe((const gchar *)buf, MIN(bufsz, 16)); } else { #if GLIB_CHECK_VERSION(2, 61, 0) datastr = g_base64_encode(buf, bufsz); #else /* older GLib versions can't cope with buf=NULL */ if (buf == NULL || bufsz == 0) { datastr = g_strdup(""); } else { datastr = g_base64_encode(buf, bufsz); } #endif } xb_builder_node_insert_text(bn, "data", datastr, "size", dataszstr, NULL); } fu_xmlb_builder_insert_kx(bn, "alignment", priv->alignment); /* 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 | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif 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 | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } 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); 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); object_class->finalize = fu_firmware_finalize; } /** * 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 * @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, 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); for (guint i = 0;; i++) { 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(firmware, fw, 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.7.5/libfwupdplugin/fu-firmware.h000066400000000000000000000210141420024370600202750ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes *(*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; /*< private >*/ gpointer padding[26]; }; /** * FU_FIRMWARE_FLAG_NONE: * * No flags set. * * Since: 1.5.0 **/ #define FU_FIRMWARE_FLAG_NONE (0u) /** * FU_FIRMWARE_FLAG_DEDUPE_ID: * * Dedupe imges by ID. * * Since: 1.5.0 **/ #define FU_FIRMWARE_FLAG_DEDUPE_ID (1u << 0) /** * FU_FIRMWARE_FLAG_DEDUPE_IDX: * * Dedupe imges 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) /** * 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 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, FwupdInstallFlags flags, GError **error, ...); gchar * fu_firmware_to_string(FuFirmware *self); void fu_firmware_export(FuFirmware *self, FuFirmwareExportFlags flags, XbBuilderNode *bn); gchar * fu_firmware_export_to_xml(FuFirmware *self, FuFirmwareExportFlags flags, GError **error); const gchar * fu_firmware_get_version(FuFirmware *self); void fu_firmware_set_version(FuFirmware *self, const gchar *version); guint64 fu_firmware_get_version_raw(FuFirmware *self); void fu_firmware_set_version_raw(FuFirmware *self, guint64 version_raw); void fu_firmware_add_flag(FuFirmware *firmware, FuFirmwareFlags flag); gboolean fu_firmware_has_flag(FuFirmware *firmware, FuFirmwareFlags flag); const gchar * fu_firmware_get_filename(FuFirmware *self); void fu_firmware_set_filename(FuFirmware *self, const gchar *filename); const gchar * fu_firmware_get_id(FuFirmware *self); void fu_firmware_set_id(FuFirmware *self, const gchar *id); guint64 fu_firmware_get_addr(FuFirmware *self); void fu_firmware_set_addr(FuFirmware *self, guint64 addr); guint64 fu_firmware_get_offset(FuFirmware *self); void fu_firmware_set_offset(FuFirmware *self, guint64 offset); gsize fu_firmware_get_size(FuFirmware *self); void fu_firmware_set_size(FuFirmware *self, gsize size); guint64 fu_firmware_get_idx(FuFirmware *self); void fu_firmware_set_idx(FuFirmware *self, guint64 idx); GBytes * fu_firmware_get_bytes(FuFirmware *self, GError **error); GBytes * fu_firmware_get_bytes_with_patches(FuFirmware *self, GError **error); void fu_firmware_set_bytes(FuFirmware *self, GBytes *bytes); guint8 fu_firmware_get_alignment(FuFirmware *self); void fu_firmware_set_alignment(FuFirmware *self, guint8 alignment); void fu_firmware_add_chunk(FuFirmware *self, FuChunk *chk); GPtrArray * fu_firmware_get_chunks(FuFirmware *self, GError **error); gboolean fu_firmware_tokenize(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_build(FuFirmware *self, XbNode *n, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_build_from_xml(FuFirmware *self, const gchar *xml, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_parse(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_parse_file(FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_parse_full(FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_firmware_write(FuFirmware *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_firmware_write_chunk(FuFirmware *self, guint64 address, guint64 chunk_sz_max, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_firmware_write_file(FuFirmware *self, GFile *file, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar * fu_firmware_get_checksum(FuFirmware *self, GChecksumType csum_kind, GError **error); void fu_firmware_add_image(FuFirmware *self, FuFirmware *img); gboolean fu_firmware_remove_image(FuFirmware *self, FuFirmware *img, GError **error); gboolean fu_firmware_remove_image_by_idx(FuFirmware *self, guint64 idx, GError **error); gboolean fu_firmware_remove_image_by_id(FuFirmware *self, const gchar *id, GError **error); GPtrArray * fu_firmware_get_images(FuFirmware *self); FuFirmware * fu_firmware_get_image_by_id(FuFirmware *self, const gchar *id, GError **error); GBytes * fu_firmware_get_image_by_id_bytes(FuFirmware *self, const gchar *id, GError **error); FuFirmware * fu_firmware_get_image_by_idx(FuFirmware *self, guint64 idx, GError **error); GBytes * fu_firmware_get_image_by_idx_bytes(FuFirmware *self, guint64 idx, GError **error); FuFirmware * fu_firmware_get_image_by_checksum(FuFirmware *self, const gchar *checksum, GError **error); void fu_firmware_add_patch(FuFirmware *self, gsize offset, GBytes *blob); fwupd-1.7.5/libfwupdplugin/fu-fmap-firmware.c000066400000000000000000000133011420024370600212110ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-fmap-firmware.h" /** * FuFmapFirmware: * * A FMAP firmware image. * * See also: [class@FuFirmware] */ #define FMAP_SIGNATURE "__FMAP__" #define FMAP_AREANAME "FMAP" G_DEFINE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU_TYPE_FIRMWARE) static gboolean fu_fmap_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuFmapFirmwareClass *klass_firmware = FU_FMAP_FIRMWARE_GET_CLASS(firmware); gsize bufsz; const guint8 *buf = g_bytes_get_data(fw, &bufsz); gsize offset = 0; FuFmap fmap; /* corrupt */ if (g_bytes_get_size(fw) < sizeof(FuFmap)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware too small for fmap"); return FALSE; } /* only search for the fmap signature if not fuzzing */ if ((flags & FWUPD_INSTALL_FLAG_NO_SEARCH) == 0) { if (!fu_memmem_safe(buf, bufsz, (const guint8 *)FMAP_SIGNATURE, 8, &offset, error)) return FALSE; fu_firmware_set_offset(firmware, offset); } /* load header */ if (!fu_memcpy_safe((guint8 *)&fmap, sizeof(fmap), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(fmap), error)) return FALSE; fu_firmware_set_addr(firmware, GUINT64_FROM_LE(fmap.base)); if (GUINT32_FROM_LE(fmap.size) != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", (guint)fmap.size, (guint)bufsz); return FALSE; } if (GUINT16_FROM_LE(fmap.nareas) < 1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "number of areas too small, got %" G_GUINT16_FORMAT, GUINT16_FROM_LE(fmap.nareas)); return FALSE; } offset += sizeof(fmap); for (gsize i = 0; i < GUINT16_FROM_LE(fmap.nareas); i++) { FuFmapArea area; g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) bytes = NULL; g_autofree gchar *area_name = NULL; /* load area */ if (!fu_memcpy_safe((guint8 *)&area, sizeof(area), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(area), error)) return FALSE; /* skip */ if (area.size == 0) continue; bytes = fu_common_bytes_new_offset(fw, (gsize)GUINT32_FROM_LE(area.offset), (gsize)GUINT32_FROM_LE(area.size), error); if (bytes == NULL) return FALSE; area_name = g_strndup((const gchar *)area.name, FU_FMAP_FIRMWARE_STRLEN); 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, GUINT32_FROM_LE(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", fmap.ver_major, fmap.ver_minor); fu_firmware_set_version(img, version); } offset += sizeof(area); } /* subclassed */ if (klass_firmware->parse != NULL) { if (!klass_firmware->parse(firmware, fw, addr_start, addr_end, flags, error)) return FALSE; } /* success */ return TRUE; } static GBytes * 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(); FuFmap hdr = { .signature = {FMAP_SIGNATURE}, .ver_major = 0x1, .ver_minor = 0x1, .base = GUINT64_TO_LE(fu_firmware_get_addr(firmware)), .size = 0x0, .name = "", .nareas = GUINT16_TO_LE(images->len), }; /* pad to offset */ if (fu_firmware_get_offset(firmware) > 0) fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware)); /* add header */ total_sz = offset = sizeof(hdr) + (sizeof(FuFmapArea) * 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); } hdr.size = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + total_sz); g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); /* add each area */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); const gchar *id = fu_firmware_get_id(img); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL); FuFmapArea area = { .offset = GUINT32_TO_LE(fu_firmware_get_offset(firmware) + offset), .size = GUINT32_TO_LE(g_bytes_get_size(fw)), .name = {""}, .flags = 0x0, }; if (id != NULL) strncpy((gchar *)area.name, id, sizeof(area.name) - 1); g_byte_array_append(buf, (const guint8 *)&area, sizeof(area)); 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, NULL); fu_byte_array_append_bytes(buf, fw); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_fmap_firmware_init(FuFmapFirmware *self) { } static void fu_fmap_firmware_class_init(FuFmapFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); 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.7.5/libfwupdplugin/fu-fmap-firmware.h000066400000000000000000000031751420024370600212260ustar00rootroot00000000000000/* * 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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error); /*< private >*/ gpointer padding[14]; }; /** * FuFmapArea: * * Specific area of volatile and static regions in firmware binary. **/ typedef struct __attribute__((packed)) { guint32 offset; /* offset relative to base */ guint32 size; /* size in bytes */ guint8 name[FU_FMAP_FIRMWARE_STRLEN]; /* descriptive name */ guint16 flags; /* flags for this area */ } FuFmapArea; /** * FuFmap: * * Mapping of volatile and static regions in firmware binary. **/ typedef struct __attribute__((packed)) { guint8 signature[8]; /* "__FMAP__" (0x5F5F464D41505F5F) */ guint8 ver_major; /* major version */ guint8 ver_minor; /* minor version */ guint64 base; /* address of the firmware binary */ guint32 size; /* size of firmware binary in bytes */ guint8 name[FU_FMAP_FIRMWARE_STRLEN]; /* name of this firmware binary */ guint16 nareas; /* number of areas described by areas[] below */ FuFmapArea areas[]; } FuFmap; FuFirmware * fu_fmap_firmware_new(void); fwupd-1.7.5/libfwupdplugin/fu-fuzzer-firmware.c.in000066400000000000000000000020351420024370600222220ustar00rootroot00000000000000/* * 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; g_setenv("G_DEBUG", "fatal-criticals", FALSE); 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_autofree gchar *str = fu_firmware_to_string(firmware); g_autoptr(GBytes) fw2 = fu_firmware_write(firmware, NULL); g_print("%s", str); if (fw2 != NULL) g_print("[%" G_GSIZE_FORMAT " bytes]\n", g_bytes_get_size(fw2)); } return 0; } fwupd-1.7.5/libfwupdplugin/fu-fuzzer-main.c000066400000000000000000000014761420024370600207350ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-hash.py000066400000000000000000000015711420024370600176130ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """ Builds a header for the plugins to include """ # pylint: disable=invalid-name,wrong-import-position,pointless-string-statement import sys import hashlib 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("usage: fu-hash.py
    ...") sys.exit(return_code) if __name__ == "__main__": if {"-?", "--help", "--usage"}.intersection(set(sys.argv)): usage(0) if len(sys.argv) < 3: usage(1) m = hashlib.sha256() for argv in sys.argv[2:]: with open(argv, "rb") as f: m.update(f.read()) with open(sys.argv[1], "w") as f2: f2.write("#pragma once\n") f2.write('#define FU_BUILD_HASH "%s"\n' % m.hexdigest()) fwupd-1.7.5/libfwupdplugin/fu-hid-device.c000066400000000000000000000404701420024370600204640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHidDevice" #include "config.h" #include "fu-hid-device.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_common_string_append_kb(str, idt, "InterfaceAutodetect", priv->interface_autodetect); fu_common_string_append_kx(str, idt, "Interface", priv->interface); if (priv->ep_addr_in != 0) fu_common_string_append_kx(str, idt, "EpAddrIn", priv->ep_addr_in); if (priv->ep_addr_out != 0) fu_common_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; } } #ifdef HAVE_GUSB static gboolean fu_hid_device_autodetect_eps(FuHidDevice *self, GUsbInterface *iface, GError **error) { #if G_USB_CHECK_VERSION(0, 3, 3) 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; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif } #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_USE_INTERRUPT_TRANSFER) { 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: 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) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_HID_DEVICE(self)); priv->interface = interface; 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_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 (priv->flags & FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER) { if (g_getenv("FU_HID_DEVICE_VERBOSE") != NULL) { g_autofree gchar *title = NULL; title = g_strdup_printf("HID::SetReport [EP=0x%02x]", priv->ep_addr_out); fu_common_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); } 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; /* special case */ if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE) wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value; if (g_getenv("FU_HID_DEVICE_VERBOSE") != NULL) { g_autofree gchar *title = NULL; title = g_strdup_printf("HID::SetReport [wValue=0x%04x ,wIndex=%u]", wvalue, priv->interface); fu_common_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 (priv->flags & FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER) { 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; } if (g_getenv("FU_HID_DEVICE_VERBOSE") != NULL) { g_autofree gchar *title = NULL; title = g_strdup_printf("HID::GetReport [EP=0x%02x]", priv->ep_addr_in); fu_common_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); } } else { guint16 wvalue = (FU_HID_REPORT_TYPE_INPUT << 8) | helper->value; /* special case */ if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE) wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value; if (g_getenv("FU_HID_DEVICE_VERBOSE") != NULL) { g_autofree gchar *title = NULL; title = g_strdup_printf("HID::GetReport [wValue=0x%04x, wIndex=%u]", wvalue, priv->interface); fu_common_dump_raw(G_LOG_DOMAIN, title, helper->buf, actual_len); } 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; } if (g_getenv("FU_HID_DEVICE_VERBOSE") != NULL) { g_autofree gchar *title = NULL; title = g_strdup_printf("HID::GetReport [wValue=0x%04x, wIndex=%u]", wvalue, priv->interface); fu_common_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.7.5/libfwupdplugin/fu-hid-device.h000066400000000000000000000040341420024370600204650ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; gpointer __reserved[31]; }; /** * 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 * * 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, /*< private >*/ FU_HID_DEVICE_FLAG_LAST } FuHidDeviceFlags; FuHidDevice * fu_hid_device_new(GUsbDevice *usb_device); void fu_hid_device_add_flag(FuHidDevice *self, FuHidDeviceFlags flag); void fu_hid_device_set_interface(FuHidDevice *self, guint8 interface); guint8 fu_hid_device_get_interface(FuHidDevice *self); 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; 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; fwupd-1.7.5/libfwupdplugin/fu-hwids.c000066400000000000000000000407241420024370600176030ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHwids" #include "config.h" #include #include #include #include "fwupd-common.h" #include "fwupd-error.h" #include "fu-common.h" #include "fu-hwids.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_dmi_hw; /* BiosVersion->"1.2.3 " */ GHashTable *hash_dmi_display; /* BiosVersion->"1.2.3" */ GHashTable *hash_smbios_override; /* 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_dmi_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}; 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}}; /* 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_smbios_override: * @self: a #FuHwids * @key: a key, e.g. %FU_HWIDS_KEY_PRODUCT_SKU * @value: (nullable): a new value, e.g. `ExampleModel` * * Sets SMBIOS override values so you can emulate another system. * * This function has no effect if called after fu_hwids_setup() * * Since: 1.5.6 **/ void fu_hwids_add_smbios_override(FuHwids *self, const gchar *key, const gchar *value) { g_return_if_fail(FU_IS_HWIDS(self)); g_return_if_fail(key != NULL); g_hash_table_insert(self->hash_smbios_override, g_strdup(key), g_strdup(value)); } /** * 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_dmi_hw, 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); } typedef gchar *(*FuHwidsConvertFunc)(FuSmbios *smbios, guint8 type, guint8 offset, GError **error); static gchar * fu_hwids_convert_string_table_cb(FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { const gchar *tmp; tmp = fu_smbios_get_string(smbios, type, offset, error); if (tmp == NULL) return NULL; /* ComputerHardwareIds.exe seems to strip spaces */ return fu_common_strstrip(tmp); } static gchar * fu_hwids_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_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); } static gboolean fu_hwids_setup_overrides(FuHwids *self, GError **error) { g_autofree gchar *localstatedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *sysconfigdir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); g_autoptr(GKeyFile) kf = g_key_file_new(); g_autoptr(GPtrArray) fns = g_ptr_array_new_with_free_func(g_free); g_autoptr(GPtrArray) keys = fu_hwids_get_keys(self); /* per-system configuration and optional overrides */ g_ptr_array_add(fns, g_build_filename(sysconfigdir, "daemon.conf", NULL)); g_ptr_array_add(fns, g_build_filename(localstatedir, "daemon.conf", NULL)); for (guint i = 0; i < fns->len; i++) { const gchar *fn = g_ptr_array_index(fns, i); if (g_file_test(fn, G_FILE_TEST_EXISTS)) { g_debug("loading HwId overrides from %s", fn); if (!g_key_file_load_from_file(kf, fn, G_KEY_FILE_NONE, error)) return FALSE; } else { g_debug("not loading HwId overrides from %s", fn); } } /* 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 = g_key_file_get_string(kf, "fwupd", key, NULL); if (value != NULL) fu_hwids_add_smbios_override(self, key, value); } /* success */ return TRUE; } /** * fu_hwids_setup: * @self: a #FuHwids * @smbios: (nullable): a #FuSmbios * @error: (nullable): optional return location for an error * * Reads all the SMBIOS values from the hardware. * * Returns: %TRUE for success * * Since: 0.9.3 **/ gboolean fu_hwids_setup(FuHwids *self, FuSmbios *smbios, GError **error) { struct { const gchar *key; guint8 type; guint8 offset; FuHwidsConvertFunc func; } map[] = {{FU_HWIDS_KEY_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_ENCLOSURE_KIND, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05, fu_hwids_convert_integer_cb}, {FU_HWIDS_KEY_FAMILY, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_PRODUCT_NAME, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_PRODUCT_SKU, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x19, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_VENDOR, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_VERSION, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x05, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x14, fu_hwids_convert_padded_integer_cb}, {FU_HWIDS_KEY_BIOS_MINOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x15, fu_hwids_convert_padded_integer_cb}, {FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x16, fu_hwids_convert_padded_integer_cb}, {FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x17, fu_hwids_convert_padded_integer_cb}, {FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x04, fu_hwids_convert_string_table_cb}, {FU_HWIDS_KEY_BASEBOARD_PRODUCT, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x05, fu_hwids_convert_string_table_cb}, {NULL, 0x00, 0x00, NULL}}; g_return_val_if_fail(FU_IS_HWIDS(self), FALSE); g_return_val_if_fail(FU_IS_SMBIOS(smbios) || smbios == NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* override using a config file */ if (!fu_hwids_setup_overrides(self, error)) return FALSE; /* get all DMI data */ for (guint i = 0; map[i].key != NULL; i++) { const gchar *contents_hdr = NULL; g_autofree gchar *contents = NULL; g_autofree gchar *contents_safe = NULL; g_autoptr(GError) error_local = NULL; /* get the data from a SMBIOS table unless an override exists */ if (g_hash_table_lookup_extended(self->hash_smbios_override, map[i].key, NULL, (gpointer *)&contents_hdr)) { if (contents_hdr == NULL) { g_debug("ignoring %s", map[i].key); continue; } } else if (smbios != 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; } contents_hdr = contents; } else { g_debug("ignoring %s", map[i].key); continue; } g_debug("smbios property %s=%s", map[i].key, contents_hdr); /* weirdly, remove leading zeros */ while (contents_hdr[0] == '0' && map[i].func != fu_hwids_convert_padded_integer_cb) contents_hdr++; g_hash_table_insert(self->hash_dmi_hw, g_strdup(map[i].key), g_strdup(contents_hdr)); /* make suitable for display */ contents_safe = g_str_to_ascii(contents_hdr, "C"); g_strdelimit(contents_safe, "\n\r", '\0'); g_strchomp(contents_safe); g_hash_table_insert(self->hash_dmi_display, g_strdup(map[i].key), g_steal_pointer(&contents_safe)); } /* 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; } g_hash_table_insert(self->hash_guid, g_strdup(guid), GUINT_TO_POINTER(1)); g_ptr_array_add(self->array_guids, g_steal_pointer(&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_dmi_hw); g_hash_table_unref(self->hash_dmi_display); g_hash_table_unref(self->hash_smbios_override); 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_dmi_hw = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->hash_dmi_display = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->hash_smbios_override = 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.7.5/libfwupdplugin/fu-hwids.h000066400000000000000000000061301420024370600176010ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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 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" FuHwids * fu_hwids_new(void); GPtrArray * fu_hwids_get_keys(FuHwids *self); const gchar * fu_hwids_get_value(FuHwids *self, const gchar *key); void fu_hwids_add_smbios_override(FuHwids *self, const gchar *key, const gchar *value); const gchar * fu_hwids_get_replace_keys(FuHwids *self, const gchar *key); gchar * fu_hwids_get_replace_values(FuHwids *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar * fu_hwids_get_guid(FuHwids *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT; GPtrArray * fu_hwids_get_guids(FuHwids *self); gboolean fu_hwids_has_guid(FuHwids *self, const gchar *guid); gboolean fu_hwids_setup(FuHwids *self, FuSmbios *smbios, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-i2c-device.c000066400000000000000000000157161420024370600204020ustar00rootroot00000000000000/* * 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" /** * 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_common_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; /* 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; } fu_udev_device_set_fd(FU_UDEV_DEVICE(self), bus_fd); 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_autoptr(GMatchInfo) info = NULL; g_autoptr(GRegex) regex = NULL; #endif /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_i2c_device_parent_class)->probe(device, error)) return FALSE; /* 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"); if (tmp != NULL) { g_autofree gchar *devid = NULL; g_autofree gchar *name_safe = g_strdup(tmp); g_strdelimit(name_safe, " /\\\"", '-'); devid = g_strdup_printf("I2C\\NAME_%s", name_safe); fu_device_add_instance_id(FU_DEVICE(self), devid); } /* get bus number out of sysfs path */ regex = g_regex_new("/i2c-([0-9]+)/", 0, 0, error); if (regex == NULL) return FALSE; tmp = g_udev_device_get_sysfs_path(udev_device); if (!g_regex_match_full(regex, tmp, -1, 0, 0, &info, error)) return FALSE; priv->bus_number = g_ascii_strtoll(g_match_info_fetch(info, 1), NULL, 10); #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 * @data: value * @error: (nullable): optional return location for an error * * Write a single byte to the I²C device. * * Returns: %TRUE for success * * Since: 1.6.1 **/ gboolean fu_i2c_device_write(FuI2cDevice *self, guint8 data, GError **error) { return fu_udev_device_pwrite_full(FU_UDEV_DEVICE(self), 0x0, &data, 0x01, error); } /** * fu_i2c_device_read: * @self: a #FuI2cDevice * @data: (out): value * @error: (nullable): optional return location for an error * * Read a single byte from the I²C device. * * Returns: %TRUE for success * * Since: 1.6.1 **/ gboolean fu_i2c_device_read(FuI2cDevice *self, guint8 *data, GError **error) { return fu_udev_device_pread_full(FU_UDEV_DEVICE(self), 0x0, data, 0x1, error); } /** * fu_i2c_device_write_full: * @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.6.2 **/ gboolean fu_i2c_device_write_full(FuI2cDevice *self, const guint8 *buf, gsize bufsz, GError **error) { return fu_udev_device_pwrite_full(FU_UDEV_DEVICE(self), 0x0, buf, bufsz, error); } /** * fu_i2c_device_read_full: * @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.6.2 **/ gboolean fu_i2c_device_read_full(FuI2cDevice *self, guint8 *buf, gsize bufsz, GError **error) { return fu_udev_device_pread_full(FU_UDEV_DEVICE(self), 0x0, buf, bufsz, error); } 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; /** * 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.7.5/libfwupdplugin/fu-i2c-device.h000066400000000000000000000017451420024370600204040ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; gpointer __reserved[31]; }; guint fu_i2c_device_get_bus_number(FuI2cDevice *self); void fu_i2c_device_set_bus_number(FuI2cDevice *self, guint bus_number); gboolean fu_i2c_device_read(FuI2cDevice *self, guint8 *data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_i2c_device_write(FuI2cDevice *self, guint8 data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_i2c_device_read_full(FuI2cDevice *self, guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_i2c_device_write_full(FuI2cDevice *self, const guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-ifd-bios.c000066400000000000000000000044251420024370600201570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-firmware-volume.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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; gsize offset = 0x0; guint32 sig; 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; g_autoptr(GBytes) fw_offset = NULL; /* ignore _FIT_ as EOF */ if (!fu_common_read_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 */ fw_offset = fu_common_bytes_new_offset(fw, offset, bufsz - offset, error); if (fw_offset == NULL) return FALSE; 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); } /* success */ return TRUE; } static void fu_ifd_bios_init(FuIfdBios *self) { fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_4K); } 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.7.5/libfwupdplugin/fu-ifd-bios.h000066400000000000000000000005631420024370600201630ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-ifd-common.c000066400000000000000000000066241420024370600205160ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-ifd-common.h" #include /** * fu_ifd_region_to_string: * @region: A #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS * * Converts a #FuIfdRegion to a string. * * Returns: identifier string * * Since: 1.6.2 **/ const gchar * fu_ifd_region_to_string(FuIfdRegion region) { if (region == FU_IFD_REGION_DESC) return "desc"; if (region == FU_IFD_REGION_BIOS) return "bios"; if (region == FU_IFD_REGION_ME) return "me"; if (region == FU_IFD_REGION_GBE) return "gbe"; if (region == FU_IFD_REGION_PLATFORM) return "platform"; if (region == FU_IFD_REGION_DEVEXP) return "devexp"; if (region == FU_IFD_REGION_BIOS2) return "bios2"; if (region == FU_IFD_REGION_EC) return "ec"; if (region == FU_IFD_REGION_IE) return "ie"; if (region == FU_IFD_REGION_10GBE) return "10gbe"; return NULL; } /** * 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.7.5/libfwupdplugin/fu-ifd-common.h000066400000000000000000000017661420024370600205250ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_IFD_REGION_DESC = 0x00, FU_IFD_REGION_BIOS = 0x01, FU_IFD_REGION_ME = 0x02, FU_IFD_REGION_GBE = 0x03, FU_IFD_REGION_PLATFORM = 0x04, FU_IFD_REGION_DEVEXP = 0x05, FU_IFD_REGION_BIOS2 = 0x06, FU_IFD_REGION_EC = 0x08, FU_IFD_REGION_IE = 0x0A, FU_IFD_REGION_10GBE = 0x0B, FU_IFD_REGION_MAX = 0x0F, } FuIfdRegion; 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_string(FuIfdRegion region); 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.7.5/libfwupdplugin/fu-ifd-firmware.c000066400000000000000000000362041420024370600210370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ifd-common.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_SIGNATURE 0x0FF0A55A #define FU_IFD_FDBAR_RESERVED 0x0000 #define FU_IFD_FDBAR_SIGNATURE 0x0010 #define FU_IFD_FDBAR_DESCRIPTOR_MAP0 0x0014 #define FU_IFD_FDBAR_DESCRIPTOR_MAP1 0x0018 #define FU_IFD_FDBAR_DESCRIPTOR_MAP2 0x001C #define FU_IFD_FDBAR_FLASH_UPPER_MAP1 0x0EFC #define FU_IFD_FDBAR_OEM_SECTION 0x0F00 #define FU_IFD_FCBA_FLCOMP 0x0000 #define FU_IFD_FCBA_FLILL 0x0004 #define FU_IFD_FCBA_FLILL1 0x0008 #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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guint32 sig; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* 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; } /* check reserved section */ for (guint i = 0; i < 0x10; i++) { if (buf[FU_IFD_FDBAR_RESERVED + i] != 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "reserved section invalid @0x%x", FU_IFD_FDBAR_RESERVED + i); return FALSE; } } /* check signature */ sig = fu_common_read_uint32(buf + FU_IFD_FDBAR_SIGNATURE, G_LITTLE_ENDIAN); if (sig != FU_IFD_SIGNATURE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "signature invalid, got 0x%x, expected 0x%x", sig, (guint)FU_IFD_SIGNATURE); return FALSE; } /* descriptor registers */ priv->descriptor_map0 = fu_common_read_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP0, G_LITTLE_ENDIAN); 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_common_read_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP1, G_LITTLE_ENDIAN); 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_common_read_uint32(buf + FU_IFD_FDBAR_DESCRIPTOR_MAP2, G_LITTLE_ENDIAN); priv->flash_mch_strap_base_addr = (priv->descriptor_map2 << 4) & 0x00000FF0; /* FCBA */ if (!fu_common_read_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLCOMP, &priv->components_rcd, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL, &priv->illegal_jedec, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL1, &priv->illegal_jedec1, G_LITTLE_ENDIAN, error)) return FALSE; /* FMBA */ if (!fu_common_read_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x0, &priv->flash_master[1], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x4, &priv->flash_master[2], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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_common_read_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_common_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, 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 GBytes * 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(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); /* success */ blob_desc = g_byte_array_free_to_bytes(g_steal_pointer(&buf_desc)); 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; } 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); /* reserved */ for (guint i = 0; i < 0x10; i++) buf->data[FU_IFD_FDBAR_RESERVED + i] = 0xff; /* signature */ fu_common_write_uint32(buf->data + FU_IFD_FDBAR_SIGNATURE, FU_IFD_SIGNATURE, G_LITTLE_ENDIAN); /* descriptor map */ fu_common_write_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP0, priv->descriptor_map0, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP1, priv->descriptor_map1, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + FU_IFD_FDBAR_DESCRIPTOR_MAP2, priv->descriptor_map2, G_LITTLE_ENDIAN); /* FCBA */ if (!fu_common_write_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLCOMP, priv->components_rcd, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL, priv->illegal_jedec, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_uint32_safe(buf->data, buf->len, priv->flash_component_base_addr + FU_IFD_FCBA_FLILL1, priv->illegal_jedec1, G_LITTLE_ENDIAN, 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_common_write_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_byte_array_free_to_bytes(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->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.7.5/libfwupdplugin/fu-ifd-firmware.h000066400000000000000000000007311420024370600210400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); fwupd-1.7.5/libfwupdplugin/fu-ifd-image.c000066400000000000000000000064121420024370600203030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ifd-image.h" 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 GBytes * 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))); /* success */ return g_byte_array_free_to_bytes(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.7.5/libfwupdplugin/fu-ifd-image.h000066400000000000000000000010701420024370600203030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); FuIfdAccess fu_ifd_image_get_access(FuIfdImage *self, FuIfdRegion region); fwupd-1.7.5/libfwupdplugin/fu-ihex-firmware.c000066400000000000000000000372621420024370600212370ustar00rootroot00000000000000/* * Copyright (C) 2019 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-ihex-firmware.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_common_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_common_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_common_strnsplit_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, guint64 addr_start, guint64 addr_end, 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; 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_common_read_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_common_read_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_common_read_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_common_read_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); fu_firmware_add_image(firmware, img_sig); } 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_common_write_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 GBytes * fu_ihex_firmware_write(FuFirmware *firmware, GError **error) { 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); return g_bytes_new(str->str, str->len); } 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); } 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.7.5/libfwupdplugin/fu-ihex-firmware.h000066400000000000000000000022121420024370600212270ustar00rootroot00000000000000/* * 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); void fu_ihex_firmware_set_padding_value(FuIhexFirmware *self, guint8 padding_value); fwupd-1.7.5/libfwupdplugin/fu-io-channel.c000066400000000000000000000276741420024370600205130ustar00rootroot00000000000000/* * 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. * * 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 (!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", 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", 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, 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 * @max_size: maximum size of the returned blob, 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, or %NULL for error * * Since: 1.2.2 **/ GBytes * fu_io_channel_read_bytes(FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) { GByteArray *buf = fu_io_channel_read_byte_array(self, max_size, timeout_ms, flags, error); if (buf == NULL) return NULL; return g_byte_array_free_to_bytes(buf); } /** * fu_io_channel_read_byte_array: * @self: a #FuIOChannel * @max_size: maximum size of the returned blob, 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, or %NULL for error * * Since: 1.3.2 **/ GByteArray * fu_io_channel_read_byte_array(FuIOChannel *self, gssize max_size, 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) buf2 = g_byte_array_new(); g_return_val_if_fail(FU_IS_IO_CHANNEL(self), NULL); /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { guint8 buf[1024]; gssize len = read(self->fd, buf, sizeof(buf)); if (len < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, strerror(errno)); return NULL; } if (len > 0) g_byte_array_append(buf2, buf, len); return g_steal_pointer(&buf2); } /* 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) { guint8 buf[1024]; gssize len = read(self->fd, buf, sizeof(buf)); 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, strerror(errno)); return NULL; } if (len > 0) g_byte_array_append(buf2, buf, len); /* check maximum size */ if (max_size > 0 && buf2->len >= (guint)max_size) 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 (buf2->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(&buf2); } /** * 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) { const guint8 *tmpbuf = NULL; gsize bytes_read_tmp; g_autoptr(GBytes) tmp = NULL; g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); tmp = fu_io_channel_read_bytes(self, bufsz, timeout_ms, flags, error); if (tmp == NULL) return FALSE; tmpbuf = g_bytes_get_data(tmp, &bytes_read_tmp); if (tmpbuf != NULL) memcpy(buf, tmpbuf, bytes_read_tmp); if (bytes_read != NULL) *bytes_read = bytes_read_tmp; 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.7.5/libfwupdplugin/fu-io-channel.h000066400000000000000000000045531420024370600205070ustar00rootroot00000000000000/* * 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; gint fu_io_channel_unix_get_fd(FuIOChannel *self); gboolean fu_io_channel_shutdown(FuIOChannel *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; 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; 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; gboolean fu_io_channel_write_bytes(FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_io_channel_write_byte_array(FuIOChannel *self, GByteArray *buf, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_io_channel_read_bytes(FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GByteArray * fu_io_channel_read_byte_array(FuIOChannel *self, gssize max_size, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-kenv.c000066400000000000000000000021231420024370600174170ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #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.7.5/libfwupdplugin/fu-kenv.h000066400000000000000000000003131420024370600174230ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gchar * fu_kenv_get_string(const gchar *key, GError **error); fwupd-1.7.5/libfwupdplugin/fu-mutex.h000066400000000000000000000021371420024370600176300ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Kalev Lember * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #if !GLIB_CHECK_VERSION(2, 61, 1) /* Backported GRWLock autoptr support for older glib versions */ typedef void GRWLockWriterLocker; static inline GRWLockWriterLocker * g_rw_lock_writer_locker_new(GRWLock *rw_lock) { g_rw_lock_writer_lock(rw_lock); return (GRWLockWriterLocker *)rw_lock; } static inline void g_rw_lock_writer_locker_free(GRWLockWriterLocker *locker) { g_rw_lock_writer_unlock((GRWLock *)locker); } typedef void GRWLockReaderLocker; static inline GRWLockReaderLocker * g_rw_lock_reader_locker_new(GRWLock *rw_lock) { g_rw_lock_reader_lock(rw_lock); return (GRWLockReaderLocker *)rw_lock; } static inline void g_rw_lock_reader_locker_free(GRWLockReaderLocker *locker) { g_rw_lock_reader_unlock((GRWLock *)locker); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRWLockWriterLocker, g_rw_lock_writer_locker_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GRWLockReaderLocker, g_rw_lock_reader_locker_free) #endif fwupd-1.7.5/libfwupdplugin/fu-plugin-private.h000066400000000000000000000073531420024370600214410ustar00rootroot00000000000000/* * 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); gboolean fu_plugin_is_open(FuPlugin *self); guint fu_plugin_get_order(FuPlugin *self); void fu_plugin_set_order(FuPlugin *self, guint order); guint fu_plugin_get_priority(FuPlugin *self); void fu_plugin_set_priority(FuPlugin *self, guint priority); void fu_plugin_set_name(FuPlugin *self, const gchar *name); const gchar * fu_plugin_get_build_hash(FuPlugin *self); GPtrArray * fu_plugin_get_rules(FuPlugin *self, FuPluginRule rule); gboolean fu_plugin_has_rule(FuPlugin *self, FuPluginRule rule, const gchar *name); GHashTable * fu_plugin_get_report_metadata(FuPlugin *self); gboolean fu_plugin_open(FuPlugin *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_startup(FuPlugin *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_coldplug(FuPlugin *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_prepare(FuPlugin *self, FuDevice *device, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_cleanup(FuPlugin *self, FuDevice *device, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_composite_prepare(FuPlugin *self, GPtrArray *devices, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_composite_cleanup(FuPlugin *self, GPtrArray *devices, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_attach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_detach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_reload(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_backend_device_added(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_backend_device_changed(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_device_created(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_plugin_runner_device_added(FuPlugin *self, FuDevice *device); void fu_plugin_runner_device_removed(FuPlugin *self, FuDevice *device); void fu_plugin_runner_device_register(FuPlugin *self, FuDevice *device); gboolean fu_plugin_runner_write_firmware(FuPlugin *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_verify(FuPlugin *self, FuDevice *device, FuProgress *progress, FuPluginVerifyFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_activate(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); gboolean fu_plugin_runner_unlock(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_clear_results(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_plugin_runner_get_results(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_plugin_runner_add_security_attrs(FuPlugin *self, FuSecurityAttrs *attrs); gint fu_plugin_name_compare(FuPlugin *plugin1, FuPlugin *plugin2); gint fu_plugin_order_compare(FuPlugin *plugin1, FuPlugin *plugin2); /* utils */ gchar * fu_plugin_guess_name_from_fn(const gchar *filename); fwupd-1.7.5/libfwupdplugin/fu-plugin-vfuncs.h000066400000000000000000000004751420024370600212710ustar00rootroot00000000000000/* * 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); fwupd-1.7.5/libfwupdplugin/fu-plugin.c000066400000000000000000002063751420024370600177710ustar00rootroot00000000000000/* * 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-context-private.h" #include "fu-device-private.h" #include "fu-mutex.h" #include "fu-plugin-private.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; 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 */ GHashTable *cache; /* (nullable): platform_id:GObject */ GRWLock cache_mutex; GHashTable *report_metadata; /* (nullable): key:value */ GFileMonitor *config_monitor; FuPluginData *data; FuPluginVfuncs vfuncs; } FuPluginPrivate; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_REGISTER, SIGNAL_RULES_CHANGED, SIGNAL_CONFIG_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, 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) { g_return_if_fail(FU_IS_PLUGIN(self)); fwupd_plugin_set_name(FWUPD_PLUGIN(self), name); } static FuPluginVfuncs * fu_plugin_get_vfuncs(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); return &priv->vfuncs; } /** * fu_plugin_get_build_hash: * @self: a #FuPlugin * * Gets the build hash a plugin was generated with. * * Returns: (transfer none): a #gchar, or %NULL for unset. * * Since: 1.2.4 **/ const gchar * fu_plugin_get_build_hash(FuPlugin *self) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); return vfuncs->build_hash; } /** * 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_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->cache_mutex); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); g_return_val_if_fail(id != NULL, NULL); g_return_val_if_fail(locker != 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_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->cache_mutex); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(id != NULL); g_return_if_fail(G_IS_OBJECT(dev)); g_return_if_fail(locker != NULL); 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_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->cache_mutex); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(id != NULL); g_return_if_fail(locker != 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 * * 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 = fu_plugin_get_vfuncs(self); 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; } 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->init != NULL) { g_debug("init(%s)", filename); vfuncs->init(self); } return TRUE; } /* 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); } static void fu_plugin_config_monitor_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuPlugin *self = FU_PLUGIN(user_data); g_autofree gchar *fn = g_file_get_path(file); g_debug("%s changed, sending signal", fn); g_signal_emit(self, signals[SIGNAL_CONFIG_CHANGED], 0); } static gchar * fu_plugin_get_config_filename(FuPlugin *self) { g_autofree gchar *conf_dir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *conf_file = g_strdup_printf("%s.conf", fu_plugin_get_name(self)); return g_build_filename(conf_dir, conf_file, NULL); } /** * 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. * * Plugins should use fu_plugin_device_add_delay() if they are not capable of * actually flashing an image to the hardware so that higher-priority plugins * can add the device themselves. * * 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 fu_plugin_device_add() and * not yet removed with fu_plugin_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 fu_plugin_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_has_custom_flag: * @self: a #FuPlugin * @flag: a custom text flag, specific to the plugin, e.g. `uefi-force-enable` * * Returns if a per-plugin HwId custom flag exists, typically added from a DMI quirk. * * Returns: %TRUE if the quirk entry exists * * Since: 1.3.1 **/ gboolean fu_plugin_has_custom_flag(FuPlugin *self, const gchar *flag) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(flag != NULL, FALSE); return fu_context_has_hwid_flag(priv->ctx, flag); } /** * 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; } static gboolean fu_plugin_device_attach(FuPlugin *self, FuDevice *device, FuProgress *progress, 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; 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); g_autoptr(FuDeviceLocker) locker = NULL; 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); g_autoptr(FuDeviceLocker) locker = NULL; 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75); 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_common_get_path(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_common_set_contents_bytes(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, GError **error) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autofree gchar *config_filename = fu_plugin_get_config_filename(self); g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file = g_file_new_for_path(config_filename); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->startup == NULL) return TRUE; g_debug("startup(%s)", fu_plugin_get_name(self)); if (!vfuncs->startup(self, &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; } /* create a monitor on the config file */ priv->config_monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, error); if (priv->config_monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(priv->config_monitor), "changed", G_CALLBACK(fu_plugin_config_monitor_changed_cb), self); /* success */ return 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, 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, 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 * @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, GError **error) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; /* 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, &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 * @flags: install flags * @device: a device * @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, FwupdInstallFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_flagged_device_generic(self, device, flags, "fu_plugin_prepare", vfuncs->prepare, error); } /** * fu_plugin_runner_cleanup: * @self: a #FuPlugin * @flags: install flags * @device: a device * @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, FwupdInstallFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_flagged_device_generic(self, device, 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_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() * * Since: 1.6.0 **/ void fu_plugin_add_device_gtype(FuPlugin *self, GType device_gtype) { FuPluginPrivate *priv = GET_PRIVATE(self); /* create as required */ if (priv->device_gtypes == NULL) priv->device_gtypes = g_array_new(FALSE, FALSE, sizeof(GType)); /* ensure (to allow quirks to use it) then add */ g_type_ensure(device_gtype); g_array_append_val(priv->device_gtypes, 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_common_check_kernel_version("5.2.0", error); } } #endif return TRUE; } /** * 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); /* 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_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); fu_common_string_replace(str, "Firmware", ""); 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, GError **error) { FuDevice *proxy; FuPluginPrivate *priv = GET_PRIVATE(self); GType device_gtype = fu_device_get_specialized_gtype(FU_DEVICE(device)); g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* fall back to plugin default */ if (device_gtype == G_TYPE_INVALID) { if (priv->device_gtypes->len > 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "too many GTypes to choose a default"); return FALSE; } device_gtype = g_array_index(priv->device_gtypes, GType, 0); } /* create new device and incorporate existing properties */ dev = g_object_new(device_gtype, "context", priv->ctx, NULL); fu_device_incorporate(dev, FU_DEVICE(device)); if (!fu_plugin_runner_device_created(self, dev, error)) return FALSE; /* 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)) { g_autofree gchar *guids = fu_device_get_guids_as_str(dev); g_debug("%s has no updates, so ignoring device", guids); return TRUE; } } /* open and add */ proxy = fu_device_get_proxy(device); if (proxy != NULL) { g_autoptr(FuDeviceLocker) locker_proxy = NULL; locker_proxy = fu_device_locker_new(proxy, error); if (locker_proxy == NULL) return FALSE; } locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add(self, dev); fu_plugin_runner_device_added(self, dev); return TRUE; } /** * fu_plugin_runner_backend_device_added: * @self: a #FuPlugin * @device: a device * @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, 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(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, 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, &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, 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_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 if it's been configured * * Since: 1.0.6 **/ gchar * fu_plugin_get_config_value(FuPlugin *self, const gchar *key) { g_autofree gchar *conf_path = fu_plugin_get_config_filename(self); g_autoptr(GKeyFile) keyfile = NULL; if (!g_file_test(conf_path, G_FILE_TEST_IS_REGULAR)) return NULL; keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, conf_path, G_KEY_FILE_NONE, NULL)) return NULL; return g_key_file_get_string(keyfile, fu_plugin_get_name(self), key, NULL); } /** * 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) { g_autofree gchar *conf_path = fu_plugin_get_config_filename(self); g_autoptr(GKeyFile) keyfile = NULL; 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); keyfile = g_key_file_new(); if (!g_key_file_load_from_file(keyfile, conf_path, G_KEY_FILE_KEEP_COMMENTS, error)) return FALSE; g_key_file_set_string(keyfile, fu_plugin_get_name(self), key, value); return g_key_file_save_to_file(keyfile, conf_path, error); } /** * fu_plugin_set_secure_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 file value and updates file so that non-privileged users * cannot read it. * * Returns: %TRUE for success * * Since: 1.7.4 **/ gboolean fu_plugin_set_secure_config_value(FuPlugin *self, const gchar *key, const gchar *value, GError **error) { g_autofree gchar *conf_path = fu_plugin_get_config_filename(self); gint ret; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_file_test(conf_path, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "%s is missing", conf_path); return FALSE; } ret = g_chmod(conf_path, 0660); if (ret == -1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to set permissions on %s", conf_path); return FALSE; } return fu_plugin_set_config_value(self, key, value, 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) { g_autofree gchar *tmp = fu_plugin_get_config_value(self, key); if (tmp == NULL) return FALSE; return g_ascii_strcasecmp(tmp, "true") == 0; } /** * 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 void fu_plugin_class_init(FuPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_plugin_finalize; /** * 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::config-changed: * @self: the #FuPlugin instance that emitted the signal * * The ::config-changed signal is emitted when one or more config files have changed which * may affect how the daemon should be run. * * Since: 1.7.0 **/ signals[SIGNAL_CONFIG_CHANGED] = g_signal_new("config-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, config_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void fu_plugin_init(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); g_rw_lock_init(&priv->cache_mutex); } static void fu_plugin_finalize(GObject *object) { FuPlugin *self = FU_PLUGIN(object); FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_rw_lock_clear(&priv->cache_mutex); /* optional */ if (vfuncs->destroy != NULL) { g_debug("destroy(%s)", fu_plugin_get_name(self)); vfuncs->destroy(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: * @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)); FuPluginPrivate *priv = GET_PRIVATE(self); if (ctx != NULL) priv->ctx = g_object_ref(ctx); return self; } fwupd-1.7.5/libfwupdplugin/fu-plugin.h000066400000000000000000000273731420024370600177750ustar00rootroot00000000000000/* * 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-version.h" #include "fu-common.h" #include "fu-context.h" #include "fu-device-locker.h" #include "fu-device.h" #include "fu-hwids.h" #include "fu-plugin.h" #include "fu-quirks.h" #include "fu-security-attrs.h" #include "fu-usb-device.h" //#include "fu-hid-device.h" #ifdef HAVE_GUDEV #include "fu-udev-device.h" #endif #include #include /* for in-tree plugins only */ #ifdef FWUPD_COMPILATION #include "fu-hash.h" /* only until HSI is declared stable */ #include "fwupd-security-attr-private.h" #endif #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) 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); void (*config_changed)(FuPlugin *self); /*< private >*/ gpointer padding[19]; }; /** * 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; /** * FuPluginVfuncs: * * The virtual functions that are implemented by the plugins. **/ typedef struct { /** * build_hash: * * Sets the plugin build hash which must be set to avoid tainting the engine. * * Since: 1.7.2 **/ const gchar *build_hash; /** * init: * @self: A #FuPlugin * * Initializes the plugin. * Sets up any static data structures for the plugin. * Most plugins should call fu_plugin_set_build_hash in here. * * Since: 1.7.2 **/ void (*init)(FuPlugin *self); /** * destroy: * @self: a plugin * * Destroys the plugin. * Any allocated memory should be freed here. * * Since: 1.7.2 **/ void (*destroy)(FuPlugin *self); /** * startup: * @self: a #FuPlugin * @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, GError **error); /** * coldplug: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Probes for devices. * * Since: 1.7.2 **/ gboolean (*coldplug)(FuPlugin *self, 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 * @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, 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 * @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, 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 * @dev: a device * @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, FwupdInstallFlags flags, GError **error); /** * cleanup * @self: a #FuPlugin * @dev: a device * @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, 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); /*< private >*/ gpointer padding[9]; } 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); FuPluginData * fu_plugin_get_data(FuPlugin *self); FuPluginData * fu_plugin_alloc_data(FuPlugin *self, gsize data_sz); FuContext * fu_plugin_get_context(FuPlugin *self); void fu_plugin_device_add(FuPlugin *self, FuDevice *device); void fu_plugin_device_remove(FuPlugin *self, FuDevice *device); void fu_plugin_device_register(FuPlugin *self, FuDevice *device); void fu_plugin_add_device_gtype(FuPlugin *self, GType device_gtype); void fu_plugin_add_firmware_gtype(FuPlugin *self, const gchar *id, GType gtype); void fu_plugin_add_udev_subsystem(FuPlugin *self, const gchar *subsystem); gpointer fu_plugin_cache_lookup(FuPlugin *self, const gchar *id); void fu_plugin_cache_remove(FuPlugin *self, const gchar *id); void fu_plugin_cache_add(FuPlugin *self, const gchar *id, gpointer dev); GPtrArray * fu_plugin_get_devices(FuPlugin *self); void fu_plugin_add_rule(FuPlugin *self, FuPluginRule rule, const gchar *name); void fu_plugin_add_report_metadata(FuPlugin *self, const gchar *key, const gchar *value); gchar * fu_plugin_get_config_value(FuPlugin *self, const gchar *key); gboolean fu_plugin_set_secure_config_value(FuPlugin *self, const gchar *key, const gchar *value, GError **error); gboolean fu_plugin_get_config_value_boolean(FuPlugin *self, const gchar *key); gboolean fu_plugin_set_config_value(FuPlugin *self, const gchar *key, const gchar *value, GError **error); gboolean fu_plugin_has_custom_flag(FuPlugin *self, const gchar *flag) G_DEPRECATED_FOR(fu_context_has_hwid_flag); fwupd-1.7.5/libfwupdplugin/fu-progress.c000066400000000000000000000560651420024370600203360ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuProgress" #include "config.h" #include #include "fu-progress.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 fu_progress_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] */ typedef struct { gchar *id; FuProgressFlags flags; guint percentage; FwupdStatus status; GPtrArray *steps; gboolean profile; GTimer *timer; guint step_now; guint step_max; gulong percentage_child_id; gulong status_child_id; FuProgress *child; FuProgress *parent; /* no-ref */ } FuProgressPrivate; typedef struct { FwupdStatus status; guint value; gdouble profile; } FuProgressStep; enum { SIGNAL_PERCENTAGE_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuProgress, fu_progress, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_progress_get_instance_private(o)) /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), NULL); return priv->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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(id != NULL); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; /* set id */ g_free(priv->id); priv->id = g_strdup(id); } /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), FWUPD_STATUS_UNKNOWN); return priv->status; } /** * fu_progress_flag_to_string: * @flag: an internal progress flag, e.g. %FU_PROGRESS_FLAG_GUESSED * * Converts an progress flag to a string. * * Returns: identifier string * * Since: 1.7.0 **/ const gchar * fu_progress_flag_to_string(FuProgressFlags flag) { if (flag == FU_PROGRESS_FLAG_GUESSED) return "guessed"; if (flag == FU_PROGRESS_FLAG_NO_PROFILE) return "no-profile"; return NULL; } /** * fu_progress_flag_from_string: * @flag: a string, e.g. `guessed` * * Converts a string to an progress flag. * * Returns: enumerated value * * Since: 1.7.0 **/ FuProgressFlags fu_progress_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "guessed") == 0) return FU_PROGRESS_FLAG_GUESSED; if (g_strcmp0(flag, "no-profile") == 0) return FU_PROGRESS_FLAG_NO_PROFILE; return FU_PROGRESS_FLAG_UNKNOWN; } /** * 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, FuProgressFlags flag) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); priv->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, FuProgressFlags flag) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); priv->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, FuProgressFlags flag) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), FALSE); return (priv->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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); /* not changed */ if (priv->status == status) return; /* save */ priv->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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), G_MAXUINT); return priv->percentage; } static void fu_progress_build_parent_chain(FuProgress *self, GString *str, guint level) { FuProgressPrivate *priv = GET_PRIVATE(self); if (priv->parent != NULL) fu_progress_build_parent_chain(priv->parent, str, level + 1); g_string_append_printf(str, "%u) %s (%u/%u)\n", level, priv->id, priv->step_now, priv->step_max); } /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(percentage <= 100); /* is it the same */ if (percentage == priv->percentage) return; /* is it less */ if (percentage < priv->percentage) { if (priv->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", priv->percentage, percentage, str->str); } return; } /* save */ priv->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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); priv->profile = profile; } /** * fu_progress_get_profile: * @self: A #FuProgress * @profile: * * Returns if the profile is enabled for this progress. * * Return value: if profiling should be enabled * * Since: 1.7.0 **/ static gboolean fu_progress_get_profile(FuProgress *self) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), FALSE); return priv->profile; } /** * fu_progress_reset: * @self: A #FuProgress * * Resets the #FuProgress object to unset * * Since: 1.7.0 **/ void fu_progress_reset(FuProgress *self) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); /* reset values */ priv->step_max = 0; priv->step_now = 0; priv->percentage = 0; /* only use the timer if profiling; it's expensive */ if (priv->profile) g_timer_start(priv->timer); /* disconnect client */ if (priv->percentage_child_id != 0) { g_signal_handler_disconnect(priv->child, priv->percentage_child_id); priv->percentage_child_id = 0; } if (priv->status_child_id != 0) { g_signal_handler_disconnect(priv->child, priv->status_child_id); priv->status_child_id = 0; } g_clear_object(&priv->child); /* no more step data */ g_ptr_array_set_size(priv->steps, 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(priv->id != NULL); /* only use the timer if profiling; it's expensive */ if (priv->profile) g_timer_start(priv->timer); /* set step_max */ priv->step_max = step_max; } /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), G_MAXUINT); return priv->step_max; } /** * fu_progress_add_step: * @self: A #FuProgress * @status: status value to use for this phase * @value: A step weighting variable argument array * * 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.7.0 **/ void fu_progress_add_step(FuProgress *self, FwupdStatus status, guint value) { FuProgressPrivate *priv = GET_PRIVATE(self); FuProgressStep *step; g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(priv->id != NULL); /* current status */ if (priv->steps->len == 0) fu_progress_set_status(self, status); /* save data */ step = g_new0(FuProgressStep, 1); step->status = status; step->value = value; step->profile = .0; g_ptr_array_add(priv->steps, step); /* in case anything is not using ->steps */ fu_progress_set_steps(self, priv->steps->len); } /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(priv->id != NULL); /* is already at 100%? */ if (priv->step_now == priv->step_max) return; /* all done */ priv->step_now = priv->step_max; fu_progress_set_percentage(self, 100); } 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) { FuProgressPrivate *priv = GET_PRIVATE(self); guint current = 0; guint total = 0; for (guint i = 0; i < priv->steps->len; i++) { FuProgressStep *step = g_ptr_array_index(priv->steps, i); if (i <= idx) current += step->value; total += step->value; } if (total == 0) return 0; 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) { FuProgressPrivate *priv = GET_PRIVATE(self); gdouble offset; gdouble range; gdouble extra; guint parent_percentage; /* propagate up the stack if FuProgress has only one step */ if (priv->step_max == 1) { fu_progress_set_percentage(self, percentage); return; } /* did we call done on a self that did not have a size set? */ if (priv->step_max == 0) return; /* already at >= 100% */ if (priv->step_now >= priv->step_max) { g_warning("already at %u/%u step_max", priv->step_now, priv->step_max); return; } /* if the child finished, set the status back to the last parent status */ if (percentage == 100 && priv->steps->len > 0) { FuProgressStep *step = g_ptr_array_index(priv->steps, priv->step_now); fu_progress_set_status(self, step->status); } /* we have to deal with non-linear step_max */ if (priv->steps->len > 0) { /* we don't store zero */ if (priv->step_now == 0) { gdouble pc = fu_progress_get_step_percentage(self, 0); parent_percentage = percentage * pc / 100; } else { gdouble pc1 = fu_progress_get_step_percentage(self, priv->step_now - 1); gdouble pc2 = fu_progress_get_step_percentage(self, priv->step_now); /* bi-linearly interpolate */ parent_percentage = (((100 - percentage) * pc1) + (percentage * pc2)) / 100; } goto out; } /* get the offset */ offset = fu_progress_discrete_to_percent(priv->step_now, priv->step_max); /* get the range between the parent step and the next parent step */ range = fu_progress_discrete_to_percent(priv->step_now + 1, priv->step_max) - 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); out: fu_progress_set_percentage(self, parent_percentage); } static void fu_progress_set_parent(FuProgress *self, FuProgress *parent) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PROGRESS(self)); priv->parent = parent; /* no ref! */ priv->profile = fu_progress_get_profile(parent); } /** * 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) { FuProgressPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PROGRESS(self), NULL); g_return_val_if_fail(priv->id != NULL, NULL); /* already created child */ if (priv->child != NULL) return priv->child; /* connect up signals */ priv->child = fu_progress_new(NULL); priv->percentage_child_id = g_signal_connect(FU_PROGRESS(priv->child), "percentage-changed", G_CALLBACK(fu_progress_child_percentage_changed_cb), self); priv->status_child_id = g_signal_connect(FU_PROGRESS(priv->child), "status-changed", G_CALLBACK(fu_progress_child_status_changed_cb), self); fu_progress_set_parent(priv->child, self); return priv->child; } static void fu_progress_show_profile(FuProgress *self) { FuProgressPrivate *priv = GET_PRIVATE(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 (priv->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 < priv->step_max; i++) { FuProgressStep *step = g_ptr_array_index(priv->steps, i); g_string_append_printf(str, "%.3f, ", step->profile); } if (priv->step_max > 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 < priv->step_max; i++) { FuProgressStep *step = g_ptr_array_index(priv->steps, i); total_time += step->profile; } 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 < priv->step_max; i++) { FuProgressStep *step = g_ptr_array_index(priv->steps, i); g_string_append_printf(str, "%u ", step->value); } /* what we _should_ have set */ g_string_append_printf(str, "] but should have been [ "); for (guint i = 0; i < priv->step_max; i++) { FuProgressStep *step = g_ptr_array_index(priv->steps, i); g_string_append_printf(str, "%.0f ", step->profile / division); /* this is sufficiently different to what we guessed */ if (fabs((gdouble)step->value - step->profile / division) > 5) close_enough = FALSE; } g_string_append(str, "]"); if (priv->flags & FU_PROGRESS_FLAG_GUESSED) { #ifdef SUPPORTED_BUILD g_debug("%s at %s", str->str, priv->id); #else g_warning("%s at %s", str->str, priv->id); 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, priv->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) { FuProgressPrivate *priv = GET_PRIVATE(self); gdouble percentage; g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(priv->id != NULL); /* did we call done on a self that did not have a size set? */ if (priv->step_max == 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", priv->id, str->str); return; } /* save the duration in the array */ if (priv->profile) { if (priv->steps->len > 0) { FuProgressStep *step = g_ptr_array_index(priv->steps, priv->step_now); step->profile = g_timer_elapsed(priv->timer, NULL); } g_timer_start(priv->timer); } /* is already at 100%? */ if (priv->step_now >= priv->step_max) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(self, str, 0); g_warning("already at 100%% [%s]: %s", priv->id, str->str); return; } /* is child not at 100%? */ if (priv->child != NULL) { FuProgressPrivate *child_priv = GET_PRIVATE(priv->child); if (child_priv->step_now != child_priv->step_max) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(priv->child, str, 0); g_warning("child is at %u/%u step_max and parent done [%s]\n%s", child_priv->step_now, child_priv->step_max, priv->id, str->str); /* do not abort, as we want to clean this up */ } } /* another */ priv->step_now++; /* update status */ if (priv->steps->len > 0) { if (priv->step_now == priv->step_max) { fu_progress_set_status(self, FWUPD_STATUS_UNKNOWN); } else { FuProgressStep *step = g_ptr_array_index(priv->steps, priv->step_now); fu_progress_set_status(self, step->status); } } /* find new percentage */ if (priv->steps->len == 0) { percentage = fu_progress_discrete_to_percent(priv->step_now, priv->step_max); } else { percentage = fu_progress_get_step_percentage(self, priv->step_now - 1); } fu_progress_set_percentage(self, (guint)percentage); /* show any profiling stats */ if (priv->profile && priv->step_now == priv->step_max && priv->steps->len > 0) fu_progress_show_profile(self); /* reset child if it exists */ if (priv->child != NULL) fu_progress_reset(priv->child); } /** * fu_progress_sleep: * @self: a #FuProgress * @delay_ms: the delay in milliseconds * * Sleeps, setting the device progress from 0..100% as time continues. * * 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_init(FuProgress *self) { FuProgressPrivate *priv = GET_PRIVATE(self); priv->timer = g_timer_new(); priv->steps = g_ptr_array_new_with_free_func(g_free); } static void fu_progress_finalize(GObject *object) { FuProgress *self = FU_PROGRESS(object); FuProgressPrivate *priv = GET_PRIVATE(self); fu_progress_reset(self); g_free(priv->id); g_ptr_array_unref(priv->steps); g_timer_destroy(priv->timer); 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, G_STRUCT_OFFSET(FuProgressClass, percentage_changed), 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, G_STRUCT_OFFSET(FuProgressClass, status_changed), 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.7.5/libfwupdplugin/fu-progress.h000066400000000000000000000047611420024370600203370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define FU_TYPE_PROGRESS (fu_progress_get_type()) G_DECLARE_DERIVABLE_TYPE(FuProgress, fu_progress, FU, PROGRESS, GObject) struct _FuProgressClass { GObjectClass parent_class; /* signals */ void (*percentage_changed)(FuProgress *self, guint value); void (*status_changed)(FuProgress *self, FwupdStatus status); /*< private >*/ gpointer padding[29]; }; /** * FuProgressFlags: * * The progress internal flags. **/ typedef guint64 FuProgressFlags; /** * FU_PROGRESS_FLAG_NONE: * * No flags set. * * Since: 1.7.0 */ #define FU_PROGRESS_FLAG_NONE (0) /** * FU_PROGRESS_FLAG_UNKNOWN: * * Unknown flag value. * * Since: 1.7.0 */ #define FU_PROGRESS_FLAG_UNKNOWN G_MAXUINT64 /** * FU_PROGRESS_FLAG_GUESSED: * * The steps have not been measured on real hardware and have been guessed. * * Since: 1.7.0 */ #define FU_PROGRESS_FLAG_GUESSED (1ull << 0) /** * FU_PROGRESS_FLAG_NO_PROFILE: * * The steps cannot be accurate enough for a profile result. * * Since: 1.7.0 */ #define FU_PROGRESS_FLAG_NO_PROFILE (1ull << 1) FuProgress * fu_progress_new(const gchar *id); const gchar * fu_progress_get_id(FuProgress *self); void fu_progress_set_id(FuProgress *self, const gchar *id); const gchar * fu_progress_flag_to_string(FuProgressFlags flag); FuProgressFlags fu_progress_flag_from_string(const gchar *flag); void fu_progress_add_flag(FuProgress *self, FuProgressFlags flag); void fu_progress_remove_flag(FuProgress *self, FuProgressFlags flag); gboolean fu_progress_has_flag(FuProgress *self, FuProgressFlags flag); FwupdStatus fu_progress_get_status(FuProgress *self); void fu_progress_set_status(FuProgress *self, FwupdStatus status); void fu_progress_set_percentage(FuProgress *self, guint percentage); void fu_progress_set_percentage_full(FuProgress *self, gsize progress_done, gsize progress_total); guint fu_progress_get_percentage(FuProgress *self); void fu_progress_set_profile(FuProgress *self, gboolean profile); void fu_progress_reset(FuProgress *self); void fu_progress_set_steps(FuProgress *self, guint step_max); guint fu_progress_get_steps(FuProgress *self); void fu_progress_add_step(FuProgress *self, FwupdStatus status, guint value); void fu_progress_finished(FuProgress *self); void fu_progress_step_done(FuProgress *self); FuProgress * fu_progress_get_child(FuProgress *self); void fu_progress_sleep(FuProgress *self, guint delay_ms); fwupd-1.7.5/libfwupdplugin/fu-quirks.c000066400000000000000000000445121420024370600200020ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuQuirks" #include "config.h" #include #include #include #include #include "fwupd-common.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" #include "fu-common.h" #include "fu-mutex.h" #include "fu-quirks.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. * * 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) { if (value == NULL) return 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; } 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_autofree gchar *xml = NULL; g_auto(GStrv) groups = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GKeyFile) kf = g_key_file_new(); g_autoptr(XbBuilderNode) root = xb_builder_node_new("quirk"); /* parse keyfile */ bytes = xb_builder_source_ctx_get_bytes(ctx, cancellable, error); if (bytes == NULL) return NULL; if (!g_key_file_load_from_data(kf, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), G_KEY_FILE_NONE, error)) return NULL; /* add each set of groups and keys */ groups = g_key_file_get_groups(kf, NULL); for (guint i = 0; groups[i] != NULL; i++) { g_auto(GStrv) keys = NULL; g_autofree gchar *group_id = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbBuilderNode) bn = NULL; /* sanity check group */ if (g_str_has_prefix(groups[i], "HwID") || g_str_has_prefix(groups[i], "DeviceInstanceID") || g_str_has_prefix(groups[i], "GUID")) { g_warning("invalid group name '%s'", groups[i]); continue; } /* get all KVs for the entry */ keys = g_key_file_get_keys(kf, groups[i], NULL, error); if (keys == NULL) return NULL; group_id = fu_quirks_build_group_key(groups[i]); bn = xb_builder_node_insert(root, "device", "id", group_id, NULL); for (guint j = 0; keys[j] != NULL; j++) { g_autofree gchar *value = NULL; /* sanity check key */ if ((self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_VERIFY) == 0 && g_hash_table_lookup(self->possible_keys, keys[j]) == NULL) { if (!g_ptr_array_find_with_equal_func(self->invalid_keys, keys[j], g_str_equal, NULL)) { g_ptr_array_add(self->invalid_keys, g_strdup(keys[j])); } } value = g_key_file_get_value(kf, groups[i], keys[j], error); if (value == NULL) return NULL; /* sanity check flags */ if (g_strcmp0(keys[j], FU_QUIRKS_FLAGS) == 0) { if (!fu_quirks_validate_flags(value, &error_local)) { g_warning("[%s] %s = %s is invalid: %s", groups[i], keys[j], value, error_local->message); } } xb_builder_node_insert_text(bn, "value", value, "key", keys[j], NULL); } } /* export as XML */ xml = xb_builder_node_export(root, XB_NODE_EXPORT_FLAG_ADD_HEADER, error); if (xml == NULL) return NULL; return g_memory_input_stream_new_from_data(g_steal_pointer(&xml), -1, g_free); } 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); if (g_getenv("FWUPD_VERBOSE") != NULL) g_debug("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_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 */ #if LIBXMLB_CHECK_VERSION(0, 1, 15) xb_builder_source_add_simple_adapter(source, "text/plain,.quirk", fu_quirks_convert_quirk_to_xml_cb, self, NULL); #else xb_builder_source_add_adapter(source, "text/plain,.quirk", fu_quirks_convert_quirk_to_xml_cb, self, NULL); #endif 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_common_get_path(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_common_get_path(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_common_get_path(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_common_strjoin_array(",", self->invalid_keys); g_debug("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; } /* 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; #if LIBXMLB_CHECK_VERSION(0, 3, 0) g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); #endif 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 */ #if LIBXMLB_CHECK_VERSION(0, 3, 0) 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); #else if (!xb_query_bind_str(self->query_kv, 0, guid, &error)) { g_warning("failed to bind 0: %s", error->message); return NULL; } if (!xb_query_bind_str(self->query_kv, 1, key, &error)) { g_warning("failed to bind 1: %s", error->message); return NULL; } n = xb_silo_query_first_full(self->silo, self->query_kv, &error); #endif 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 * @iter_cb: (scope async): 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, FuQuirksIter iter_cb, gpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; #if LIBXMLB_CHECK_VERSION(0, 3, 0) g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); #endif 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 */ #if LIBXMLB_CHECK_VERSION(0, 3, 0) 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); results = xb_silo_query_with_context(self->silo, self->query_vs, &context, &error); #else if (!xb_query_bind_str(self->query_vs, 0, guid, &error)) { g_warning("failed to bind 0: %s", error->message); return FALSE; } results = xb_silo_query_full(self->silo, self->query_vs, &error); #endif 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_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, "CfiDeviceCmdReadId"); fu_quirks_add_possible_key(self, "CfiDeviceCmdReadIdSz"); fu_quirks_add_possible_key(self, "CfiDeviceCmdChipErase"); fu_quirks_add_possible_key(self, "CfiDeviceCmdSectorErase"); fu_quirks_add_possible_key(self, "CfiDevicePageSize"); fu_quirks_add_possible_key(self, "CfiDeviceSectorSize"); } 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.7.5/libfwupdplugin/fu-quirks.h000066400000000000000000000131171420024370600200040ustar00rootroot00000000000000/* * 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: 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; const gchar * fu_quirks_lookup_by_id(FuQuirks *self, const gchar *guid, const gchar *key); gboolean fu_quirks_lookup_by_id_iter(FuQuirks *self, const gchar *guid, FuQuirksIter iter_cb, gpointer user_data); void fu_quirks_add_possible_key(FuQuirks *self, const gchar *possible_key); /** * FU_QUIRKS_PLUGIN: * * The quirk key for the plugin name. * * Since: 1.3.7 **/ #define FU_QUIRKS_PLUGIN "Plugin" /** * FU_QUIRKS_FLAGS: * * The quirk key for the public flags. * * Since: 1.3.7 **/ #define FU_QUIRKS_FLAGS "Flags" /** * FU_QUIRKS_SUMMARY: * * The quirk key for the summary. * * Since: 1.3.7 **/ #define FU_QUIRKS_SUMMARY "Summary" /** * FU_QUIRKS_ICON: * * The quirk key for the icon. * * Since: 1.3.7 **/ #define FU_QUIRKS_ICON "Icon" /** * FU_QUIRKS_NAME: * * The quirk key for the name. * * 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. * * Since: 1.3.7 **/ #define FU_QUIRKS_GUID "Guid" /** * FU_QUIRKS_COUNTERPART_GUID: * * The quirk key for the counterpart GUID. * * Since: 1.3.7 **/ #define FU_QUIRKS_COUNTERPART_GUID "CounterpartGuid" /** * FU_QUIRKS_PARENT_GUID: * * The quirk key for the parent GUID. * * Since: 1.3.7 **/ #define FU_QUIRKS_PARENT_GUID "ParentGuid" /** * FU_QUIRKS_PROXY_GUID: * * The quirk key for the proxy GUID. * * 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. * * Since: 1.3.7 **/ #define FU_QUIRKS_CHILDREN "Children" /** * FU_QUIRKS_VERSION: * * The quirk key for the version. * * Since: 1.3.7 **/ #define FU_QUIRKS_VERSION "Version" /** * FU_QUIRKS_VENDOR: * * The quirk key for the vendor name. * * Since: 1.3.7 **/ #define FU_QUIRKS_VENDOR "Vendor" /** * FU_QUIRKS_VENDOR_ID: * * The quirk key for the vendor ID. * * 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. * * 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. * * 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. * * Since: 1.3.7 **/ #define FU_QUIRKS_FIRMWARE_SIZE "FirmwareSize" /** * FU_QUIRKS_INSTALL_DURATION: * * The quirk key for the install duration in seconds. * * 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. * * Since: 1.3.7 **/ #define FU_QUIRKS_GTYPE "GType" /** * FU_QUIRKS_FIRMWARE_GTYPE: * * The quirk key for the custom firmware GType. * * 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. * * Since: 1.4.1 **/ #define FU_QUIRKS_PRIORITY "Priority" /** * FU_QUIRKS_BATTERY_THRESHOLD: * * The quirk key for the battery threshold in percent. * * Since: 1.6.0 **/ #define FU_QUIRKS_BATTERY_THRESHOLD "BatteryThreshold" /** * FU_QUIRKS_REMOVE_DELAY: * * The quirk key for the device removal delay in seconds. * * Since: 1.5.0 **/ #define FU_QUIRKS_REMOVE_DELAY "RemoveDelay" /** * FU_QUIRKS_INHIBIT: * * The quirk key to inhibit the UPDATABLE flag and to set an update error. * * Since: 1.6.2 **/ #define FU_QUIRKS_INHIBIT "Inhibit" fwupd-1.7.5/libfwupdplugin/fu-security-attrs-private.h000066400000000000000000000020411420024370600231320ustar00rootroot00000000000000/* * 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); void fu_security_attrs_depsolve(FuSecurityAttrs *self); GVariant * fu_security_attrs_to_variant(FuSecurityAttrs *self); GPtrArray * fu_security_attrs_get_all(FuSecurityAttrs *self); void fu_security_attrs_append_internal(FuSecurityAttrs *self, FwupdSecurityAttr *attr); FwupdSecurityAttr * fu_security_attrs_get_by_appstream_id(FuSecurityAttrs *self, const gchar *appstream_id); fwupd-1.7.5/libfwupdplugin/fu-security-attrs.c000066400000000000000000000251071420024370600214650ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSecurityAttrs" #include "fu-security-attrs.h" #include #include #include "fwupd-security-attr-private.h" #include "fwupd-version.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 * * Gets a specific #FwupdSecurityAttr from the array. * * Returns: (transfer full): a #FwupdSecurityAttr or %NULL * * Since: 1.7.2 **/ FwupdSecurityAttr * fu_security_attrs_get_by_appstream_id(FuSecurityAttrs *self, const gchar *appstream_id) { 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 (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), appstream_id) == 0) return g_object_ref(attr); } 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 *security_attr = g_ptr_array_index(self->attrs, i); GVariant *tmp = fwupd_security_attr_to_variant(security_attr); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } /** * fu_security_attrs_get_all: * @self: a #FuSecurityAttrs * * Gets all the attributes in the object. * * Returns: (transfer container) (element-type FwupdSecurityAttr): attributes * * Since: 1.5.0 **/ GPtrArray * fu_security_attrs_get_all(FuSecurityAttrs *self) { g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); return g_ptr_array_ref(self->attrs); } /** * 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; 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) { hsi_number = j - 1; 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(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); } /** * 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_autoptr(GHashTable) attrs_by_id = NULL; g_return_if_fail(FU_IS_SECURITY_ATTRS(self)); /* make hash of ID -> object */ attrs_by_id = g_hash_table_new(g_str_hash, g_str_equal); for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); g_hash_table_insert(attrs_by_id, (gpointer)fwupd_security_attr_get_appstream_id(attr), (gpointer)attr); } /* set flat where required */ for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); GPtrArray *obsoletes = fwupd_security_attr_get_obsoletes(attr); for (guint j = 0; j < obsoletes->len; j++) { const gchar *obsolete = g_ptr_array_index(obsoletes, j); FwupdSecurityAttr *attr_tmp = g_hash_table_lookup(attrs_by_id, obsolete); /* by AppStream ID */ if (attr_tmp != NULL) { g_debug("security attr %s obsoleted by %s", obsolete, fwupd_security_attr_get_appstream_id(attr_tmp)); fwupd_security_attr_add_flag(attr_tmp, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED); } /* by plugin name */ for (guint k = 0; k < self->attrs->len; k++) { attr_tmp = g_ptr_array_index(self->attrs, k); if (g_strcmp0(obsolete, fwupd_security_attr_get_plugin(attr_tmp)) == 0) { g_debug("security attr %s obsoleted by %s", obsolete, fwupd_security_attr_get_appstream_id(attr_tmp)); fwupd_security_attr_add_flag( attr_tmp, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED); } } } } /* sort */ g_ptr_array_sort(self->attrs, fu_security_attrs_sort_cb); } /** * 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.7.5/libfwupdplugin/fu-security-attrs.h000066400000000000000000000007351420024370600214720ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); void fu_security_attrs_remove_all(FuSecurityAttrs *self); fwupd-1.7.5/libfwupdplugin/fu-self-test.c000066400000000000000000004035121420024370600203710ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fwupd-security-attr-private.h" #include "fu-cabinet.h" #include "fu-common-private.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-efi-firmware-file.h" #include "fu-efi-firmware-filesystem.h" #include "fu-efi-firmware-section.h" #include "fu-efi-firmware-volume.h" #include "fu-ifd-image.h" #include "fu-plugin-private.h" #include "fu-security-attrs-private.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_common_get_contents_bytes(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(GError) error = NULL; GBytes *data_tmp; #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_common_get_contents_bytes(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_tmp = fu_archive_lookup_by_fn(archive, "firmware.metainfo.xml", &error); g_assert_no_error(error); g_assert_nonnull(data_tmp); checksum1 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data_tmp); g_assert_cmpstr(checksum1, ==, "8611114f51f7151f190de86a5c9259d79ff34216"); data_tmp = fu_archive_lookup_by_fn(archive, "firmware.bin", &error); g_assert_no_error(error); g_assert_nonnull(data_tmp); checksum2 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data_tmp); g_assert_cmpstr(checksum2, ==, "7c0ae84b191822bcadbdcbe2f74a011695d783c7"); data_tmp = 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_tmp); } static void fu_common_gpt_type_func(void) { g_assert_cmpstr(fu_common_convert_to_gpt_type("0xef"), ==, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"); g_assert_cmpstr(fu_common_convert_to_gpt_type("0x0b"), ==, "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"); g_assert_cmpstr(fu_common_convert_to_gpt_type("fat32lba"), ==, "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"); g_assert_cmpstr(fu_common_convert_to_gpt_type("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_autoptr(GByteArray) array = g_byte_array_new(); 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); g_assert_cmpint(array->len, ==, 10); g_assert_cmpint(memcmp(array->data, "hello\0\0\0\0\0", array->len), ==, 0); } static void fu_common_crc_func(void) { guint8 buf[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; g_assert_cmpint(fu_common_crc8(buf, sizeof(buf)), ==, 0x7A); g_assert_cmpint(fu_common_crc16(buf, sizeof(buf)), ==, 0x4DF1); g_assert_cmpint(fu_common_crc32(buf, sizeof(buf)), ==, 0x40EFAB9E); } static void fu_common_string_append_kv_func(void) { g_autoptr(GString) str = g_string_new(NULL); fu_common_string_append_kv(str, 0, "hdr", NULL); fu_common_string_append_kv(str, 0, "key", "value"); fu_common_string_append_kv(str, 0, "key1", "value1"); fu_common_string_append_kv(str, 1, "key2", "value2"); fu_common_string_append_kv(str, 1, "", "value2"); fu_common_string_append_kv(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_common_version_guess_format_func(void) { g_assert_cmpint(fu_common_version_guess_format(NULL), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_common_version_guess_format(""), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_common_version_guess_format("1234ac"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint(fu_common_version_guess_format("1.2"), ==, FWUPD_VERSION_FORMAT_PAIR); g_assert_cmpint(fu_common_version_guess_format("1.2.3"), ==, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpint(fu_common_version_guess_format("1.2.3.4"), ==, FWUPD_VERSION_FORMAT_QUAD); g_assert_cmpint(fu_common_version_guess_format("1.2.3.4.5"), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_common_version_guess_format("1a.2b.3"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint(fu_common_version_guess_format("1"), ==, FWUPD_VERSION_FORMAT_NUMBER); g_assert_cmpint(fu_common_version_guess_format("0x10201"), ==, FWUPD_VERSION_FORMAT_NUMBER); } static void fu_device_version_format_func(void) { g_autoptr(FuDevice) device = fu_device_new(); 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(); 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(); g_autoptr(FuDevice) device2 = fu_device_new(); /* 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_probe(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(); /* 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_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; /* these tests will not write */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); 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_smbios_to_string(smbios); if (g_getenv("FWUPD_VERBOSE") != NULL) 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_smbios3_func(void) { const gchar *str; gboolean ret; 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); if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *dump = fu_smbios_to_string(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_smbios_dt_func(void) { const gchar *str; gboolean ret; g_autofree gchar *path = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; path = g_test_build_filename(G_TEST_DIST, "tests", "devicetree", "base", NULL); smbios = fu_smbios_new(); ret = fu_smbios_setup_from_path(smbios, path, &error); g_assert_no_error(error); g_assert_true(ret); if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *dump = fu_smbios_to_string(smbios); g_debug("%s", dump); } /* get vendor */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "Hughski Limited"); } static void fu_smbios_dt_fallback_func(void) { const gchar *str; gboolean ret; g_autofree gchar *path = NULL; g_autoptr(FuSmbios) smbios = fu_smbios_new(); g_autoptr(GError) error = NULL; path = g_test_build_filename(G_TEST_DIST, "tests", "devicetree-fallback", "base", NULL); ret = fu_smbios_setup_from_path(smbios, path, &error); g_assert_no_error(error); g_assert_true(ret); if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *dump = fu_smbios_to_string(smbios); g_debug("%s", dump); } /* get vendor */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "solidrun"); /* get model */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "honeycomb"); } static void fu_smbios_class_func(void) { g_autofree gchar *path = g_test_build_filename(G_TEST_DIST, "tests", "dmi", "class", NULL); g_autoptr(FuSmbios) smbios = fu_smbios_new(); g_autoptr(GError) error = NULL; gboolean ret; const gchar *str; guint8 byte; ret = fu_smbios_setup_from_kernel(smbios, path, &error); g_assert_no_error(error); g_assert_true(ret); if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *dump = fu_smbios_to_string(smbios); g_debug("%s", dump); } str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 4, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "FwupdTest"); byte = fu_smbios_get_integer(smbios, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 5, &error); g_assert_no_error(error); g_assert_cmpuint(byte, ==, 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_common_strnsplit_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_common_strnsplit_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_common_strnsplit_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_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}}; for (guint i = 0; strs[i].in != NULL; i++) { g_autofree gchar *tmp = fu_common_strsafe(strs[i].in, 7); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_common_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_common_uri_get_scheme(strs[i].in); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_hwids_func(void) { g_autofree gchar *testdatadir = NULL; g_autoptr(FuHwids) hwids = NULL; g_autoptr(FuSmbios) smbios = NULL; 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}}; /* these tests will not write */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); 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); hwids = fu_hwids_new(); ret = fu_hwids_setup(hwids, smbios, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_MANUFACTURER), ==, "LENOVO"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_ENCLOSURE_KIND), ==, "a"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_FAMILY), ==, "ThinkPad T440s"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_PRODUCT_NAME), ==, "20ARS19C0C"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_BIOS_VENDOR), ==, "LENOVO"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_BIOS_VERSION), ==, "GJET75WW (2.25 )"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE), ==, "02"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_BIOS_MINOR_RELEASE), ==, "19"); g_assert_cmpstr(fu_hwids_get_value(hwids, FU_HWIDS_KEY_PRODUCT_SKU), ==, "LENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s"); for (guint i = 0; guids[i].key != NULL; i++) { 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_hwids_has_guid(hwids, 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_plugin_devices_func(void) { g_autoptr(FuDevice) device = fu_device_new(); g_autoptr(FuDevice) child = fu_device_new(); 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(); g_autoptr(FuDevice) child1 = fu_device_new(); g_autoptr(FuDevice) child2 = fu_device_new(); 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(); 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_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); } static void fu_plugin_quirks_device_func(void) { FuDevice *device_tmp; GPtrArray *children; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(); 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(device, "USB\\VID_0BDA&PID_1100"); fu_device_convert_instance_ids(device); g_assert_cmpstr(fu_device_get_name(device), ==, "Hub"); /* 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); g_setenv("FWUPD_SYSFSSECURITYDIR", old_kernel_dir, TRUE); ret = fu_common_kernel_locked_down(); g_assert_false(ret); locked_dir = g_test_build_filename(G_TEST_DIST, "tests", "lockdown", "locked", NULL); g_setenv("FWUPD_SYSFSSECURITYDIR", locked_dir, TRUE); ret = fu_common_kernel_locked_down(); g_assert_true(ret); none_dir = g_test_build_filename(G_TEST_DIST, "tests", "lockdown", "none", NULL); g_setenv("FWUPD_SYSFSSECURITYDIR", none_dir, TRUE); ret = fu_common_kernel_locked_down(); g_assert_false(ret); } static void fu_common_firmware_builder_func(void) { const gchar *data; g_autofree gchar *archive_fn = NULL; g_autoptr(GBytes) archive_blob = NULL; g_autoptr(GBytes) firmware_blob = NULL; g_autoptr(GError) error = NULL; /* get test file */ archive_fn = g_test_build_filename(G_TEST_BUILT, "tests", "builder", "firmware.tar", NULL); archive_blob = fu_common_get_contents_bytes(archive_fn, &error); g_assert_no_error(error); g_assert_nonnull(archive_blob); /* generate the firmware */ firmware_blob = fu_common_firmware_builder(archive_blob, "startup.sh", "firmware.bin", &error); if (firmware_blob == NULL) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { g_test_skip("Missing permissions to create namespace in container"); return; } if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("User namespaces not supported in container"); return; } g_assert_no_error(error); } /* check it */ data = g_bytes_get_data(firmware_blob, NULL); g_assert_cmpstr(data, ==, "xobdnas eht ni gninnur"); } static void fu_test_stdout_cb(const gchar *line, gpointer user_data) { guint *lines = (guint *)user_data; g_debug("got '%s'", line); (*lines)++; } 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(); 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_spawn_func(void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[3] = {"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[0] = fn; ret = fu_common_spawn_sync(argv, fu_test_stdout_cb, &lines, 0, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(lines, ==, 6); } static void fu_common_spawn_timeout_func(void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[3] = {"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[0] = fn; ret = fu_common_spawn_sync(argv, fu_test_stdout_cb, &lines, 50, 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_common_endian_func(void) { guint8 buf[2]; fu_common_write_uint16(buf, 0x1234, G_LITTLE_ENDIAN); g_assert_cmpint(buf[0], ==, 0x34); g_assert_cmpint(buf[1], ==, 0x12); g_assert_cmpint(fu_common_read_uint16(buf, G_LITTLE_ENDIAN), ==, 0x1234); fu_common_write_uint16(buf, 0x1234, G_BIG_ENDIAN); g_assert_cmpint(buf[0], ==, 0x12); g_assert_cmpint(buf[1], ==, 0x34); g_assert_cmpint(fu_common_read_uint16(buf, G_BIG_ENDIAN), ==, 0x1234); } static GBytes * _build_cab(GCabCompression compression, ...) { gboolean ret; va_list args; g_autoptr(GCabCabinet) cabinet = NULL; g_autoptr(GCabFolder) cabfolder = NULL; g_autoptr(GError) error = NULL; g_autoptr(GOutputStream) op = NULL; /* create a new archive */ cabinet = gcab_cabinet_new(); cabfolder = gcab_folder_new(compression); ret = gcab_cabinet_add_folder(cabinet, cabfolder, &error); g_assert_no_error(error); g_assert_true(ret); /* add each file */ va_start(args, compression); do { const gchar *fn; const gchar *text; g_autoptr(GCabFile) cabfile = NULL; 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)); cabfile = gcab_file_new_with_bytes(fn, blob); ret = gcab_folder_add_file(cabfolder, cabfile, FALSE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); } while (TRUE); va_end(args); /* write the archive to a blob */ op = g_memory_output_stream_new_resizable(); ret = gcab_cabinet_write_simple(cabinet, op, NULL, NULL, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = g_output_stream_close(op, NULL, &error); g_assert_no_error(error); g_assert_true(ret); return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(op)); } 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_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; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab( GCAB_COMPRESSION_NONE, "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_common_cab_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); #if LIBXMLB_CHECK_VERSION(0, 2, 0) 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); #else rel = xb_node_query_first(component, "releases/release", &error); #endif 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( GCAB_COMPRESSION_NONE, "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_common_cab_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(GCAB_COMPRESSION_NONE, "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_common_cab_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(GCAB_COMPRESSION_NONE, "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_common_cab_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(GCAB_COMPRESSION_NONE, "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_common_cab_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; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab(GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_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); #if LIBXMLB_CHECK_VERSION(0, 2, 0) 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); #else rel = xb_node_query_first(component, "releases/release", &error); #endif 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( GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " 486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_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; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif /* create silo */ blob = _build_cab(GCAB_COMPRESSION_NONE, "lvfs\\acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "lvfs\\firmware.bin", "world", NULL); silo = fu_common_cab_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); #if LIBXMLB_CHECK_VERSION(0, 2, 0) 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); #else rel = xb_node_query_first(component, "releases/release", &error); #endif 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(GCAB_COMPRESSION_NONE, "foo.txt", "hello", "bar.txt", "world", NULL); silo = fu_common_cab_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(GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " 7004701\n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_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(GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } 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_common_get_contents_bytes(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 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(GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_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(GCAB_COMPRESSION_NONE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_common_cab_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } 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(); 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(); 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_with_context(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(); g_autoptr(FuDevice) dev2 = fu_device_new(); g_autoptr(FuDevice) dev3 = fu_device_new(); g_autoptr(FuDevice) dev4 = fu_device_new(); /* 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(); 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"); /* first one */ fu_device_inhibit(device, "needs-activation", "Device is pending 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_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(); 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(); 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); fu_device_add_private_flag(device, TEST_FLAG_BAZ); g_assert_cmpint(fu_device_get_private_flags(device), ==, TEST_FLAG_FOO | TEST_FLAG_BAZ); tmp = fu_device_to_string(device); g_assert_cmpstr(tmp, ==, "FuDevice:\n" " Flags: none\n" " CustomFlags: baz\n" " PrivateFlags: foo\n"); } static void fu_device_flags_func(void) { g_autoptr(FuDevice) device = fu_device_new(); /* 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); } static void fu_device_children_func(void) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) child = fu_device_new(); g_autoptr(FuDevice) parent = fu_device_new_with_context(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(); g_autoptr(FuDevice) child_root = NULL; g_autoptr(FuDevice) grandparent = fu_device_new(); g_autoptr(FuDevice) grandparent_root = NULL; g_autoptr(FuDevice) parent = fu_device_new(); 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) { g_autoptr(FuDevice) device = fu_device_new(); g_autoptr(FuDevice) donor = fu_device_new(); /* 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"); /* 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); } 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(); g_autoptr(FuDevice) dev2 = fu_device_new(); 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, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_coldplug(backend, &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_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_autoptr(GPtrArray) chunked1 = NULL; g_autoptr(GPtrArray) chunked2 = NULL; g_autoptr(GPtrArray) chunked3 = NULL; g_autoptr(GPtrArray) chunked4 = 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"); 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_common_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_common_strstrip(map[i].old); g_assert_cmpstr(tmp, ==, map[i].new); } } static void fu_common_version_semver_func(void) { struct { const gchar *old; const gchar *new; } map[] = {{"1.2.3", "1.2.3"}, {"1.2-3", "1.2.3"}, {"1~2-3", "1.2.3"}, {".1.2", "1.2"}, {"1.2.", "1.2"}, {"1..2", "1.2"}, {"CBET1.2.3", "1.2.3"}, {"1.2.3alpha", "1.2.3"}, {"5", "5"}, {"\t5\n", "5"}, {"0x123456", "0.18.13398"}, {"coreboot-unknown", NULL}, {"", NULL}, {" ", NULL}, {NULL, NULL}}; for (guint i = 0; map[i].old != NULL; i++) { g_autofree gchar *tmp = fu_common_version_ensure_semver(map[i].old); g_assert_cmpstr(tmp, ==, map[i].new); } } static void fu_common_strtoull_func(void) { gboolean ret; guint64 val = 0; g_autoptr(GError) error = NULL; ret = fu_common_strtoull_full("123", &val, 123, 200, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 123); ret = fu_common_strtoull_full("0x123", &val, 0, 0x123, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 0x123); ret = fu_common_strtoull_full(NULL, &val, 0, G_MAXUINT32, NULL); g_assert_false(ret); ret = fu_common_strtoull_full("", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_common_strtoull_full("124", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_common_strtoull_full("119", &val, 120, 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}, {0, NULL}}; 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}, {0, NULL}}; 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}, {0, NULL}}; 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"}, {NULL, NULL}}; /* check version conversion */ for (i = 0; version_from_uint64[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_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; version_from_uint32[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_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; version_from_uint16[i].ver != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_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; version_parse[i].old != NULL; i++) { g_autofree gchar *ver = NULL; ver = fu_common_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_common_vercmp_full("1.2.3", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint( fu_common_vercmp_full("001.002.003", "001.002.003", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_common_vercmp_full("0x00000002", "0x2", FWUPD_VERSION_FORMAT_HEX), ==, 0); /* upgrade and downgrade */ g_assert_cmpint(fu_common_vercmp_full("1.2.3", "1.2.4", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint( fu_common_vercmp_full("001.002.000", "001.002.009", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3", "1.2.2", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); g_assert_cmpint( fu_common_vercmp_full("001.002.009", "001.002.000", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* unequal depth */ g_assert_cmpint(fu_common_vercmp_full("1.2.3", "1.2.3.1", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3.1", "1.2.4", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); /* mixed-alpha-numeric */ g_assert_cmpint(fu_common_vercmp_full("1.2.3a", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3a", "1.2.3b", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3b", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha version append */ g_assert_cmpint(fu_common_vercmp_full("1.2.3", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3a", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha only */ g_assert_cmpint(fu_common_vercmp_full("alpha", "alpha", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_common_vercmp_full("alpha", "beta", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("beta", "alpha", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha-compare */ g_assert_cmpint(fu_common_vercmp_full("1.2a.3", "1.2a.3", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_common_vercmp_full("1.2a.3", "1.2b.3", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2b.3", "1.2a.3", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* tilde is all-powerful */ g_assert_cmpint( fu_common_vercmp_full("1.2.3~rc1", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3~rc1", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_common_vercmp_full("1.2.3", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); g_assert_cmpint( fu_common_vercmp_full("1.2.3~rc2", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* invalid */ g_assert_cmpint(fu_common_vercmp_full("1", NULL, FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); g_assert_cmpint(fu_common_vercmp_full(NULL, "1", FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); g_assert_cmpint(fu_common_vercmp_full(NULL, NULL, FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); } 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_common_get_contents_bytes(filename_hex, &error); g_assert_no_error(error); g_assert_nonnull(data_file); ret = fu_firmware_parse(firmware, data_file, FWUPD_INSTALL_FLAG_NONE, &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_common_get_contents_bytes(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_common_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-continous 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_common_get_contents_bytes(filename_shex, &error); g_assert_no_error(error); g_assert_nonnull(data_file); ret = fu_firmware_parse(firmware, data_file, FWUPD_INSTALL_FLAG_NONE, &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_NONE, &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_common_get_contents_bytes(filename_srec, &error); g_assert_no_error(error); g_assert_nonnull(data_srec); ret = fu_firmware_parse(firmware, data_srec, FWUPD_INSTALL_FLAG_NONE, &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_common_get_contents_bytes(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_common_bytes_compare(data_bin, data_ref, &error); g_assert_no_error(error); g_assert_true(ret); } 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_common_get_contents_bytes(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); 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_common_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_common_get_contents_bytes(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_common_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_common_get_contents_bytes(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); /* dfu -> FuDfuFirmware */ firmware1 = fu_firmware_new_from_gtypes(blob, 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, 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, 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_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_common_get_contents_bytes(filename_dfu, &error); g_assert_no_error(error); g_assert_nonnull(data_dfu); ret = fu_firmware_parse(firmware, data_dfu, FWUPD_INSTALL_FLAG_NONE, &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_common_get_contents_bytes(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_common_bytes_compare(data_bin, data_ref, &error); g_assert_no_error(error); g_assert_true(ret); } 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_common_get_contents_bytes(filename_dfu, &error); g_assert_no_error(error); g_assert_nonnull(data_dfu); ret = fu_firmware_parse(firmware, data_dfu, FWUPD_INSTALL_FLAG_NONE, &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_common_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_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) { 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) 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_idx(img1_old, 13); fu_firmware_set_id(img1_old, "DAVE"); fu_firmware_add_image(firmware, img1_old); 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"); } 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); 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(); 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(); 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(); 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_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 *expected_hsi8 = 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 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); hsi4 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi4, ==, "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); hsi5 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi5, ==, "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); hsi6 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi6, ==, "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); hsi7 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi7, ==, "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); hsi8 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_ADD_VERSION); expected_hsi8 = g_strdup_printf("HSI:3! (v%d.%d.%d)", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); g_assert_cmpstr(hsi8, ==, expected_hsi8); g_clear_object(&attr); } static void fu_firmware_dfuse_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_dfuse_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_dfuse_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "dfuse.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, ==, "c1ff429f0e381c8fe8e1b2ee41a5a9a79e2f2ff7"); /* 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_firmware_srec_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_srec_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_srec_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "srec.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); } static void fu_firmware_ihex_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_ihex_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_ihex_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "ihex.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, ==, "a8d74f767f3fc992b413e5ba801cedc80a4cf013"); /* 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_firmware_fmap_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_fmap_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_fmap_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "fmap.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, ==, "a0b9ffc10a586d217edf9e9bae7c1fe7c564ea01"); /* 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_efi_firmware_section_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_efi_firmware_section_new(); g_autoptr(FuFirmware) firmware2 = fu_efi_firmware_section_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "efi-firmware-section.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); } static void fu_efi_firmware_file_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_efi_firmware_file_new(); g_autoptr(FuFirmware) firmware2 = fu_efi_firmware_file_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "efi-firmware-file.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, ==, "1002c14b29a76069f3b7e35c50a55d2b0d197441"); /* 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_efi_firmware_filesystem_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_efi_firmware_filesystem_new(); g_autoptr(FuFirmware) firmware2 = fu_efi_firmware_filesystem_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "efi-firmware-filesystem.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, ==, "d6fbadc1c303a3b4eede9db7fb0ddb353efffc86"); /* 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_efi_firmware_volume_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_efi_firmware_volume_new(); g_autoptr(FuFirmware) firmware2 = fu_efi_firmware_volume_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "efi-firmware-volume.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); } static void fu_ifd_image_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_ifd_image_new(); g_autoptr(FuFirmware) firmware2 = fu_ifd_image_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "ifd.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, ==, "aebfb3845c9bc638de30360f5ece156958918ca2"); /* 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); } 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_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_progress_percentage_changed_cb), &helper); fu_progress_set_steps(progress, 5); fu_progress_step_done(progress); g_assert_cmpint(helper.updates, ==, 1); g_assert_cmpint(helper.last_percentage, ==, 20); for (guint i = 0; i < 4; i++) fu_progress_step_done(progress); g_assert_cmpint(helper.last_percentage, ==, 100); g_assert_cmpint(helper.updates, ==, 5); } 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_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_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 60); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 20); 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); fu_progress_add_step(child, FWUPD_STATUS_DEVICE_WRITE, 75); 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); fu_progress_add_step(grandchild, FWUPD_STATUS_DEVICE_WRITE, 10); 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); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; 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); g_setenv("G_MESSAGES_DEBUG", "all", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_DATADIR", testdatadir, TRUE); g_setenv("FWUPD_PLUGINDIR", testdatadir, TRUE); g_setenv("FWUPD_SYSCONFDIR", testdatadir, TRUE); g_setenv("FWUPD_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); g_test_add_func("/fwupd/common{strnsplit}", fu_common_strnsplit_func); g_test_add_func("/fwupd/common{memmem}", fu_common_memmem_func); g_test_add_func("/fwupd/progress", fu_progress_func); g_test_add_func("/fwupd/progress{child}", fu_progress_child_func); 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/security-attrs{hsi}", fu_security_attrs_hsi_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{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/common{align-up}", fu_common_align_up_func); g_test_add_func("/fwupd/common{gpt-type}", fu_common_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_common_string_append_kv_func); g_test_add_func("/fwupd/common{version-guess-format}", fu_common_version_guess_format_func); g_test_add_func("/fwupd/common{strtoull}", fu_common_strtoull_func); g_test_add_func("/fwupd/common{version}", fu_common_version_func); g_test_add_func("/fwupd/common{version-semver}", fu_common_version_semver_func); g_test_add_func("/fwupd/common{vercmp}", fu_common_vercmp_func); g_test_add_func("/fwupd/common{strstrip}", fu_common_strstrip_func); g_test_add_func("/fwupd/common{endian}", fu_common_endian_func); g_test_add_func("/fwupd/common{cabinet}", fu_common_cabinet_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_func("/fwupd/common{bytes-get-data}", fu_common_bytes_get_data_func); g_test_add_func("/fwupd/common{spawn)", fu_common_spawn_func); g_test_add_func("/fwupd/common{spawn-timeout)", fu_common_spawn_timeout_func); g_test_add_func("/fwupd/common{firmware-builder}", fu_common_firmware_builder_func); g_test_add_func("/fwupd/common{kernel-lockdown}", fu_common_kernel_lockdown_func); g_test_add_func("/fwupd/common{strsafe}", fu_common_strsafe_func); g_test_add_func("/fwupd/common{uri-scheme}", fu_common_uri_scheme_func); g_test_add_func("/fwupd/efivar", fu_efivar_func); g_test_add_func("/fwupd/hwids", fu_hwids_func); g_test_add_func("/fwupd/smbios", fu_smbios_func); g_test_add_func("/fwupd/smbios3", fu_smbios3_func); g_test_add_func("/fwupd/smbios{dt}", fu_smbios_dt_func); g_test_add_func("/fwupd/smbios{dt-fallback}", fu_smbios_dt_fallback_func); g_test_add_func("/fwupd/smbios{class}", fu_smbios_class_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{dedupe}", fu_firmware_dedupe_func); g_test_add_func("/fwupd/firmware{build}", fu_firmware_build_func); g_test_add_func("/fwupd/firmware{ihex}", fu_firmware_ihex_func); g_test_add_func("/fwupd/firmware{ihex-xml}", fu_firmware_ihex_xml_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{srec-xml}", fu_firmware_srec_xml_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{dfuse-xml}", fu_firmware_dfuse_xml_func); g_test_add_func("/fwupd/firmware{fmap}", fu_firmware_fmap_func); g_test_add_func("/fwupd/firmware{fmap-xml}", fu_firmware_fmap_xml_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{custom-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("/efi/firmware-section{xml}", fu_efi_firmware_section_xml_func); g_test_add_func("/efi/firmware-file{xml}", fu_efi_firmware_file_xml_func); g_test_add_func("/efi/firmware-filesystem{xml}", fu_efi_firmware_filesystem_xml_func); g_test_add_func("/efi/firmware-volume{xml}", fu_efi_firmware_volume_xml_func); g_test_add_func("/ifd/image{xml}", fu_ifd_image_xml_func); return g_test_run(); } fwupd-1.7.5/libfwupdplugin/fu-smbios-private.h000066400000000000000000000011731420024370600214310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-smbios.h" gboolean fu_smbios_setup(FuSmbios *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_smbios_setup_from_path(FuSmbios *self, const gchar *path, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_smbios_setup_from_file(FuSmbios *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_smbios_setup_from_kernel(FuSmbios *self, const gchar *path, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.7.5/libfwupdplugin/fu-smbios.c000066400000000000000000000731231420024370600177600ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSmbios" #include "config.h" #include #include #ifdef _WIN32 #include #endif #include "fwupd-error.h" #include "fu-common.h" #include "fu-kenv.h" #include "fu-smbios-private.h" /** * FuSmbios: * * Enumerate the SMBIOS data on the system, either using DMI or Device Tree. * * See also: [class@FuHwids] */ struct _FuSmbios { FuFirmware parent_instance; guint32 structure_table_len; GPtrArray *items; }; /* little endian */ typedef struct __attribute__((packed)) { gchar anchor_str[4]; guint8 entry_point_csum; guint8 entry_point_len; guint8 smbios_major_ver; guint8 smbios_minor_ver; guint16 max_structure_sz; guint8 entry_point_rev; guint8 formatted_area[5]; gchar intermediate_anchor_str[5]; guint8 intermediate_csum; guint16 structure_table_len; guint32 structure_table_addr; guint16 number_smbios_structs; guint8 smbios_bcd_rev; } FuSmbiosStructureEntryPoint32; /* little endian */ typedef struct __attribute__((packed)) { gchar anchor_str[5]; guint8 entry_point_csum; guint8 entry_point_len; guint8 smbios_major_ver; guint8 smbios_minor_ver; guint8 smbios_docrev; guint8 entry_point_rev; guint8 reserved0; guint32 structure_table_len; guint64 structure_table_addr; } FuSmbiosStructureEntryPoint64; typedef struct { guint8 type; guint16 handle; GByteArray *buf; GPtrArray *strings; } FuSmbiosItem; G_DEFINE_TYPE(FuSmbios, fu_smbios, FU_TYPE_FIRMWARE) static void fu_smbios_set_integer(FuSmbios *self, guint8 type, guint8 offset, guint8 value) { FuSmbiosItem *item = g_ptr_array_index(self->items, type); for (guint i = item->buf->len; i < (guint)offset + 1; i++) fu_byte_array_append_uint8(item->buf, 0x0); item->buf->data[offset] = value; } static void fu_smbios_set_string(FuSmbios *self, guint8 type, guint8 offset, const gchar *buf, gssize bufsz) { FuSmbiosItem *item = g_ptr_array_index(self->items, type); /* NUL terminated UTF-8 */ if (bufsz < 0) bufsz = strlen(buf); /* add value to string table */ g_ptr_array_add(item->strings, g_strndup(buf, (gsize)bufsz)); fu_smbios_set_integer(self, type, offset, item->strings->len); } static gboolean fu_smbios_convert_dt_string(FuSmbios *self, guint8 type, guint8 offset, const gchar *path, const gchar *subpath) { gsize bufsz = 0; g_autofree gchar *fn = g_build_filename(path, subpath, NULL); g_autofree gchar *buf = NULL; /* not found */ if (!g_file_get_contents(fn, &buf, &bufsz, NULL)) return FALSE; if (bufsz == 0) return FALSE; fu_smbios_set_string(self, type, offset, buf, (gssize)bufsz); return TRUE; } static gchar ** fu_smbios_convert_dt_string_array(FuSmbios *self, const gchar *path, const gchar *subpath) { gsize bufsz = 0; g_autofree gchar *fn = g_build_filename(path, subpath, NULL); g_autofree gchar *buf = NULL; g_auto(GStrv) split = NULL; /* not found */ if (!g_file_get_contents(fn, &buf, &bufsz, NULL)) return NULL; if (bufsz == 0) return NULL; /* return only if valid */ split = g_strsplit(buf, ",", -1); if (g_strv_length(split) == 0) return NULL; /* success */ return g_steal_pointer(&split); } #ifdef HAVE_KENV_H static gboolean fu_smbios_convert_kenv_string(FuSmbios *self, guint8 type, guint8 offset, const gchar *sminfo, GError **error) { g_autofree gchar *value = fu_kenv_get_string(sminfo, error); if (value == NULL) return FALSE; fu_smbios_set_string(self, type, offset, value, -1); return TRUE; } static gboolean fu_smbios_setup_from_kenv(FuSmbios *self, GError **error) { gboolean is_valid = FALSE; g_autoptr(GError) error_local = NULL; /* add all four faked structures */ for (guint i = 0; i < FU_SMBIOS_STRUCTURE_TYPE_LAST; i++) { FuSmbiosItem *item = g_new0(FuSmbiosItem, 1); item->type = i; item->buf = g_byte_array_new(); item->strings = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(self->items, item); } /* DMI:Manufacturer */ if (!fu_smbios_convert_kenv_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, "smbios.bios.vendor", &error_local)) { g_debug("ignoring: %s", error_local->message); g_clear_error(&error_local); } else { is_valid = TRUE; } /* DMI:BiosVersion */ if (!fu_smbios_convert_kenv_string(self, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x05, "smbios.bios.version", &error_local)) { g_debug("ignoring: %s", error_local->message); g_clear_error(&error_local); } else { is_valid = TRUE; } /* DMI:Family */ if (!fu_smbios_convert_kenv_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, "smbios.system.family", &error_local)) { g_debug("ignoring: %s", error_local->message); g_clear_error(&error_local); } else { is_valid = TRUE; } /* DMI:ProductName */ if (!fu_smbios_convert_kenv_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, "smbios.planar.product", &error_local)) { g_debug("ignoring: %s", error_local->message); g_clear_error(&error_local); } else { is_valid = TRUE; } /* DMI:BaseboardManufacturer */ if (!fu_smbios_convert_kenv_string(self, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x04, "smbios.planar.maker", &error_local)) { g_debug("ignoring: %s", error_local->message); g_clear_error(&error_local); } else { is_valid = TRUE; } /* we got no data */ if (!is_valid) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "no SMBIOS information provided"); return FALSE; } /* success */ return TRUE; } #endif static gboolean fu_smbios_setup_from_path_dt(FuSmbios *self, const gchar *path, GError **error) { gboolean has_family; gboolean has_model; gboolean has_vendor; g_autofree gchar *fn_battery = NULL; /* add all four faked structures */ for (guint i = 0; i < FU_SMBIOS_STRUCTURE_TYPE_LAST; i++) { FuSmbiosItem *item = g_new0(FuSmbiosItem, 1); item->type = i; item->buf = g_byte_array_new(); item->strings = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(self->items, item); } /* if it has a battery it is portable (probably a laptop) */ fn_battery = g_build_filename(path, "battery", NULL); if (g_file_test(fn_battery, G_FILE_TEST_EXISTS)) { fu_smbios_set_integer(self, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05, FU_SMBIOS_CHASSIS_KIND_PORTABLE); } /* DMI:Manufacturer */ has_vendor = fu_smbios_convert_dt_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, path, "vendor"); /* DMI:Family */ has_family = fu_smbios_convert_dt_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, path, "model-name"); /* DMI:ProductName */ has_model = fu_smbios_convert_dt_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, path, "model"); /* fall back to the first compatible string if required */ if (!has_vendor || !has_model || !has_family) { g_auto(GStrv) parts = NULL; /* NULL if invalid, otherwise we're sure this has size of exactly 3 */ parts = fu_smbios_convert_dt_string_array(self, path, "compatible"); if (parts != NULL) { if (!has_vendor && g_strv_length(parts) > 0) { fu_smbios_set_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x4, parts[0], -1); } if (!has_model && g_strv_length(parts) > 1) { fu_smbios_set_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, parts[1], -1); } if (!has_family && g_strv_length(parts) > 2) { fu_smbios_set_string(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, parts[2], -1); } } } /* DMI:BiosVersion */ fu_smbios_convert_dt_string(self, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x05, path, "ibm,firmware-versions/version"); /* DMI:BaseboardManufacturer */ fu_smbios_convert_dt_string(self, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x04, path, "vpd/root-node-vpd@a000/enclosure@1e00/backplane@800/vendor"); /* DMI:BaseboardProduct */ fu_smbios_convert_dt_string( self, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x05, path, "vpd/root-node-vpd@a000/enclosure@1e00/backplane@800/part-number"); return TRUE; } static gboolean fu_smbios_setup_from_data(FuSmbios *self, const guint8 *buf, gsize sz, GError **error) { /* go through each structure */ for (gsize i = 0; i < sz; i++) { FuSmbiosItem *item; guint16 str_handle = 0; guint8 str_len = 0; guint8 str_type = 0; /* le */ if (!fu_common_read_uint8_safe(buf, sz, i + 0x0, &str_type, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sz, i + 0x1, &str_len, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, sz, i + 0x2, &str_handle, G_LITTLE_ENDIAN, error)) return FALSE; /* invalid */ if (str_len == 0x00) break; if (i + str_len >= sz) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "structure larger than available data"); return FALSE; } /* create a new result */ item = g_new0(FuSmbiosItem, 1); item->type = str_type; item->handle = GUINT16_FROM_LE(str_handle); item->buf = g_byte_array_sized_new(str_len); item->strings = g_ptr_array_new_with_free_func(g_free); g_byte_array_append(item->buf, buf + i, str_len); g_ptr_array_add(self->items, item); /* jump to the end of the struct */ i += str_len; if (buf[i] == '\0' && buf[i + 1] == '\0') { i++; continue; } /* add strings from table */ for (gsize start_offset = i; i < sz; i++) { if (buf[i] == '\0') { if (start_offset == i) break; g_ptr_array_add(item->strings, g_strdup((const gchar *)&buf[start_offset])); start_offset = i + 1; } } } 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_autofree gchar *basename = 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); /* use a heuristic */ basename = g_path_get_basename(filename); if (g_strcmp0(basename, "base") == 0) return fu_smbios_setup_from_path_dt(self, filename, error); /* 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_encode_string_from_kernel(FuSmbios *self, const gchar *file_contents, guint8 type, guint8 offset, GError **error) { fu_smbios_set_string(self, type, offset, file_contents, -1); return TRUE; } static gboolean fu_smbios_encode_byte_from_kernel(FuSmbios *self, const gchar *file_contents, guint8 type, guint8 offset, GError **error) { gchar *endp; gint64 value = g_ascii_strtoll(file_contents, &endp, 10); if (*endp != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "non-numeric values in numeric string: %s", endp); return FALSE; } if (value < 0 || value > G_MAXUINT8) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "value \"%s\" is not representable in a byte", file_contents); return FALSE; } fu_smbios_set_integer(self, type, offset, value); return TRUE; } /* * The mapping from SMBIOS field to sysfs name can be found by mapping * the field to a kernel property name in dmi_decode() * (drivers/firmware/dmi_scan.c), then the property name to sysfs entry * in dmi_id_init_attr_table() (drivers/firmware/dmi-id.c). This table * lists each attribute exposed in /sys/class/dmi when CONFIG_DMIID is * enabled, mapping to the SMBIOS field and a function that can convert * the textual version of the field back into the raw SMBIOS table * representation. */ #define SYSFS_DMI_FIELD(_name, _type, _offset, kind) \ { \ .name = _name, .type = _type, .offset = _offset, \ .encode = fu_smbios_encode_##kind##_from_kernel \ } const struct kernel_dmi_field { const gchar *name; gboolean (*encode)(FuSmbios *, const gchar *, guint8, guint8, GError **); guint8 type; guint8 offset; } KERNEL_DMI_FIELDS[] = { SYSFS_DMI_FIELD("bios_vendor", 0, 4, string), SYSFS_DMI_FIELD("bios_version", 0, 5, string), SYSFS_DMI_FIELD("bios_date", 0, 8, string), SYSFS_DMI_FIELD("sys_vendor", 1, 4, string), SYSFS_DMI_FIELD("product_name", 1, 5, string), SYSFS_DMI_FIELD("product_version", 1, 6, string), SYSFS_DMI_FIELD("product_serial", 1, 7, string), /* SYSFS_DMI_FIELD("product_uuid", 1, 8, uuid) */ SYSFS_DMI_FIELD("product_family", 1, 26, string), SYSFS_DMI_FIELD("product_sku", 1, 25, string), SYSFS_DMI_FIELD("board_vendor", 2, 4, string), SYSFS_DMI_FIELD("board_name", 2, 5, string), SYSFS_DMI_FIELD("board_version", 2, 6, string), SYSFS_DMI_FIELD("board_serial", 2, 7, string), SYSFS_DMI_FIELD("board_asset_tag", 2, 8, string), SYSFS_DMI_FIELD("chassis_vendor", 3, 4, string), SYSFS_DMI_FIELD("chassis_type", 3, 5, byte), SYSFS_DMI_FIELD("chassis_version", 3, 6, string), SYSFS_DMI_FIELD("chassis_serial", 3, 7, string), SYSFS_DMI_FIELD("chassis_asset_tag", 3, 8, string), }; /** * fu_smbios_setup_from_kernel: * @self: a #FuSmbios * @path: a directory path * @error: (nullable): optional return location for an error * * Reads SMBIOS value from DMI values provided by the kernel, such as in * /sys/class/dmi on Linux. * * Returns: %TRUE for success * * Since: 1.6.2 **/ gboolean fu_smbios_setup_from_kernel(FuSmbios *self, const gchar *path, GError **error) { gboolean any_success = FALSE; /* add fake structures */ for (guint i = 0; i < FU_SMBIOS_STRUCTURE_TYPE_LAST; i++) { FuSmbiosItem *item = g_new0(FuSmbiosItem, 1); item->type = i; item->buf = g_byte_array_new(); item->strings = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(self->items, item); } /* parse every known field from the corresponding file */ for (gsize i = 0; i < G_N_ELEMENTS(KERNEL_DMI_FIELDS); i++) { const struct kernel_dmi_field *field = &KERNEL_DMI_FIELDS[i]; gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = g_build_filename(path, field->name, NULL); g_autoptr(GError) local_error = NULL; if (!g_file_get_contents(fn, &buf, &bufsz, &local_error)) { g_debug("unable to read SMBIOS data from %s: %s", fn, local_error->message); continue; } /* trim trailing newline added by kernel */ if (buf[bufsz - 1] == '\n') buf[bufsz - 1] = 0; if (!field->encode(self, buf, field->type, field->offset, &local_error)) { g_warning("failed to parse SMBIOS data from %s: %s", fn, local_error->message); continue; } any_success = TRUE; } if (!any_success) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read any SMBIOS values from %s", path); return FALSE; } return TRUE; } static gboolean fu_smbios_parse_ep32(FuSmbios *self, const gchar *buf, gsize sz, GError **error) { FuSmbiosStructureEntryPoint32 *ep; guint8 csum = 0; g_autofree gchar *version_str = NULL; /* verify size */ if (sz != sizeof(FuSmbiosStructureEntryPoint32)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios entry point got %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32)); return FALSE; } /* verify checksum */ for (guint i = 0; i < sz; 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 */ ep = (FuSmbiosStructureEntryPoint32 *)buf; if (memcmp(ep->intermediate_anchor_str, "_DMI_", 5) != 0) { g_autofree gchar *tmp = g_strndup(ep->intermediate_anchor_str, 5); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "intermediate anchor signature invalid, got %s", tmp); return FALSE; } for (guint i = 10; i < sz; 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 = GUINT16_FROM_LE(ep->structure_table_len); version_str = g_strdup_printf("%u.%u", ep->smbios_major_ver, ep->smbios_minor_ver); fu_firmware_set_version(FU_FIRMWARE(self), version_str); fu_firmware_set_version_raw(FU_FIRMWARE(self), (((guint16)ep->smbios_major_ver) << 8) + ep->smbios_minor_ver); return TRUE; } static gboolean fu_smbios_parse_ep64(FuSmbios *self, const gchar *buf, gsize sz, GError **error) { FuSmbiosStructureEntryPoint64 *ep; guint8 csum = 0; g_autofree gchar *version_str = NULL; /* verify size */ if (sz != sizeof(FuSmbiosStructureEntryPoint64)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios3 entry point got %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32)); return FALSE; } /* verify checksum */ for (guint i = 0; i < sz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "entry point checksum invalid"); return FALSE; } ep = (FuSmbiosStructureEntryPoint64 *)buf; self->structure_table_len = GUINT32_FROM_LE(ep->structure_table_len); version_str = g_strdup_printf("%u.%u", ep->smbios_major_ver, ep->smbios_minor_ver); fu_firmware_set_version(FU_FIRMWARE(self), version_str); return TRUE; } static gboolean fu_smbios_setup_from_path_dmi(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); /* 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 %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT " or %" G_GSIZE_FORMAT, sz, sizeof(FuSmbiosStructureEntryPoint32), sizeof(FuSmbiosStructureEntryPoint64)); return FALSE; } /* parse 32 bit structure */ if (memcmp(ep_raw, "_SM_", 4) == 0) { if (!fu_smbios_parse_ep32(self, ep_raw, sz, error)) return FALSE; } else if (memcmp(ep_raw, "_SM3_", 5) == 0) { if (!fu_smbios_parse_ep64(self, 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, guint64 addr_start, guint64 addr_end, 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); } /** * 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) { g_autofree gchar *basename = 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); /* use a heuristic */ basename = g_path_get_basename(path); if (g_strcmp0(basename, "base") == 0) return fu_smbios_setup_from_path_dt(self, path, error); return fu_smbios_setup_from_path_dmi(self, path, 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 *path_dt = NULL; g_autofree gchar *sysfsfwdir = NULL; const gchar *path_dmi_class = "/sys/class/dmi/id"; g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); sysfsfwdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW); /* DMI */ path = g_build_filename(sysfsfwdir, "dmi", "tables", NULL); if (g_file_test(path, G_FILE_TEST_EXISTS)) { g_autoptr(GError) error_local = NULL; 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); } } /* the values the kernel parsed; these are world-readable */ if (g_file_test(path_dmi_class, G_FILE_TEST_IS_DIR)) { g_debug("trying to read %s", path_dmi_class); return fu_smbios_setup_from_kernel(self, path_dmi_class, error); } /* DT */ path_dt = g_build_filename(sysfsfwdir, "devicetree", "base", NULL); if (g_file_test(path_dt, G_FILE_TEST_EXISTS)) return fu_smbios_setup_from_path(self, path_dt, error); #ifdef HAVE_KENV_H /* kenv */ return fu_smbios_setup_from_kenv(self, error); #endif /* neither found */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "neither SMBIOS or DT found"); return FALSE; #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_common_strsafe(tmp, 20); xb_builder_node_insert_text(bc, "string", value, "idx", title, NULL); } } } /** * fu_smbios_to_string: * @self: a #FuSmbios * * Dumps the parsed SMBIOS data to a string. * * Returns: a UTF-8 string * * Since: 1.0.0 **/ gchar * fu_smbios_to_string(FuSmbios *self) { g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); return fu_firmware_to_string(FU_FIRMWARE(self)); } 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; } /** * 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 a SMBIOS data blob, which includes the SMBIOS section header. * * Returns: (transfer full): a #GBytes, or %NULL if invalid or not found * * Since: 1.0.0 **/ GBytes * fu_smbios_get_data(FuSmbios *self, guint8 type, GError **error) { FuSmbiosItem *item; g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); 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; } return g_bytes_new(item->buf->data, item->buf->len); } /** * 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.7.5/libfwupdplugin/fu-smbios.h000066400000000000000000000060071420024370600177620ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; gchar * fu_smbios_to_string(FuSmbios *self); const gchar * fu_smbios_get_string(FuSmbios *self, guint8 type, guint8 offset, GError **error); guint fu_smbios_get_integer(FuSmbios *self, guint8 type, guint8 offset, GError **error); GBytes * fu_smbios_get_data(FuSmbios *self, guint8 type, GError **error); fwupd-1.7.5/libfwupdplugin/fu-srec-firmware.c000066400000000000000000000414711420024370600212330ustar00rootroot00000000000000/* * Copyright (C) 2019 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-srec-firmware.h" /** * FuSrecFirmware: * * A SREC firmware image. * * See also: [class@FuFirmware] */ typedef struct { GPtrArray *records; } 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; } 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) (element-type FuSrecFirmwareRecord): records * * 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; 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_common_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: addrsz = 2; break; case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: addrsz = 2; break; case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: addrsz = 3; break; case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: addrsz = 4; 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(); } if (g_getenv("FU_SREC_FIRMWARE_VERBOSE") != NULL) { g_debug("line %03u S%u addr:0x%04x datalen:0x%02x", token_idx + 1, rec_kind, rec_addr32, (guint)rec_count - addrsz - 1); } /* 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_common_strnsplit_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, guint64 addr_start, guint64 addr_end, 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 (guint8 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 < addr_start) { g_debug( "ignoring data at 0x%x as before start address 0x%x at line %u", (guint)rcd->addr, (guint)addr_start, 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 GBytes * fu_srec_firmware_write(FuFirmware *firmware, GError **error) { 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(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new_from_bytes(buf_blob, fu_firmware_get_addr(firmware), 0x0, 64); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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 (chunks->len > G_MAXUINT16) kind_coun = FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24; fu_srec_firmware_write_line(str, kind_coun, chunks->len, NULL, 0); } /* EOF */ fu_srec_firmware_write_line(str, kind_term, 0x0, NULL, 0); /* success */ return g_string_free_to_bytes(g_steal_pointer(&str)); } 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.7.5/libfwupdplugin/fu-srec-firmware.h000066400000000000000000000040561420024370600212360ustar00rootroot00000000000000/* * 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); GPtrArray * fu_srec_firmware_get_records(FuSrecFirmware *self); GType fu_srec_firmware_record_get_type(void); FuSrecFirmwareRecord * fu_srec_firmware_record_new(guint ln, FuFirmareSrecRecordKind kind, guint32 addr); fwupd-1.7.5/libfwupdplugin/fu-udev-device-private.h000066400000000000000000000003151420024370600223320ustar00rootroot00000000000000/* * 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); fwupd-1.7.5/libfwupdplugin/fu-udev-device.c000066400000000000000000001617211420024370600206660ustar00rootroot00000000000000/* * 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-udev-device-private.h" /** * FuUdevDevice: * * A UDev device, typically only available on Linux. * * See also: [class@FuDevice] */ typedef struct { GUdevDevice *udev_device; guint32 vendor; guint32 model; guint32 subsystem_vendor; guint32 subsystem_model; guint8 revision; gchar *subsystem; gchar *bind_id; gchar *driver; gchar *device_file; gint fd; 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); } static guint32 fu_udev_device_get_sysfs_attr_as_uint32(GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull(g_udev_device_get_sysfs_attr(udev_device, name)); if (tmp > G_MAXUINT32) { g_warning("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path(udev_device)); return G_MAXUINT32; } return tmp; #else return G_MAXUINT32; #endif } static guint8 fu_udev_device_get_sysfs_attr_as_uint8(GUdevDevice *udev_device, const gchar *name) { #ifdef HAVE_GUDEV guint64 tmp = fu_common_strtoull(g_udev_device_get_sysfs_attr(udev_device, name)); if (tmp > G_MAXUINT8) { g_warning("reading %s for %s overflowed", name, g_udev_device_get_sysfs_path(udev_device)); return G_MAXUINT8; } return tmp; #else return G_MAXUINT8; #endif } #ifdef HAVE_GUDEV static void fu_udev_device_to_string_raw(GUdevDevice *udev_device, guint idt, GString *str) { const gchar *const *keys; if (udev_device == NULL) return; keys = g_udev_device_get_property_keys(udev_device); for (guint i = 0; keys[i] != NULL; i++) { fu_common_string_append_kv(str, idt, keys[i], g_udev_device_get_property(udev_device, keys[i])); } keys = g_udev_device_get_sysfs_attr_keys(udev_device); for (guint i = 0; keys[i] != NULL; i++) { fu_common_string_append_kv(str, idt, keys[i], g_udev_device_get_sysfs_attr(udev_device, keys[i])); } } #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->udev_device != NULL) { fu_common_string_append_kv(str, idt, "SysfsPath", g_udev_device_get_sysfs_path(priv->udev_device)); fu_common_string_append_kv(str, idt, "Subsystem", priv->subsystem); if (priv->driver != NULL) fu_common_string_append_kv(str, idt, "Driver", priv->driver); if (priv->bind_id != NULL) fu_common_string_append_kv(str, idt, "BindId", priv->bind_id); if (priv->device_file != NULL) fu_common_string_append_kv(str, idt, "DeviceFile", priv->device_file); } if (g_getenv("FU_UDEV_DEVICE_DEBUG") != NULL) { g_autoptr(GUdevDevice) udev_parent = NULL; fu_udev_device_to_string_raw(priv->udev_device, idt, str); udev_parent = g_udev_device_get_parent(priv->udev_device); if (udev_parent != NULL) { fu_common_string_append_kv(str, idt, "Parent", NULL); fu_udev_device_to_string_raw(udev_parent, idt + 1, str); } } #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"); } static 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) { g_autofree gchar *devid = NULL; g_autofree gchar *id_safe = NULL; /* this prefix is not useful */ if (g_str_has_prefix(tmp, "PNP: ")) tmp += 5; id_safe = g_utf8_strup(tmp, -1); g_strdelimit(id_safe, " /\\\"", '-'); devid = g_strdup_printf("SERIO\\FWID_%s", id_safe); fu_device_add_instance_id(FU_DEVICE(self), devid); } return TRUE; } #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; /* set ven:dev:rev */ priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(priv->udev_device, "revision"); priv->subsystem_vendor = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "subsystem_vendor"); priv->subsystem_model = fu_udev_device_get_sysfs_attr_as_uint32(priv->udev_device, "subsystem_device"); #ifdef HAVE_GUDEV /* fallback to the parent */ udev_parent = g_udev_device_get_parent(priv->udev_device); if (udev_parent != NULL && priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0) { priv->vendor = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(udev_parent, "revision"); priv->subsystem_vendor = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "subsystem_vendor"); priv->subsystem_model = fu_udev_device_get_sysfs_attr_as_uint32(udev_parent, "subsystem_device"); } /* 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_MAXUINT32) { 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); } } /* 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) { 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 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); } /* 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 && priv->model != 0x0000 && priv->subsystem_vendor != 0x0000 && priv->subsystem_model != 0x0000) { g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; devid1 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X&REV_%02X", subsystem, priv->vendor, priv->model, priv->subsystem_vendor, priv->subsystem_model, priv->revision); fu_device_add_instance_id(device, devid1); devid2 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X", subsystem, priv->vendor, priv->model, priv->subsystem_vendor, priv->subsystem_model); fu_device_add_instance_id(device, devid2); } if (priv->vendor != 0x0000 && priv->model != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&REV_%02X", subsystem, priv->vendor, priv->model, priv->revision); fu_device_add_instance_id(device, devid); } if (priv->vendor != 0x0000 && priv->model != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&DEV_%04X", subsystem, priv->vendor, priv->model); fu_device_add_instance_id(device, devid); } if (priv->vendor != 0x0000) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X", subsystem, priv->vendor); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add device class */ tmp = g_udev_device_get_sysfs_attr(priv->udev_device, "class"); if (tmp != NULL && g_str_has_prefix(tmp, "0x")) { g_autofree gchar *class_id = g_utf8_strup(tmp + 2, -1); g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\VEN_%04X&CLASS_%s", subsystem, priv->vendor, class_id); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add devtype */ tmp = g_udev_device_get_devtype(priv->udev_device); if (tmp != NULL) { g_autofree gchar *devtype = g_utf8_strup(tmp, -1); g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\TYPE_%s", subsystem, devtype); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add the driver */ if (priv->driver != NULL) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("%s\\DRIVER_%s", subsystem, priv->driver); fu_device_add_instance_id_full(device, devid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* add subsystem to match in plugins */ if (subsystem != NULL) { fu_device_add_instance_id_full(device, subsystem, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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); } #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); g_set_object(&priv->udev_device, udev_device_phys); fu_device_set_metadata(FU_DEVICE(self), "ParentSubsystem", g_udev_device_get_subsystem(udev_device)); } else { g_set_object(&priv->udev_device, udev_device); } #else g_set_object(&priv->udev_device, 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 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); 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_get_dev: * @self: a #FuUdevDevice * * Gets the #GUdevDevice. * * 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); 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) return fu_common_strtoull(g_udev_device_get_number(priv->udev_device)); #endif return G_MAXUINT64; } /** * 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 **/ guint32 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 device code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint32 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 **/ guint32 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 **/ guint32 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); } #endif /** * fu_udev_device_set_physical_id: * @self: a #FuUdevDevice * @subsystems: a subsystem string, e.g. `pci,usb` * @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. * * 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 *subsystem = NULL; const gchar *tmp; g_autofree gchar *physical_id = 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 in turn */ split = g_strsplit(subsystems, ",", -1); for (guint i = 0; split[i] != NULL; i++) { subsystem = split[i]; if (g_strcmp0(priv->subsystem, subsystem) == 0) { udev_device = g_object_ref(priv->udev_device); break; } udev_device = g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL); if (udev_device != NULL) 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) { 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 DEVPATH"); 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_fd: * @self: a #FuUdevDevice * * Gets the file descriptor if the device is open. * * Returns: positive integer, or -1 if the device is not open * * Since: 1.3.3 **/ gint fu_udev_device_get_fd(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), -1); return priv->fd; } /** * fu_udev_device_set_fd: * @self: a #FuUdevDevice * @fd: a valid file descriptor * * Replace the file descriptor to use when the device has already been opened. * This object will automatically close() @fd when fu_device_close() is called. * * Since: 1.3.3 **/ void fu_udev_device_set_fd(FuUdevDevice *self, gint fd) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UDEV_DEVICE(self)); if (priv->fd > 0) close(priv->fd); priv->fd = fd; } /** * 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); /* open device */ if (priv->device_file != NULL && priv->flags != FU_UDEV_DEVICE_FLAG_NONE) { gint flags; 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 priv->fd = g_open(priv->device_file, flags, 0); if (priv->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, strerror(errno)); return FALSE; } } /* 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); /* close device */ if (priv->fd > 0) { if (!g_close(priv->fd, error)) return FALSE; priv->fd = 0; } /* success */ 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 * @error: (nullable): optional return location for an error * * Control a device using a low-level request. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_ioctl(FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, GError **error) { #ifdef HAVE_IOCTL_H FuUdevDevicePrivate *priv = GET_PRIVATE(self); gint rc_tmp; 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->fd == 0) { 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; } rc_tmp = ioctl(priv->fd, request, buf); 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; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ioctl error: %s", strerror(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_full: * @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.4.5 **/ gboolean fu_udev_device_pread_full(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->fd == 0) { 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(priv->fd, buf, bufsz, port) != (gssize)bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read from port 0x%04x: %s", (guint)port, 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->fd == 0) { 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(priv->fd, offset, SEEK_SET) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to seek to 0x%04x: %s", (guint)offset, 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_full: * @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.4.5 **/ gboolean fu_udev_device_pwrite_full(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->fd == 0) { 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(priv->fd, buf, bufsz, port) != (gssize)bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write to port %04x: %s", (guint)port, 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_pwrite: * @self: a #FuUdevDevice * @port: offset address * @data: value * @error: (nullable): optional return location for an error * * Write to a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pwrite(FuUdevDevice *self, goffset port, guint8 data, GError **error) { return fu_udev_device_pwrite_full(self, port, &data, 0x01, error); } /** * 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, "not supported"); 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; guint64 tmp64; 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; tmp64 = fu_common_strtoull(tmp); if (value != NULL) *value = tmp64; return TRUE; } /** * fu_udev_device_pread: * @self: a #FuUdevDevice * @port: offset address * @data: (out): value * @error: (nullable): optional return location for an error * * Read from a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *data, GError **error) { return fu_udev_device_pread_full(self, port, data, 0x1, 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 *const subsystem) { g_autoptr(GPtrArray) out = g_ptr_array_new_with_free_func(g_object_unref); #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GUdevDevice) udev_parent = g_udev_device_get_parent(priv->udev_device); const gchar *udev_parent_path = g_udev_device_get_sysfs_path(udev_parent); 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 = g_udev_device_get_parent(enumerated_device); const gchar *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) { FuUdevDevice *dev = fu_udev_device_new_with_context(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_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 = g_udev_device_get_parent(enumerated_device); const gchar *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_with_context(fu_device_get_context(FU_DEVICE(self)), g_steal_pointer(&enumerated_device)); g_ptr_array_add(out, dev); } } #endif return g_steal_pointer(&out); } 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->fd > 0) g_close(priv->fd, NULL); 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; } 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; /** * 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_with_context: * @ctx: (nullable): a #FuContext * @udev_device: a #GUdevDevice * * Creates a new #FuUdevDevice. * * Returns: (transfer full): a #FuUdevDevice * * Since: 1.7.1 **/ FuUdevDevice * fu_udev_device_new_with_context(FuContext *ctx, GUdevDevice *udev_device) { #ifdef HAVE_GUDEV /* create the correct object depending on the subsystem */ if (g_strcmp0(g_udev_device_get_subsystem(udev_device), "i2c-dev") == 0) { return g_object_new(FU_TYPE_I2C_DEVICE, "context", ctx, "udev-device", udev_device, NULL); } #endif return g_object_new(FU_TYPE_UDEV_DEVICE, "context", ctx, "udev-device", udev_device, NULL); } /** * fu_udev_device_new: * @udev_device: a #GUdevDevice * * Creates a new #FuUdevDevice. * * Returns: (transfer full): a #FuUdevDevice * * Since: 1.1.2 **/ FuUdevDevice * fu_udev_device_new(GUdevDevice *udev_device) { return fu_udev_device_new_with_context(NULL, udev_device); } fwupd-1.7.5/libfwupdplugin/fu-udev-device.h000066400000000000000000000110401420024370600206570ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #ifdef HAVE_GUDEV #include #else #define G_UDEV_TYPE_DEVICE G_TYPE_OBJECT #define GUdevDevice GObject #endif #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; gpointer __reserved[31]; }; /** * 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 fallback from the parent * @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 * * 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, /*< private >*/ FU_UDEV_DEVICE_FLAG_LAST } FuUdevDeviceFlags; FuUdevDevice * fu_udev_device_new(GUdevDevice *udev_device) G_DEPRECATED_FOR(fu_udev_device_new_with_context); FuUdevDevice * fu_udev_device_new_with_context(FuContext *ctx, GUdevDevice *udev_device); GUdevDevice * fu_udev_device_get_dev(FuUdevDevice *self); void fu_udev_device_set_dev(FuUdevDevice *self, GUdevDevice *udev_device); const gchar * fu_udev_device_get_device_file(FuUdevDevice *self); const gchar * fu_udev_device_get_sysfs_path(FuUdevDevice *self); const gchar * fu_udev_device_get_subsystem(FuUdevDevice *self); const gchar * fu_udev_device_get_bind_id(FuUdevDevice *self); void fu_udev_device_set_bind_id(FuUdevDevice *self, const gchar *bind_id); const gchar * fu_udev_device_get_driver(FuUdevDevice *self); guint32 fu_udev_device_get_vendor(FuUdevDevice *self); guint32 fu_udev_device_get_model(FuUdevDevice *self); guint32 fu_udev_device_get_subsystem_vendor(FuUdevDevice *self); guint32 fu_udev_device_get_subsystem_model(FuUdevDevice *self); guint8 fu_udev_device_get_revision(FuUdevDevice *self); guint64 fu_udev_device_get_number(FuUdevDevice *self); guint fu_udev_device_get_slot_depth(FuUdevDevice *self, const gchar *subsystem); gboolean fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_set_logical_id(FuUdevDevice *self, const gchar *subsystem, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_udev_device_set_flags(FuUdevDevice *self, FuUdevDeviceFlags flags); gint fu_udev_device_get_fd(FuUdevDevice *self); void fu_udev_device_set_fd(FuUdevDevice *self, gint fd); gboolean fu_udev_device_ioctl(FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_pwrite(FuUdevDevice *self, goffset port, guint8 data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_pwrite_full(FuUdevDevice *self, goffset port, const guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *data, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_pread_full(FuUdevDevice *self, goffset port, guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_udev_device_seek(FuUdevDevice *self, goffset offset, GError **error) G_GNUC_WARN_UNUSED_RESULT; const gchar * fu_udev_device_get_sysfs_attr(FuUdevDevice *self, const gchar *attr, GError **error); gboolean fu_udev_device_get_sysfs_attr_uint64(FuUdevDevice *self, const gchar *attr, guint64 *value, GError **error); gchar * fu_udev_device_get_parent_name(FuUdevDevice *self); gboolean fu_udev_device_write_sysfs(FuUdevDevice *self, const gchar *attribute, const gchar *val, GError **error) G_GNUC_WARN_UNUSED_RESULT; const gchar * fu_udev_device_get_devtype(FuUdevDevice *self); GPtrArray * fu_udev_device_get_siblings_with_subsystem(FuUdevDevice *self, const gchar *subsystem); GPtrArray * fu_udev_device_get_children_with_subsystem(FuUdevDevice *self, const gchar *subsystem); fwupd-1.7.5/libfwupdplugin/fu-usb-device-private.h000066400000000000000000000003261420024370600221620ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" const gchar * fu_usb_device_get_platform_id(FuUsbDevice *self); fwupd-1.7.5/libfwupdplugin/fu-usb-device.c000066400000000000000000000563241420024370600205160ustar00rootroot00000000000000/* * 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-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 */ 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)) 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); } static void fu_usb_device_init(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); priv->configuration = -1; #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 } /** * 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_autofree gchar *devid = 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; } if (g_getenv("FU_USB_DEVICE_DEBUG") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "HUB_DT", data, sz); /* see http://www.usblyzer.com/usb-hub-class-decoder.htm */ if (sz == 0x09) { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&HUB_%02X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), data[7]); fu_device_add_instance_id(FU_DEVICE(self), devid); } else if (sz == 0x0c) { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&HUB_%02X%02X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), data[11], data[10]); fu_device_add_instance_id(FU_DEVICE(self), devid); } return TRUE; } #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 */ locker = fu_device_locker_new(priv->usb_device, error); if (locker == NULL) 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)) 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 (!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_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); g_autoptr(GError) error_local = NULL; if (!iface->claimed) continue; if (!g_usb_device_release_interface(priv->usb_device, iface->number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &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; } 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 *devid0 = NULL; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; g_autofree gchar *platform_id = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GPtrArray) intfs = NULL; /* 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) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint16(release, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version(device, version); } /* add GUIDs in order of priority */ devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X&REV_%04X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), release); fu_device_add_instance_id(device, devid2); devid1 = g_strdup_printf("USB\\VID_%04X&PID_%04X", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device)); fu_device_add_instance_id(device, devid1); devid0 = g_strdup_printf("USB\\VID_%04X", g_usb_device_get_vid(priv->usb_device)); fu_device_add_instance_id_full(device, devid0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* add the interface GUIDs */ intfs = g_usb_device_get_interfaces(priv->usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); g_autofree gchar *intid1 = NULL; g_autofree gchar *intid2 = NULL; g_autofree gchar *intid3 = NULL; intid1 = g_strdup_printf("USB\\CLASS_%02X&SUBCLASS_%02X&PROT_%02X", g_usb_interface_get_class(intf), g_usb_interface_get_subclass(intf), g_usb_interface_get_protocol(intf)); fu_device_add_instance_id_full(device, intid1, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid2 = g_strdup_printf("USB\\CLASS_%02X&SUBCLASS_%02X", g_usb_interface_get_class(intf), g_usb_interface_get_subclass(intf)); fu_device_add_instance_id_full(device, intid2, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); intid3 = g_strdup_printf("USB\\CLASS_%02X", g_usb_interface_get_class(intf)); fu_device_add_instance_id_full(device, intid3, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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); } #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) { #if G_USB_CHECK_VERSION(0, 3, 1) 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 */ 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 /* 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_with_context(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_with_context(fu_device_get_context(device), dev); return fu_device_unbind_driver(FU_DEVICE(udev_device), error); } /** * fu_usb_device_new_with_context: * @ctx: (nullable): a #FuContext * @usb_device: a USB device * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.7.1 **/ FuUsbDevice * fu_usb_device_new_with_context(FuContext *ctx, GUsbDevice *usb_device) { return g_object_new(FU_TYPE_USB_DEVICE, "context", ctx, "usb-device", usb_device, NULL); } /** * fu_usb_device_new: * @usb_device: a USB device * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.0.2 **/ FuUsbDevice * fu_usb_device_new(GUsbDevice *usb_device) { return fu_usb_device_new_with_context(NULL, usb_device); } #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_common_string_append_kx(str, idt, "Configuration", priv->configuration); 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_common_string_append_kv(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_common_string_append_kv(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; device_class->open = fu_usb_device_open; device_class->setup = fu_usb_device_setup; 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.7.5/libfwupdplugin/fu-usb-device.h000066400000000000000000000025411420024370600205130ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; gpointer __reserved[31]; }; FuUsbDevice * fu_usb_device_new(GUsbDevice *usb_device) G_DEPRECATED_FOR(fu_usb_device_new_with_context); FuUsbDevice * fu_usb_device_new_with_context(FuContext *ctx, GUsbDevice *usb_device); guint16 fu_usb_device_get_vid(FuUsbDevice *self); guint16 fu_usb_device_get_pid(FuUsbDevice *self); guint16 fu_usb_device_get_spec(FuUsbDevice *self); GUsbDevice * fu_usb_device_get_dev(FuUsbDevice *device); void fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device); gboolean fu_usb_device_is_open(FuUsbDevice *device); GUdevDevice * fu_usb_device_find_udev_device(FuUsbDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_usb_device_set_configuration(FuUsbDevice *device, gint configuration); void fu_usb_device_add_interface(FuUsbDevice *device, guint8 number); fwupd-1.7.5/libfwupdplugin/fu-version.c000066400000000000000000000010641420024370600201440ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-version.h.in000066400000000000000000000023371420024370600205620ustar00rootroot00000000000000/* * 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.7.5/libfwupdplugin/fu-volume-private.h000066400000000000000000000004601420024370600214420ustar00rootroot00000000000000/* * 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); fwupd-1.7.5/libfwupdplugin/fu-volume.c000066400000000000000000000243241420024370600177720ustar00rootroot00000000000000/* * 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 "fwupd-error.h" #include "fu-volume-private.h" /** * FuVolume: * * Volume abstraction that uses UDisks */ struct _FuVolume { GObject parent_instance; GDBusProxy *proxy_blk; GDBusProxy *proxy_fs; gchar *mount_path; /* only when mounted ourselves */ }; enum { PROP_0, PROP_MOUNT_PATH, PROP_PROXY_BLOCK, PROP_PROXY_FILESYSTEM, 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); 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; 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; 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); } 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); return NULL; } /** * 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(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); if (val == NULL) 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); } fwupd-1.7.5/libfwupdplugin/fu-volume.h000066400000000000000000000024601420024370600177740ustar00rootroot00000000000000/* * 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. * * Since: 1.5.0 **/ #define FU_VOLUME_KIND_ESP "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" /** * FU_VOLUME_KIND_BDP: * * The GUID for the BDP. * * Since: 1.5.3 **/ #define FU_VOLUME_KIND_BDP "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" const gchar * fu_volume_get_id(FuVolume *self); gboolean fu_volume_check_free_space(FuVolume *self, guint64 required, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_volume_is_mounted(FuVolume *self); gboolean fu_volume_is_encrypted(FuVolume *self); gchar * fu_volume_get_mount_point(FuVolume *self); gboolean fu_volume_mount(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_volume_unmount(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuDeviceLocker * fu_volume_locker(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_volume_is_internal(FuVolume *self); gchar * fu_volume_get_id_type(FuVolume *self); fwupd-1.7.5/libfwupdplugin/fwupdplugin.h000066400000000000000000000036141420024370600204230ustar00rootroot00000000000000/* * 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 #ifndef FWUPD_DISABLE_DEPRECATED #include #endif #undef __FWUPDPLUGIN_H_INSIDE__ fwupd-1.7.5/libfwupdplugin/fwupdplugin.map000066400000000000000000000613161420024370600207540ustar00rootroot00000000000000# generated automatically, do not edit! LIBFWUPDPLUGIN_0.1.0 { global: fu_device_add_flag; fu_device_get_metadata; fu_device_get_type; fu_device_new; fu_device_set_metadata; local: *; }; LIBFWUPDPLUGIN_0.6.1 { global: fu_device_get_equivalent_id; fu_device_set_equivalent_id; local: *; } LIBFWUPDPLUGIN_0.1.0; LIBFWUPDPLUGIN_0.7.1 { global: fu_device_set_id; fu_device_set_name; local: *; } LIBFWUPDPLUGIN_0.6.1; LIBFWUPDPLUGIN_0.7.2 { global: fu_device_add_guid; fu_device_get_alternate; fu_device_set_alternate; local: *; } LIBFWUPDPLUGIN_0.7.1; LIBFWUPDPLUGIN_0.8.0 { global: fu_plugin_alloc_data; fu_plugin_cache_add; fu_plugin_cache_lookup; fu_plugin_cache_remove; fu_plugin_device_add; fu_plugin_device_remove; fu_plugin_get_data; fu_plugin_get_name; fu_plugin_get_type; fu_plugin_new; fu_plugin_open; fu_plugin_runner_clear_results; fu_plugin_runner_coldplug; fu_plugin_runner_get_results; fu_plugin_runner_startup; fu_plugin_runner_unlock; fu_plugin_runner_verify; fu_plugin_set_name; local: *; } LIBFWUPDPLUGIN_0.7.2; LIBFWUPDPLUGIN_0.9.3 { global: fu_hwids_get_guid; fu_hwids_get_guids; fu_hwids_get_replace_keys; fu_hwids_get_replace_values; fu_hwids_get_type; fu_hwids_get_value; fu_hwids_has_guid; fu_hwids_new; fu_hwids_setup; local: *; } LIBFWUPDPLUGIN_0.8.0; LIBFWUPDPLUGIN_0.9.5 { global: fu_common_get_contents_fd; fu_common_set_contents_bytes; local: *; } LIBFWUPDPLUGIN_0.9.3; LIBFWUPDPLUGIN_0.9.7 { global: fu_common_extract_archive; fu_common_firmware_builder; fu_common_get_contents_bytes; fu_common_mkdir_parent; fu_common_rmtree; fu_common_spawn_sync; fu_device_get_metadata_boolean; fu_device_get_metadata_integer; fu_device_set_metadata_boolean; fu_device_set_metadata_integer; fu_plugin_device_register; fu_plugin_runner_device_register; local: *; } LIBFWUPDPLUGIN_0.9.5; LIBFWUPDPLUGIN_0.9.8 { global: fu_device_to_string; local: *; } LIBFWUPDPLUGIN_0.9.7; LIBFWUPDPLUGIN_1.0.0 { global: fu_device_locker_get_type; fu_device_locker_new; fu_device_locker_new_full; fu_plugin_add_rule; fu_plugin_get_order; fu_plugin_get_rules; fu_plugin_has_rule; fu_plugin_set_order; fu_plugin_set_priority; fu_smbios_get_data; fu_smbios_get_string; fu_smbios_get_type; fu_smbios_new; fu_smbios_setup; fu_smbios_setup_from_file; fu_smbios_setup_from_path; fu_smbios_to_string; local: *; } LIBFWUPDPLUGIN_0.9.8; LIBFWUPDPLUGIN_1.0.1 { global: fu_chunk_array_to_string; fu_quirks_get_type; fu_quirks_load; fu_quirks_lookup_by_id; fu_quirks_new; local: *; } LIBFWUPDPLUGIN_1.0.0; LIBFWUPDPLUGIN_1.0.2 { global: fu_device_get_remove_delay; fu_device_set_remove_delay; fu_usb_device_get_dev; fu_usb_device_get_type; fu_usb_device_new; fu_usb_device_set_dev; local: *; } LIBFWUPDPLUGIN_1.0.1; LIBFWUPDPLUGIN_1.0.3 { global: fu_common_read_uint16; fu_common_read_uint32; fu_common_write_uint16; fu_common_write_uint32; fu_usb_device_is_open; local: *; } LIBFWUPDPLUGIN_1.0.2; LIBFWUPDPLUGIN_1.0.4 { global: fu_plugin_add_report_metadata; fu_plugin_get_report_metadata; local: *; } LIBFWUPDPLUGIN_1.0.3; LIBFWUPDPLUGIN_1.0.5 { global: fu_device_get_release_default; local: *; } LIBFWUPDPLUGIN_1.0.4; LIBFWUPDPLUGIN_1.0.6 { global: fu_common_get_files_recursive; fu_plugin_get_config_value; local: *; } LIBFWUPDPLUGIN_1.0.5; LIBFWUPDPLUGIN_1.0.8 { global: fu_common_error_array_get_best; fu_common_get_path; fu_device_add_child; fu_device_add_parent_guid; fu_device_attach; fu_device_detach; fu_device_get_children; fu_device_get_guids_as_str; fu_device_get_order; fu_device_get_parent; fu_device_get_parent_guids; fu_device_has_parent_guid; fu_device_read_firmware; fu_device_set_order; fu_device_set_parent; fu_device_write_firmware; fu_plugin_guess_name_from_fn; fu_plugin_name_compare; fu_plugin_order_compare; local: *; } LIBFWUPDPLUGIN_1.0.6; LIBFWUPDPLUGIN_1.0.9 { global: fu_plugin_runner_composite_cleanup; fu_plugin_runner_composite_prepare; local: *; } LIBFWUPDPLUGIN_1.0.8; LIBFWUPDPLUGIN_1.1.0 { global: fu_device_get_alternate_id; fu_device_get_custom_flags; fu_device_incorporate; fu_device_set_alternate_id; fu_device_set_custom_flags; local: *; } LIBFWUPDPLUGIN_1.0.9; LIBFWUPDPLUGIN_1.1.1 { global: fu_device_get_priority; fu_device_set_priority; fu_plugin_get_priority; local: *; } LIBFWUPDPLUGIN_1.1.0; LIBFWUPDPLUGIN_1.1.2 { global: fu_chunk_array_new; fu_chunk_array_new_from_bytes; fu_chunk_new; fu_chunk_to_string; fu_common_find_program_in_path; fu_common_strstrip; fu_common_strtoull; fu_device_add_counterpart_guid; fu_device_close; fu_device_ensure_id; fu_device_get_logical_id; fu_device_get_physical_id; fu_device_open; fu_device_poll; fu_device_prepare_firmware; fu_device_probe; fu_device_probe_invalidate; fu_device_set_firmware_size_max; fu_device_set_firmware_size_min; fu_device_set_logical_id; fu_device_set_physical_id; fu_device_set_poll_interval; fu_device_setup; fu_plugin_runner_device_removed; fu_udev_device_emit_changed; fu_udev_device_get_dev; fu_udev_device_get_model; fu_udev_device_get_revision; fu_udev_device_get_subsystem; fu_udev_device_get_sysfs_path; fu_udev_device_get_type; fu_udev_device_get_vendor; fu_udev_device_new; fu_udev_device_set_physical_id; fu_usb_device_get_pid; fu_usb_device_get_platform_id; fu_usb_device_get_vid; local: *; } LIBFWUPDPLUGIN_1.1.1; LIBFWUPDPLUGIN_1.2.0 { global: fu_common_cab_build_silo; fu_common_string_replace; fu_common_version_from_uint16; fu_common_version_from_uint32; fu_common_version_guess_format; local: *; } LIBFWUPDPLUGIN_1.1.2; LIBFWUPDPLUGIN_1.2.2 { global: fu_archive_get_type; fu_archive_lookup_by_fn; fu_archive_new; fu_common_dump_bytes; fu_common_dump_raw; fu_device_has_guid; fu_io_channel_get_type; fu_io_channel_new_file; fu_io_channel_read_bytes; fu_io_channel_read_raw; fu_io_channel_shutdown; fu_io_channel_unix_get_fd; fu_io_channel_unix_new; fu_io_channel_write_bytes; fu_io_channel_write_raw; local: *; } LIBFWUPDPLUGIN_1.2.0; LIBFWUPDPLUGIN_1.2.4 { global: fu_common_bytes_align; fu_common_dump_full; fu_common_string_append_kb; fu_common_string_append_ku; fu_common_string_append_kv; fu_common_string_append_kx; fu_device_incorporate_from_component; fu_plugin_get_build_hash; fu_udev_device_get_slot_depth; local: *; } LIBFWUPDPLUGIN_1.2.2; LIBFWUPDPLUGIN_1.2.5 { global: fu_common_guid_is_plausible; fu_device_add_instance_id; fu_device_convert_instance_ids; local: *; } LIBFWUPDPLUGIN_1.2.4; LIBFWUPDPLUGIN_1.2.6 { global: fu_common_bytes_compare; fu_common_bytes_is_empty; fu_common_realpath; fu_device_activate; fu_device_get_firmware_size_max; fu_device_get_firmware_size_min; fu_device_set_firmware_size; fu_plugin_runner_activate; local: *; } LIBFWUPDPLUGIN_1.2.5; LIBFWUPDPLUGIN_1.2.9 { global: fu_common_version_ensure_semver; fu_common_version_verify_format; fu_device_add_instance_id_full; fu_device_set_version; local: *; } LIBFWUPDPLUGIN_1.2.6; LIBFWUPDPLUGIN_1.3.1 { global: fu_byte_array_append_uint16; fu_byte_array_append_uint32; fu_byte_array_append_uint8; fu_common_bytes_pad; fu_common_strnsplit; fu_device_rescan; fu_firmware_add_image; fu_firmware_get_image_by_id; fu_firmware_get_image_by_id_bytes; fu_firmware_get_image_by_idx; fu_firmware_get_image_by_idx_bytes; fu_firmware_get_images; fu_firmware_get_type; fu_firmware_new; fu_firmware_new_from_bytes; fu_firmware_parse; fu_firmware_parse_full; fu_firmware_to_string; fu_firmware_write; fu_ihex_firmware_get_type; fu_ihex_firmware_new; fu_memcpy_safe; fu_plugin_has_custom_flag; fu_udev_device_get_device_file; local: *; } LIBFWUPDPLUGIN_1.2.9; LIBFWUPDPLUGIN_1.3.2 { global: fu_common_bytes_compare_raw; fu_common_strwidth; fu_firmware_tokenize; fu_io_channel_read_byte_array; fu_io_channel_write_byte_array; fu_srec_firmware_get_records; fu_srec_firmware_get_type; fu_srec_firmware_new; fu_srec_firmware_record_new; fu_usb_device_find_udev_device; local: *; } LIBFWUPDPLUGIN_1.3.1; LIBFWUPDPLUGIN_1.3.3 { global: fu_common_read_uint16_safe; fu_common_read_uint32_safe; fu_common_read_uint8_safe; fu_common_version_parse_from_format; fu_device_cleanup; fu_device_get_possible_plugins; fu_device_get_specialized_gtype; fu_device_prepare; fu_device_reload; fu_device_remove_metadata; fu_dfu_firmware_get_pid; fu_dfu_firmware_get_release; fu_dfu_firmware_get_type; fu_dfu_firmware_get_version; fu_dfu_firmware_get_vid; fu_dfu_firmware_new; fu_dfu_firmware_set_pid; fu_dfu_firmware_set_release; fu_dfu_firmware_set_version; fu_dfu_firmware_set_vid; fu_firmware_get_version; fu_firmware_parse_file; fu_firmware_set_version; fu_firmware_write_file; fu_plugin_add_firmware_gtype; fu_quirks_lookup_by_id_iter; fu_udev_device_get_fd; fu_udev_device_ioctl; fu_udev_device_pread; fu_udev_device_pwrite; fu_udev_device_set_fd; local: *; } LIBFWUPDPLUGIN_1.3.2; LIBFWUPDPLUGIN_1.3.4 { global: fu_archive_iterate; fu_ihex_firmware_get_records; fu_usb_device_get_spec; local: *; } LIBFWUPDPLUGIN_1.3.3; LIBFWUPDPLUGIN_1.3.5 { global: fu_common_fnmatch; fu_device_incorporate_flag; fu_plugin_is_open; local: *; } LIBFWUPDPLUGIN_1.3.4; LIBFWUPDPLUGIN_1.3.6 { global: fu_common_version_from_uint64; fu_udev_device_set_flags; local: *; } LIBFWUPDPLUGIN_1.3.5; LIBFWUPDPLUGIN_1.3.8 { global: fu_common_kernel_locked_down; local: *; } LIBFWUPDPLUGIN_1.3.6; LIBFWUPDPLUGIN_1.3.9 { global: fu_common_vercmp_full; local: *; } LIBFWUPDPLUGIN_1.3.8; LIBFWUPDPLUGIN_1.4.0 { global: fu_cabinet_get_silo; fu_cabinet_get_type; fu_cabinet_new; fu_cabinet_parse; fu_cabinet_set_jcat_context; fu_cabinet_set_size_max; fu_device_get_root; fu_device_locker_close; fu_device_retry; fu_device_retry_add_recovery; fu_device_retry_set_delay; fu_device_set_version_bootloader; fu_device_set_version_format; fu_device_set_version_lowest; fu_efivar_delete; fu_efivar_delete_with_glob; fu_efivar_exists; fu_efivar_get_data; fu_efivar_secure_boot_enabled; fu_efivar_set_data; fu_efivar_supported; fu_hid_device_get_interface; fu_hid_device_get_report; fu_hid_device_get_type; fu_hid_device_new; fu_hid_device_set_interface; fu_hid_device_set_report; fu_plugin_get_config_value_boolean; fu_plugin_runner_device_created; local: *; } LIBFWUPDPLUGIN_1.3.9; LIBFWUPDPLUGIN_1.4.1 { global: fu_device_get_proxy; fu_device_get_proxy_guid; fu_device_set_proxy; fu_device_set_proxy_guid; local: *; } LIBFWUPDPLUGIN_1.4.0; LIBFWUPDPLUGIN_1.4.5 { global: fu_udev_device_get_devtype; fu_udev_device_get_parent_name; fu_udev_device_get_sysfs_attr; fu_udev_device_pread_full; fu_udev_device_pwrite_full; fu_udev_device_write_sysfs; local: *; } LIBFWUPDPLUGIN_1.4.1; LIBFWUPDPLUGIN_1.4.6 { global: fu_common_get_esp_default; fu_common_get_esp_for_path; fu_common_get_volumes_by_kind; fu_common_is_live_media; fu_volume_check_free_space; fu_volume_get_id; fu_volume_get_mount_point; fu_volume_get_type; fu_volume_is_mounted; fu_volume_locker; fu_volume_mount; fu_volume_unmount; local: *; } LIBFWUPDPLUGIN_1.4.5; LIBFWUPDPLUGIN_1.4.7 { global: fu_efivar_get_names; local: *; } LIBFWUPDPLUGIN_1.4.6; LIBFWUPDPLUGIN_1.5.0 { global: fu_byte_array_set_size; fu_common_cpuid; fu_common_crc16; fu_common_crc32; fu_common_crc32_full; fu_common_crc8; fu_common_filename_glob; fu_device_bind_driver; fu_device_dump_firmware; fu_device_report_metadata_post; fu_device_report_metadata_pre; fu_device_unbind_driver; fu_efivar_get_data_bytes; fu_efivar_secure_boot_enabled_full; fu_efivar_set_data_bytes; fu_firmware_add_flag; fu_firmware_build; fu_firmware_flag_from_string; fu_firmware_flag_to_string; fu_firmware_has_flag; fu_firmware_remove_image; fu_firmware_remove_image_by_id; fu_firmware_remove_image_by_idx; fu_fmap_firmware_get_type; fu_fmap_firmware_new; fu_plugin_runner_add_security_attrs; fu_plugin_runner_device_added; fu_security_attrs_append; fu_security_attrs_calculate_hsi; fu_security_attrs_depsolve; fu_security_attrs_get_all; fu_security_attrs_get_type; fu_security_attrs_new; fu_security_attrs_remove_all; fu_security_attrs_to_variant; fu_smbios_get_integer; fu_udev_device_get_number; fu_udev_device_get_subsystem_model; fu_udev_device_get_subsystem_vendor; local: *; } LIBFWUPDPLUGIN_1.4.7; LIBFWUPDPLUGIN_1.5.1 { global: fu_common_get_volume_by_device; fu_common_get_volume_by_devnum; fu_device_add_possible_plugin; fu_efivar_space_used; fu_volume_is_encrypted; local: *; } LIBFWUPDPLUGIN_1.5.0; LIBFWUPDPLUGIN_1.5.2 { global: fu_hid_device_add_flag; fu_volume_get_id_type; fu_volume_is_internal; local: *; } LIBFWUPDPLUGIN_1.5.1; LIBFWUPDPLUGIN_1.5.3 { global: fu_udev_device_get_driver; local: *; } LIBFWUPDPLUGIN_1.5.2; LIBFWUPDPLUGIN_1.5.4 { global: fu_common_bytes_new_offset; local: *; } LIBFWUPDPLUGIN_1.5.3; LIBFWUPDPLUGIN_1.5.5 { global: fu_common_get_cpu_vendor; fu_common_strsafe; fu_device_add_internal_flag; fu_device_has_internal_flag; fu_device_internal_flag_from_string; fu_device_internal_flag_to_string; fu_device_remove_internal_flag; fu_device_retry_full; fu_efi_signature_get_kind; fu_efi_signature_get_owner; fu_efi_signature_get_type; fu_efi_signature_kind_to_string; fu_efi_signature_list_get_type; fu_efi_signature_list_new; fu_efivar_get_monitor; fu_firmware_get_image_by_checksum; local: *; } LIBFWUPDPLUGIN_1.5.4; LIBFWUPDPLUGIN_1.5.6 { global: fu_chunk_array_mutable_new; fu_chunk_bytes_new; fu_chunk_get_address; fu_chunk_get_bytes; fu_chunk_get_data; fu_chunk_get_data_out; fu_chunk_get_data_sz; fu_chunk_get_idx; fu_chunk_get_page; fu_chunk_get_type; fu_chunk_set_address; fu_chunk_set_bytes; fu_chunk_set_idx; fu_chunk_set_page; fu_common_get_memory_size; fu_common_strjoin_array; fu_common_uri_get_scheme; fu_dfuse_firmware_get_type; fu_dfuse_firmware_new; fu_firmware_new_from_gtypes; fu_firmware_strparse_uint16_safe; fu_firmware_strparse_uint24_safe; fu_firmware_strparse_uint32_safe; fu_firmware_strparse_uint4_safe; fu_firmware_strparse_uint8_safe; fu_hwids_add_smbios_override; fu_hwids_get_keys; fu_memdup_safe; fu_plugin_get_devices; fu_plugin_runner_backend_device_added; fu_plugin_runner_backend_device_changed; local: *; } LIBFWUPDPLUGIN_1.5.5; LIBFWUPDPLUGIN_1.5.7 { global: fu_bluez_device_get_type; fu_bluez_device_read; fu_bluez_device_read_string; fu_bluez_device_write; fu_firmware_get_version_raw; fu_firmware_set_version_raw; local: *; } LIBFWUPDPLUGIN_1.5.6; LIBFWUPDPLUGIN_1.5.8 { global: fu_bluez_device_notify_start; fu_bluez_device_notify_stop; fu_byte_array_append_bytes; fu_byte_array_append_uint64; fu_common_read_uint64; fu_common_read_uint64_safe; fu_common_write_uint16_safe; fu_common_write_uint32_safe; fu_common_write_uint64; fu_common_write_uint64_safe; fu_common_write_uint8_safe; fu_device_get_backend_id; fu_device_get_battery_level; fu_device_set_backend_id; fu_device_set_battery_level; fu_quirks_add_possible_key; fu_udev_device_set_logical_id; local: *; } LIBFWUPDPLUGIN_1.5.7; LIBFWUPDPLUGIN_1.6.0 { global: fu_battery_state_to_string; fu_byte_array_align_up; fu_byte_array_set_size_full; fu_bytes_get_data_safe; fu_cabinet_add_file; fu_cabinet_export; fu_cabinet_get_file; fu_cabinet_sign; fu_common_align_up; fu_context_add_compile_version; fu_context_add_firmware_gtype; fu_context_add_quirk_key; fu_context_add_runtime_version; fu_context_add_udev_subsystem; fu_context_get_battery_level; fu_context_get_battery_state; fu_context_get_battery_threshold; fu_context_get_firmware_gtype_by_id; fu_context_get_firmware_gtype_ids; fu_context_get_hwid_guids; fu_context_get_hwid_replace_value; fu_context_get_hwid_value; fu_context_get_smbios_data; fu_context_get_smbios_integer; fu_context_get_smbios_string; fu_context_get_type; fu_context_get_udev_subsystems; fu_context_has_hwid_guid; fu_context_load_hwinfo; fu_context_load_quirks; fu_context_lookup_quirk_by_id; fu_context_lookup_quirk_by_id_iter; fu_context_new; fu_context_security_changed; fu_context_set_battery_level; fu_context_set_battery_state; fu_context_set_battery_threshold; fu_context_set_compile_versions; fu_context_set_runtime_versions; fu_device_add_security_attrs; fu_device_get_battery_threshold; fu_device_get_context; fu_device_inhibit; fu_device_remove_flag; fu_device_set_battery_threshold; fu_device_set_context; fu_device_uninhibit; fu_firmware_add_chunk; fu_firmware_build_from_xml; fu_firmware_export; fu_firmware_export_to_xml; fu_firmware_get_addr; fu_firmware_get_alignment; fu_firmware_get_bytes; fu_firmware_get_checksum; fu_firmware_get_chunks; fu_firmware_get_filename; fu_firmware_get_id; fu_firmware_get_idx; fu_firmware_get_offset; fu_firmware_get_size; fu_firmware_set_addr; fu_firmware_set_alignment; fu_firmware_set_bytes; fu_firmware_set_filename; fu_firmware_set_id; fu_firmware_set_idx; fu_firmware_set_offset; fu_firmware_set_size; fu_firmware_write_chunk; fu_ihex_firmware_set_padding_value; fu_plugin_add_device_gtype; fu_plugin_get_context; fu_udev_device_get_siblings_with_subsystem; fu_xmlb_builder_insert_kb; fu_xmlb_builder_insert_kv; fu_xmlb_builder_insert_kx; local: *; } LIBFWUPDPLUGIN_1.5.8; LIBFWUPDPLUGIN_1.6.1 { global: fu_backend_coldplug; fu_backend_device_added; fu_backend_device_changed; fu_backend_device_removed; fu_backend_get_context; fu_backend_get_devices; fu_backend_get_enabled; fu_backend_get_name; fu_backend_get_type; fu_backend_lookup_by_id; fu_backend_set_enabled; fu_backend_setup; fu_i2c_device_get_bus_number; fu_i2c_device_get_type; fu_i2c_device_read; fu_i2c_device_write; fu_kenv_get_string; fu_srec_firmware_record_get_type; fu_version_string; local: *; } LIBFWUPDPLUGIN_1.6.0; LIBFWUPDPLUGIN_1.6.2 { global: fu_common_check_kernel_version; fu_common_crc16_full; fu_common_get_firmware_search_path; fu_common_reset_firmware_search_path; fu_common_set_firmware_search_path; fu_device_add_guid_full; fu_device_add_parent_physical_id; fu_device_add_private_flag; fu_device_emit_request; fu_device_get_parent_physical_ids; fu_device_get_private_flags; fu_device_get_proxy_with_fallback; fu_device_get_request_cnt; fu_device_get_results; fu_device_has_parent_physical_id; fu_device_has_private_flag; fu_device_new_with_context; fu_device_register_private_flag; fu_device_remove_child; fu_device_remove_private_flag; fu_device_set_private_flags; fu_device_set_update_state; fu_device_set_vendor; fu_efi_firmware_file_get_type; fu_efi_firmware_file_new; fu_efi_firmware_filesystem_get_type; fu_efi_firmware_filesystem_new; fu_efi_firmware_section_get_type; fu_efi_firmware_section_new; fu_efi_firmware_volume_get_type; fu_efi_firmware_volume_new; fu_efi_guid_to_name; fu_i2c_device_read_full; fu_i2c_device_set_bus_number; fu_i2c_device_write_full; fu_ifd_access_to_string; fu_ifd_bios_get_type; fu_ifd_bios_new; fu_ifd_firmware_check_jedec_cmd; fu_ifd_firmware_get_type; fu_ifd_firmware_new; fu_ifd_image_get_access; fu_ifd_image_get_type; fu_ifd_image_new; fu_ifd_image_set_access; fu_ifd_region_to_access; fu_ifd_region_to_name; fu_ifd_region_to_string; fu_plugin_add_udev_subsystem; fu_smbios_setup_from_kernel; fu_udev_device_get_children_with_subsystem; fu_udev_device_set_dev; local: *; } LIBFWUPDPLUGIN_1.6.1; LIBFWUPDPLUGIN_1.7.0 { global: fu_cfu_device_offer_to_string; fu_cfu_device_reject_to_string; fu_cfu_device_status_to_string; fu_cfu_offer_get_bank; fu_cfu_offer_get_component_id; fu_cfu_offer_get_force_ignore_version; fu_cfu_offer_get_force_immediate_reset; fu_cfu_offer_get_hw_variant; fu_cfu_offer_get_milestone; fu_cfu_offer_get_product_id; fu_cfu_offer_get_protocol_revision; fu_cfu_offer_get_segment_number; fu_cfu_offer_get_token; fu_cfu_offer_get_type; fu_cfu_offer_new; fu_cfu_offer_set_bank; fu_cfu_offer_set_component_id; fu_cfu_offer_set_force_ignore_version; fu_cfu_offer_set_force_immediate_reset; fu_cfu_offer_set_hw_variant; fu_cfu_offer_set_milestone; fu_cfu_offer_set_product_id; fu_cfu_offer_set_protocol_revision; fu_cfu_offer_set_segment_number; fu_cfu_offer_set_token; fu_cfu_payload_get_type; fu_cfu_payload_new; fu_common_strnsplit_full; fu_device_attach_full; fu_device_detach_full; fu_device_set_progress; fu_plugin_runner_attach; fu_plugin_runner_cleanup; fu_plugin_runner_detach; fu_plugin_runner_prepare; fu_plugin_runner_reload; fu_plugin_runner_write_firmware; fu_plugin_set_config_value; fu_progress_add_flag; fu_progress_add_step; fu_progress_finished; fu_progress_flag_from_string; fu_progress_flag_to_string; fu_progress_get_child; fu_progress_get_id; fu_progress_get_percentage; fu_progress_get_status; fu_progress_get_steps; fu_progress_get_type; fu_progress_has_flag; fu_progress_new; fu_progress_remove_flag; fu_progress_reset; fu_progress_set_id; fu_progress_set_percentage; fu_progress_set_percentage_full; fu_progress_set_profile; fu_progress_set_status; fu_progress_set_steps; fu_progress_sleep; fu_progress_step_done; local: *; } LIBFWUPDPLUGIN_1.6.2; LIBFWUPDPLUGIN_1.7.1 { global: fu_cfi_device_get_cmd; fu_cfi_device_get_flash_id; fu_cfi_device_get_size; fu_cfi_device_get_type; fu_cfi_device_new; fu_cfi_device_set_flash_id; fu_cfi_device_set_size; fu_common_check_full_disk_encryption; fu_common_crc8_full; fu_common_mkdir; fu_device_add_string; fu_device_get_internal_flags; fu_device_set_internal_flags; fu_security_attrs_append_internal; fu_udev_device_new_with_context; fu_usb_device_new_with_context; local: *; } LIBFWUPDPLUGIN_1.7.0; LIBFWUPDPLUGIN_1.7.2 { global: fu_context_has_hwid_flag; fu_device_get_firmware_gtype; fu_device_set_firmware_gtype; fu_security_attrs_get_by_appstream_id; fu_udev_device_get_bind_id; fu_udev_device_get_sysfs_attr_uint64; fu_udev_device_seek; fu_udev_device_set_bind_id; local: *; } LIBFWUPDPLUGIN_1.7.1; LIBFWUPDPLUGIN_1.7.3 { global: fu_archive_firmware_get_type; fu_archive_firmware_new; fu_cfi_device_get_page_size; fu_cfi_device_get_sector_size; fu_cfi_device_set_page_size; fu_cfi_device_set_sector_size; fu_common_strtoull_full; fu_common_sum16; fu_common_sum16_bytes; fu_common_sum16w; fu_common_sum16w_bytes; fu_common_sum32; fu_common_sum32_bytes; fu_common_sum32w; fu_common_sum32w_bytes; fu_common_sum8; fu_common_sum8_bytes; local: *; } LIBFWUPDPLUGIN_1.7.2; LIBFWUPDPLUGIN_1.7.4 { global: fu_backend_registered; fu_cfi_device_get_block_size; fu_cfi_device_set_block_size; fu_common_get_contents_stream; fu_context_get_lid_state; fu_context_set_lid_state; fu_firmware_add_patch; fu_firmware_get_bytes_with_patches; fu_lid_state_to_string; fu_memmem_safe; fu_plugin_set_secure_config_value; fu_usb_device_add_interface; fu_usb_device_set_configuration; local: *; } LIBFWUPDPLUGIN_1.7.3; fwupd-1.7.5/libfwupdplugin/meson.build000066400000000000000000000206401420024370600200460ustar00rootroot00000000000000if get_option('tests') subdir('tests') endif fwupdplugin_version_h = configure_file( input : 'fu-version.h.in', output : 'fu-version.h', configuration : conf ) fwupdplugin_src = [ 'fu-archive.c', 'fu-backend.c', 'fu-bluez-device.c', 'fu-cabinet.c', 'fu-chunk.c', # fuzzing 'fu-common.c', # fuzzing 'fu-common-cab.c', 'fu-common-guid.c', 'fu-common-version.c', # fuzzing 'fu-context.c', # fuzzing 'fu-device-locker.c', # fuzzing 'fu-device.c', # fuzzing 'fu-dfu-firmware.c', # fuzzing 'fu-cfu-common.c', # fuzzing 'fu-cfu-offer.c', # fuzzing 'fu-cfu-payload.c', # fuzzing 'fu-volume.c', # fuzzing 'fu-firmware.c', # fuzzing 'fu-firmware-common.c', # fuzzing 'fu-dfuse-firmware.c', # fuzzing 'fu-fmap-firmware.c', # fuzzing 'fu-hwids.c', # fuzzing 'fu-ihex-firmware.c', # fuzzing 'fu-io-channel.c', # fuzzing 'fu-plugin.c', 'fu-quirks.c', # fuzzing 'fu-progress.c', # fuzzing 'fu-security-attrs.c', 'fu-smbios.c', # fuzzing 'fu-srec-firmware.c', # fuzzing 'fu-archive-firmware.c', 'fu-kenv.c', # fuzzing 'fu-efi-signature.c', 'fu-efi-signature-list.c', 'fu-efi-common.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-ifd-bios.c', # fuzzing 'fu-ifd-common.c', # fuzzing 'fu-ifd-firmware.c', # fuzzing 'fu-ifd-image.c', # fuzzing 'fu-efivar.c', 'fu-udev-device.c', 'fu-i2c-device.c', 'fu-usb-device.c', 'fu-cfi-device.c', 'fu-hid-device.c', 'fu-version.c', ] 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' # fuzzing else error('no OS support for @0@'.format(host_machine.system())) endif fwupdplugin_headers = [ 'fu-archive.h', 'fu-backend.h', 'fu-bluez-device.h', 'fu-cabinet.h', 'fu-chunk.h', 'fu-common.h', 'fu-common-cab.h', 'fu-common-guid.h', 'fu-common-version.h', 'fu-context.h', 'fu-deprecated.h', 'fu-device.h', 'fu-device-metadata.h', 'fu-device-locker.h', 'fu-dfu-firmware.h', 'fu-cfu-common.h', 'fu-cfu-offer.h', 'fu-cfu-payload.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-ifd-bios.h', 'fu-ifd-common.h', 'fu-ifd-firmware.h', 'fu-ifd-image.h', 'fu-volume.h', 'fu-firmware.h', 'fu-firmware-common.h', 'fu-fmap-firmware.h', 'fu-dfuse-firmware.h', 'fu-hwids.h', 'fu-ihex-firmware.h', 'fu-io-channel.h', 'fu-plugin.h', 'fu-quirks.h', 'fu-security-attrs.h', 'fu-progress.h', 'fu-smbios.h', 'fu-cfi-device.h', 'fu-srec-firmware.h', 'fu-archive-firmware.h', 'fu-efi-signature.h', 'fu-efi-signature-list.h', 'fu-efivar.h', 'fu-udev-device.h', 'fu-i2c-device.h', 'fu-usb-device.h', 'fu-hid-device.h', ] install_headers( 'fwupdplugin.h', subdir : 'fwupd-1', ) install_headers([fwupdplugin_headers, 'fu-plugin-vfuncs.h'], subdir : 'fwupd-1/libfwupdplugin', ) fu_hash = custom_target( 'fu-hash.h', input : fwupdplugin_src, output : 'fu-hash.h', command : [python3.path(), join_paths(meson.current_source_dir(), 'fu-hash.py'), '@OUTPUT@', '@INPUT@'] ) fwupdplugin_headers_private = [ fu_hash, 'fu-context-private.h', 'fu-device-private.h', 'fu-kenv.h', 'fu-plugin-private.h', 'fu-security-attrs-private.h', 'fu-smbios-private.h', 'fu-udev-device-private.h', 'fu-usb-device-private.h', fwupdplugin_version_h, ] introspection_deps = [ libxmlb, libjcat, giounix, ] pkgg_requires = [ 'gio-2.0', 'gmodule-2.0', 'gobject-2.0', 'fwupd', 'json-glib-1.0', 'libarchive', 'libgcab-1.0', 'xmlb', 'jcat', ] if get_option('gusb') introspection_deps += gusb pkgg_requires += 'gusb' endif if get_option('gudev') introspection_deps += gudev endif library_deps = [ introspection_deps, gmodule, libjsonglib, libgcab, valgrind, libjcat, platform_deps, ] if get_option('libarchive') library_deps += libarchive endif if get_option('lzma') library_deps += lzma endif fwupdplugin_mapfile = 'fwupdplugin.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), fwupdplugin_mapfile) fwupdplugin = library( 'fwupdplugin', sources : [ fwupdplugin_src, fwupdplugin_headers, fwupdplugin_headers_private, ], soversion : libfwupdplugin_lt_current, version : libfwupdplugin_lt_version, include_directories : [ root_incdir, fwupd_incdir, ], dependencies : [ library_deps ], link_with : [ fwupd, ], link_args : vflag, link_depends : fwupdplugin_mapfile, install : true ) fwupdplugin_pkgg = import('pkgconfig') fwupdplugin_pkgg.generate( libraries : fwupdplugin, requires : pkgg_requires, subdirs : 'fwupd-1', version : meson.project_version(), name : 'fwupdplugin', filebase : 'fwupdplugin', description : 'library for plugins to use to interact with fwupd daemon', ) if get_option('introspection') gir_dep = declare_dependency(sources: fwupd_gir) girtargets = [] extra_args = [] if get_option('gusb') 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_headers_private, ], 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 : 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. fwupdplugin_mapfile_target = custom_target('fwupdplugin_mapfile', input: fwupdplugin_gir[0], output: 'fwupdplugin.map', command: [ python3, join_paths(meson.source_root(), 'contrib', 'generate-version-script.py'), 'LIBFWUPDPLUGIN', '@INPUT@', '@OUTPUT@', '--override', 'fu_chunk_get_type', '1.5.6', '--override', 'fu_srec_firmware_record_get_type', '1.6.1', ], ) test('fwupdplugin-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), 'fwupdplugin.map'), fwupdplugin_mapfile_target, ], ) endif if get_option('tests') test_deps = [ fu_hash, ] 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', test_deps, sources : [ fwupdplugin_src, 'fu-self-test.c' ], include_directories : [ root_incdir, fwupd_incdir, ], dependencies : [ library_deps ], link_with : [ fwupd, fwupdplugin ], c_args : [ ], ) test('fwupdplugin-self-test', e, is_parallel:false, timeout:180, env : env) endif fwupdplugin_incdir = include_directories('.') fwupd-1.7.5/libfwupdplugin/tests/000077500000000000000000000000001420024370600170445ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/builder/000077500000000000000000000000001420024370600204725ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/builder/meson.build000066400000000000000000000005441420024370600226370ustar00rootroot00000000000000if get_option('tests') tar = find_program('gtar', 'tar') builder_test_firmware = custom_target('builder-test-firmware', input : [ 'source.bin', 'startup.sh', ], output : 'firmware.tar', command : [ tar, '--xform', 's,.*/,,', '--absolute-names', '--create', '--file', '@OUTPUT@', '@INPUT@', ], ) endif fwupd-1.7.5/libfwupdplugin/tests/builder/source.bin000066400000000000000000000000261420024370600224620ustar00rootroot00000000000000running in the sandboxfwupd-1.7.5/libfwupdplugin/tests/builder/startup.sh000077500000000000000000000000561420024370600225340ustar00rootroot00000000000000#!/bin/sh cat source.bin | rev > firmware.bin fwupd-1.7.5/libfwupdplugin/tests/cfu-offer.builder.xml000066400000000000000000000006651420024370600230760ustar00rootroot00000000000000 0x42 1 1 0xAB 0xCD 0x4567 0xFACE 0xF 0x1 0x2 0xDEAD fwupd-1.7.5/libfwupdplugin/tests/cfu-payload.builder.xml000066400000000000000000000003671420024370600234250ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= 0x8001234 aGVsbG8gd29ybGQ= 0x8005678 fwupd-1.7.5/libfwupdplugin/tests/colorhug/000077500000000000000000000000001420024370600206665ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/colorhug/README.md000066400000000000000000000004361420024370600221500ustar00rootroot00000000000000# 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.7.5/libfwupdplugin/tests/colorhug/firmware.bin000066400000000000000000000175001420024370600231770ustar00rootroot000000000000001 (~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.7.5/libfwupdplugin/tests/colorhug/firmware.bin.asc000066400000000000000000000007311420024370600237420ustar00rootroot00000000000000-----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.7.5/libfwupdplugin/tests/colorhug/firmware.bin.p7b000066400000000000000000000043251420024370600236670ustar00rootroot00000000000000-----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.7.5/libfwupdplugin/tests/colorhug/firmware.metainfo.xml000066400000000000000000000023361420024370600250310ustar00rootroot00000000000000 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.7.5/libfwupdplugin/tests/colorhug/meson.build000066400000000000000000000004161420024370600230310ustar00rootroot00000000000000colorhug_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.7.5/libfwupdplugin/tests/devicetree-fallback/000077500000000000000000000000001420024370600227205ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree-fallback/base/000077500000000000000000000000001420024370600236325ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree-fallback/base/compatible000077700000000000000000000000001420024370600334102../../devicetree/base/compatibleustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/000077500000000000000000000000001420024370600211635ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/000077500000000000000000000000001420024370600220755ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/compatible000066400000000000000000000000651420024370600241400ustar00rootroot00000000000000solidrun,honeycombsolidrun,lx2160a-cex7fsl,lx2160afwupd-1.7.5/libfwupdplugin/tests/devicetree/base/ibm,firmware-versions/000077500000000000000000000000001420024370600263235ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/ibm,firmware-versions/version000066400000000000000000000000071420024370600277300ustar00rootroot000000000000001.2.3-4fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/model000066400000000000000000000000101420024370600231070ustar00rootroot00000000000000ColorHugfwupd-1.7.5/libfwupdplugin/tests/devicetree/base/model-name000066400000000000000000000000261420024370600240340ustar00rootroot00000000000000To Be Filled By O.E.M.fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/name000066400000000000000000000000011420024370600227270ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vendor000066400000000000000000000000171420024370600233130ustar00rootroot00000000000000Hughski Limitedfwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/000077500000000000000000000000001420024370600226665ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/name000066400000000000000000000000041420024370600235230ustar00rootroot00000000000000vpdfwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/000077500000000000000000000000001420024370600260445ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00/000077500000000000000000000000001420024370600305315ustar00rootroot00000000000000backplane@800/000077500000000000000000000000001420024370600327225ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00name000066400000000000000000000000121420024370600335560ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00/backplane@800backplanepart-number000066400000000000000000000000111420024370600350710ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00/backplane@800PCB-CH001vendor000066400000000000000000000000161420024370600341370ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00/backplane@800Richard Hughesfwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/enclosure@1e00/name000066400000000000000000000000121420024370600313650ustar00rootroot00000000000000enclosurefwupd-1.7.5/libfwupdplugin/tests/devicetree/base/vpd/root-node-vpd@a000/name000066400000000000000000000000161420024370600267040ustar00rootroot00000000000000root-node-vpdfwupd-1.7.5/libfwupdplugin/tests/dfuse.bin000066400000000000000000000011701420024370600206430ustar00rootroot00000000000000DfuSehTargetone&4 hello worldxV hello worldTargettwo hello worldBxV4UFD`pfwupd-1.7.5/libfwupdplugin/tests/dfuse.builder.xml000066400000000000000000000012151420024370600223200ustar00rootroot00000000000000 0x1234 0x5678 0x8642 0x1 one aGVsbG8gd29ybGQ= 0x8001234 aGVsbG8gd29ybGQ= 0x8005678 two 0x7 aGVsbG8gd29ybGQ= 0x8000000 fwupd-1.7.5/libfwupdplugin/tests/dmi/000077500000000000000000000000001420024370600176155ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/dmi/class/000077500000000000000000000000001420024370600207225ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/dmi/class/chassis_type000066400000000000000000000000031420024370600233340ustar00rootroot0000000000000016 fwupd-1.7.5/libfwupdplugin/tests/dmi/class/sys_vendor000066400000000000000000000000121420024370600230310ustar00rootroot00000000000000FwupdTest fwupd-1.7.5/libfwupdplugin/tests/dmi/tables/000077500000000000000000000000001420024370600210675ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/dmi/tables/DMI000066400000000000000000000047331420024370600214320ustar00rootroot00000000000000*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.7.5/libfwupdplugin/tests/dmi/tables/smbios_entry_point000066400000000000000000000000371420024370600247400ustar00rootroot00000000000000_SM__DMI_ Ӽ>'fwupd-1.7.5/libfwupdplugin/tests/dmi/tables64/000077500000000000000000000000001420024370600212415ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/dmi/tables64/DMI000066400000000000000000000133051420024370600215770ustar00rootroot00000000000000ڲ@""##(())**++,,--..@@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.7.5/libfwupdplugin/tests/dmi/tables64/smbios_entry_point000066400000000000000000000000301420024370600251030ustar00rootroot00000000000000_SM3_ jfwupd-1.7.5/libfwupdplugin/tests/efi-firmware-file.builder.xml000066400000000000000000000003171420024370600245060ustar00rootroot00000000000000 ced4eac6-49f3-4c12-a597-fc8c33447691 0x0B aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/efi-firmware-filesystem.bin000066400000000000000000000002301420024370600242700ustar00rootroot00000000000000x匌=O5a-Ә_FVHHIL3Dv#hello worldIL3Dv#hello worldfwupd-1.7.5/libfwupdplugin/tests/efi-firmware-filesystem.builder.xml000066400000000000000000000010021420024370600257430ustar00rootroot00000000000000 8c8ce578-8a3d-4f1c-9935-896185c32dd3 0x3 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= 0x3 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/efi-firmware-section.builder.xml000066400000000000000000000002301420024370600252250ustar00rootroot00000000000000 0x02 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/efi-firmware-volume.bin000066400000000000000000000001301420024370600234120ustar00rootroot00000000000000+vL'G[OPX_FVHHXhello worldfwupd-1.7.5/libfwupdplugin/tests/efi-firmware-volume.builder.xml000066400000000000000000000002401420024370600250710ustar00rootroot00000000000000 0x3 fff12b8d-7696-4c8b-a985-2747075b4f50 aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/efi/000077500000000000000000000000001420024370600176075ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/efi/efivars/000077500000000000000000000000001420024370600212465ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/efi/efivars/BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000001420024370600276140ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000011420024370600301250ustar00rootroot000000000000001fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461420024370600344030ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.7.5/libfwupdplugin/tests/efi/efivars/meson.build000066400000000000000000000011121420024370600234030ustar00rootroot00000000000000configure_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.7.5/libfwupdplugin/tests/efi/meson.build000066400000000000000000000000221420024370600217430ustar00rootroot00000000000000subdir('efivars') fwupd-1.7.5/libfwupdplugin/tests/firmware.bin000066400000000000000000000002101420024370600213430ustar00rootroot00000000000000=   ? B = fwupd-1.7.5/libfwupdplugin/tests/firmware.dfu000066400000000000000000000002301420024370600213530ustar00rootroot00000000000000=   ? B = !C4UFD4Yfwupd-1.7.5/libfwupdplugin/tests/firmware.dfuse000066400000000000000000000011701420024370600217070ustar00rootroot00000000000000DfuSehTargetone&4 hello worldxV hello worldTargettwo hello worldBxV4UFDP]qfwupd-1.7.5/libfwupdplugin/tests/firmware.hex000066400000000000000000000006001420024370600213620ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.7.5/libfwupdplugin/tests/firmware.shex000066400000000000000000000006441420024370600215550ustar00rootroot00000000000000:100000003DEF20F000000000FACF01F0FBCF02F03E :10001000E9CF03F0EACF04F0E1CF05F0E2CF06F03C :10002000D9CF07F0DACF08F0F3CF09F0F4CF0AF018 :10003000F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0B8 :100040000EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FFA8 :100050000AC0F4FF09C0F3FF08C0DAFF07C0D9FFE8 :1000600006C0E2FF05C0E1FF04C0EAFF03C0E9FFEC :1000700002C0FBFF01C0FAFF11003FEF20F00001BA :0800800042EF20F03DEF20F0FB :080000FD6465616462656566DB :00000001FF fwupd-1.7.5/libfwupdplugin/tests/firmware.srec000066400000000000000000000006331420024370600215400ustar00rootroot00000000000000S0220000687474703A2F2F737265636F72642E736F75726365666F7267652E6E65742F1D S12300003DEF20F000000000FACF01F0FBCF02F0E9CF03F0EACF04F0E1CF05F0E2CF06F086 S1230020D9CF07F0DACF08F0F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0FC S12300400EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF08C0DAFF07C0D9FFDC S123006006C0E2FF05C0E1FF04C0EAFF03C0E9FF02C0FBFF01C0FAFF11003FEF20F0000112 S10B008042EF20F03DEF20F0F7 S5030005F7 fwupd-1.7.5/libfwupdplugin/tests/fmap-offset.bin000066400000000000000000000002551420024370600217470ustar00rootroot00000000000000__FMAP__ FMAPTESThello worldWorld!fwupd-1.7.5/libfwupdplugin/tests/fmap-offset.builder.xml000066400000000000000000000003341420024370600234220ustar00rootroot00000000000000 0x10 FMAP aGVsbG8gd29ybGQ= TEST V29ybGQh fwupd-1.7.5/libfwupdplugin/tests/fmap.bin000066400000000000000000000002351420024370600204610ustar00rootroot00000000000000__FMAP__ FMAPTESThello worldWorld!fwupd-1.7.5/libfwupdplugin/tests/fmap.builder.xml000066400000000000000000000003041420024370600221330ustar00rootroot00000000000000 FMAP aGVsbG8gd29ybGQ= TEST V29ybGQh fwupd-1.7.5/libfwupdplugin/tests/ifd-bios.bin000066400000000000000000000100001420024370600212210ustar00rootroot00000000000000+vL'G[OP_FVH H3hello world+vL'G[OP_FVH H3hello worldfwupd-1.7.5/libfwupdplugin/tests/ifd-bios.builder.xml000066400000000000000000000006641420024370600227150ustar00rootroot00000000000000 bios 0x1 0x1000 fff12b8d-7696-4c8b-a985-2747075b4f50 0xB aGVsbG8gd29ybGQ= fff12b8d-7696-4c8b-a985-2747075b4f50 0xB aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/ifd-no-bios.bin000066400000000000000000000400001420024370600216360ustar00rootroot00000000000000ZX01\2BWorld!World!fwupd-1.7.5/libfwupdplugin/tests/ifd-no-bios.builder.xml000066400000000000000000000010231420024370600233150ustar00rootroot00000000000000 0x40003 0x58100208 0x310330 0x325c00f5 0x42 gbe 0x3 0x3000 V29ybGQh me 0x2 0x2000 V29ybGQh fwupd-1.7.5/libfwupdplugin/tests/ifd.bin000066400000000000000000000300001420024370600202710ustar00rootroot00000000000000ZX01\2Bx匌=O5a-_FVH HIL3Dv} J#IL3Dvhello worldhello worldIL3Dv#hello world+vL'G[OP_FVH H3hello worldWorld!fwupd-1.7.5/libfwupdplugin/tests/ifd.builder.xml000066400000000000000000000030251420024370600217550ustar00rootroot00000000000000 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.7.5/libfwupdplugin/tests/ihex.bin000066400000000000000000000004401420024370600204710ustar00rootroot00000000000000:100000004E6571756520706F72726F2071756973BE :100010007175616D206573742071756920646F6CF2 :100020006F72656D20697073756D207175696120DF :10003000646F6C6F722073697420616D65742C201D :10004000636F6E73656374657475722C2061646987 :0C00500070697363692076656C69740A3E :040000FD646176655F :00000001FF fwupd-1.7.5/libfwupdplugin/tests/ihex.builder.xml000066400000000000000000000004061420024370600221500ustar00rootroot00000000000000 TmVxdWUgcG9ycm8gcXVpc3F1YW0gZXN0IHF1aSBkb2xvcmVtIGlwc3VtIHF1aWEgZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyLCBhZGlwaXNjaSB2ZWxpdAo= signature ZGF2ZQ== fwupd-1.7.5/libfwupdplugin/tests/lockdown/000077500000000000000000000000001420024370600206645ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/lockdown/locked/000077500000000000000000000000001420024370600221255ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/lockdown/locked/lockdown000066400000000000000000000000411420024370600236630ustar00rootroot00000000000000none integrity [confidentiality] fwupd-1.7.5/libfwupdplugin/tests/lockdown/none/000077500000000000000000000000001420024370600216235ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/lockdown/none/lockdown000066400000000000000000000000411420024370600233610ustar00rootroot00000000000000[none] integrity confidentiality fwupd-1.7.5/libfwupdplugin/tests/meson.build000066400000000000000000000000631420024370600212050ustar00rootroot00000000000000subdir('builder') subdir('colorhug') subdir('efi') fwupd-1.7.5/libfwupdplugin/tests/metadata.xml000077700000000000000000000000001420024370600262222../../src/tests/metadata.xmlustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/quirks.d/000077500000000000000000000000001420024370600206045ustar00rootroot00000000000000fwupd-1.7.5/libfwupdplugin/tests/quirks.d/tests.quirk000066400000000000000000000007341420024370600230270ustar00rootroot00000000000000[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 [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 fwupd-1.7.5/libfwupdplugin/tests/spawn.sh000077500000000000000000000002701420024370600205320ustar00rootroot00000000000000#!/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.7.5/libfwupdplugin/tests/srec-addr32.bin000066400000000000000000000001201420024370600215400ustar00rootroot00000000000000S00600004844521B S3100100000068656C6C6F20776F726C6492 S5030001FB S70500000000FA fwupd-1.7.5/libfwupdplugin/tests/srec-addr32.builder.xml000066400000000000000000000001661420024370600232270ustar00rootroot00000000000000 0x1000000 HDR aGVsbG8gd29ybGQ= fwupd-1.7.5/libfwupdplugin/tests/srec.bin000066400000000000000000000001101420024370600204620ustar00rootroot00000000000000S00600004844521B S10E000068656C6C6F20776F726C6495 S5030001FB S9030000FC fwupd-1.7.5/libfwupdplugin/tests/srec.builder.xml000066400000000000000000000001351420024370600221460ustar00rootroot00000000000000 HDR aGVsbG8gd29ybGQ= fwupd-1.7.5/meson.build000066400000000000000000000450721420024370600150210ustar00rootroot00000000000000project('fwupd', 'c', version : '1.7.5', license : 'LGPL-2.1+', meson_version : '>=0.50.0', default_options : ['warning_level=2', 'c_std=c99'], ) 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) if git.found() source_version = run_command(git, 'describe').stdout().strip() if source_version == '' source_version = fwupd_version endif 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) libfwupdplugin_lt_current = '5' libfwupdplugin_lt_revision = '0' libfwupdplugin_lt_age = '0' libfwupdplugin_lt_version = '@0@.@1@.@2@'.format(libfwupdplugin_lt_current, libfwupdplugin_lt_age, libfwupdplugin_lt_revision) # get supported warning flags warning_flags = [ '-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', '-Winit-self', '-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-unused-parameter', '-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' ] if get_option('static_analysis') and host_machine.system() != 'windows' 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 # 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') # do not use deprecated symbols or defines internally add_project_arguments('-DFWUPD_DISABLE_DEPRECATED', 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') # 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') 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()) 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.45.8') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8', required: false) if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif if gio.version().version_compare ('>= 2.55.0') conf.set('HAVE_GIO_2_55_0', '1') endif gmodule = dependency('gmodule-2.0') if build_standalone if get_option('gudev') gudev = dependency('gudev-1.0', version : '>= 232') conf.set('HAVE_GUDEV', '1') else gudev = dependency('', required : false) endif if get_option('bluez') conf.set('HAVE_BLUEZ', '1') endif if get_option('hsi') conf.set('HAVE_HSI', '1') endif libxmlb = dependency('xmlb', version : '>= 0.1.13', fallback : ['libxmlb', 'libxmlb_dep']) if get_option('gusb') gusb = dependency('gusb', version : '>= 0.3.0', fallback : ['gusb', 'gusb_dep']) conf.set('HAVE_GUSB', '1') endif if get_option('sqlite') sqlite = dependency('sqlite3') conf.set('HAVE_SQLITE', '1') endif if get_option('libarchive') libarchive = dependency('libarchive') conf.set('HAVE_LIBARCHIVE', '1') endif endif libjcat = dependency('jcat', version : '>= 0.1.0', fallback : ['libjcat', 'libjcat_dep']) libjsonglib = dependency('json-glib-1.0', version : '>= 1.1.1') valgrind = dependency('valgrind', required: false) if get_option('curl') libcurl = dependency('libcurl', version : '>= 7.56.0') conf.set('HAVE_LIBCURL', '1') if libcurl.version().version_compare('>= 7.62.0') conf.set('HAVE_LIBCURL_7_62_0', '1') endif endif if build_daemon if get_option('polkit') polkit = dependency('polkit-gobject-1', version : '>= 0.103') 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_pkgconfig_variable('actiondir')) else 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_pkgconfig_variable('udevdir') endif endif libm = cc.find_library('m', required: false) libgcab = dependency('libgcab-1.0', version : '>= 1.0', fallback : ['gcab', 'gcab_dep']) if libgcab.type_name() == 'pkgconfig' and cc.has_function('gcab_file_set_bytes', dependencies: libgcab) conf.set('HAVE_GCAB_FILE_SET_BYTES', '1') endif bashcomp = dependency('bash-completion', required: false) if host_machine.system() != 'freebsd' python3 = find_program('python3') else python3 = find_program('python3.8', 'python3', 'python3.9') endif if get_option('gnutls') gnutls = dependency('gnutls', version : '>= 3.6.0') conf.set('HAVE_GNUTLS', '1') else if get_option('plugin_uefi_pk') error('plugin_uefi_pk needs -Dgnutls=true to work') endif if get_option('plugin_synaptics_rmi') error('plugin_synaptics_rmi needs -Dgnutls=true to work') endif endif if get_option('lzma') lzma = dependency('liblzma') conf.set('HAVE_LZMA', '1') else if get_option('plugin_intel_spi') error('plugin_intel_spi needs -Dlzma=true to work') endif 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 if get_option('offline') if not get_option('systemd') warning('-Doffline=true requires -Dsystemd=true') endif conf.set('HAVE_FWUPDOFFLINE', '1') endif host_cpu = host_machine.cpu_family() 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('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('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('fnmatch.h') conf.set('HAVE_FNMATCH_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 if cc.has_header('cpuid.h') and cc.has_header_symbol('cpuid.h', '__get_cpuid_count') and (host_cpu == 'x86' or host_cpu == 'x86_64') conf.set('HAVE_CPUID_H', '1') else if get_option('plugin_msr') error('cpuid.h is required for -Dplugin_msr=true') endif 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('linux/ipmi_msgdefs.h', 'IPMI_DEVICE_IN_FW_UPDATE_ERR') conf.set('HAVE_IPMI_DEVICE_IN_FW_UPDATE_ERR', '1') endif if cc.has_header_symbol('linux/ipmi_msgdefs.h', 'IPMI_DEVICE_IN_INIT_ERR') conf.set('HAVE_IPMI_DEVICE_IN_INIT_ERR', '1') 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 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 if build_standalone and get_option('plugin_tpm') tpm2tss = dependency('tss2-esys', version : '>= 2.0') conf.set('HAVE_TSS2', '1') else tpm2tss = dependency('', required: false) endif if build_standalone and get_option('plugin_uefi_capsule') efiboot = dependency('efiboot') efivar = dependency('efivar') if cc.has_header_symbol('efivar/efivar-types.h', 'efi_time_t', dependencies : efivar) conf.set('HAVE_EFI_TIME_T', '1') endif 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' else EFI_MACHINE_TYPE_NAME = '' endif conf.set_quoted('EFI_MACHINE_TYPE_NAME', EFI_MACHINE_TYPE_NAME) if get_option('efi_binary') efi_binary = dependency ('fwupd-efi', fallback: ['fwupd-efi', 'fwupd_efi_dep']) endif if get_option('plugin_uefi_capsule_splash') r = run_command([python3, 'po/test-deps']) if r.returncode() != 0 error(r.stdout()) endif endif endif if build_standalone and get_option('plugin_dell') libsmbios_c = dependency('libsmbios_c', version : '>= 2.4.0') conf.set('HAVE_DELL', '1') if not get_option('plugin_uefi_capsule') error('plugin_dell also needs plugin_uefi_capsule to work') endif endif if build_standalone and get_option('plugin_modem_manager') libmm_glib = dependency('mm-glib', version : '>= 1.10.0') add_project_arguments('-DMM_REQUIRED_VERSION="1.10.0"', language : 'c') libqmi_glib = dependency('qmi-glib', version : '>= 1.23.1') libmbim_glib = dependency('mbim-glib', version : '>= 1.22.0') endif if get_option('soup_session_compat') conf.set('SOUP_SESSION_COMPAT', '1') endif if build_standalone and get_option('plugin_nvme') if not cc.has_header('linux/nvme_ioctl.h') error('NVMe support requires kernel >= 4.4') endif endif if build_standalone and get_option('plugin_thunderbolt') umockdev = dependency('umockdev-1.0', required: false) conf.set('HAVE_THUNDERBOLT', '1') endif if build_standalone and get_option('plugin_flashrom') libflashrom = dependency('flashrom', fallback : ['flashrom', 'flashrom_dep']) endif if build_standalone and get_option('systemd') systemd = dependency('systemd', version : '>= 211') libsystemd = dependency('libsystemd') 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_pkgconfig_variable('systemdsystemunitdir') systemdsystempresetdir = systemd.get_pkgconfig_variable('systemdsystempresetdir') systemd_shutdown_dir = systemd.get_pkgconfig_variable('systemdshutdowndir') systemd_modules_load_dir = systemd.get_pkgconfig_variable('modulesloaddir') else systemdunitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir', define_variable: ['rootprefix', systemd_root_prefix]) systemdsystempresetdir = systemd.get_pkgconfig_variable('systemdsystempresetdir', define_variable: ['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_pkgconfig_variable('systemdshutdowndir', define_variable: [systemd_root_prefix_varname, systemd_root_prefix]) systemd_modules_load_dir = systemd.get_pkgconfig_variable('modulesloaddir', define_variable: [systemd_root_prefix_varname, systemd_root_prefix]) endif else libsystemd = dependency('', required: false) endif if build_standalone and get_option('elogind') elogind = dependency('libelogind', version : '>= 211') conf.set('HAVE_LOGIND' , '1') endif if build_standalone and get_option('consolekit') conf.set('HAVE_CONSOLEKIT' , '1') endif if get_option('supported_build') conf.set('SUPPORTED_BUILD', '1') endif gnome = import('gnome') i18n = import('i18n') 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' plugin_dir = 'fwupd-plugins-@0@'.format(libfwupdplugin_lt_current) else plugin_dir = join_paths(libdir, 'fwupd-plugins-@0@'.format(libfwupdplugin_lt_current)) endif conf.set_quoted('FWUPD_PLUGINDIR', plugin_dir) endif # sanity check, otherwise there is not point building if host_machine.system() == 'windows' and not get_option('gusb') error('-Dgusb=true 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()) motd_file = '85-fwupd' motd_dir = 'motd.d' conf.set_quoted('MOTD_FILE', motd_file) conf.set_quoted('MOTD_DIR', motd_dir) configure_file( output : 'config.h', configuration : conf ) if build_standalone plugin_deps = [] plugin_deps += libxmlb plugin_deps += gio plugin_deps += giounix plugin_deps += gmodule plugin_deps += gudev plugin_deps += libjsonglib endif if get_option('gusb') plugin_deps += gusb endif if get_option('libarchive') plugin_deps += libarchive endif if get_option('plugin_logitech_bulkcontroller') protobufc = dependency('libprotobuf-c') protoc = find_program('protoc', 'protoc-c') plugin_deps += protobufc endif root_incdir = include_directories('.') if get_option('docs') == 'gtkdoc' gtkdocscan = find_program('gtkdoc-scan') elif get_option('docs') == 'docgen' r = run_command([python3, 'docs/test-deps.py']) if r.returncode() != 0 error(r.stdout()) endif gidocgen_dep = dependency('gi-docgen', version: '>= 2021.1', native: true, fallback: ['gi-docgen', 'dummy_dep'], ) gidocgen = find_program('gi-docgen') if not get_option('introspection') error('introspection is needed for docgen') endif endif subdir('libfwupd') if build_daemon and get_option('polkit') subdir('policy') endif if build_standalone gcab = find_program('gcab', required : get_option('tests')) subdir('data') subdir('po') subdir('libfwupdplugin') subdir('src') subdir('plugins') subdir('contrib') endif subdir('docs') if get_option('systemd') and build_daemon meson.add_install_script('meson_post_install.sh', systemdunitdir) endif makensis = find_program('makensis', required : false) if makensis.found() run_target( 'makensis', command: [ makensis, join_paths(meson.source_root(), 'contrib', 'setup-win32.nsi'), ]) endif fwupd-1.7.5/meson_options.txt000066400000000000000000000150411420024370600163050ustar00rootroot00000000000000option('build', type : 'combo', choices : ['all', 'standalone', 'library'], value : 'all', description : 'build type') option('consolekit', type : 'boolean', value : true, description : 'enable ConsoleKit support') option('static_analysis', type : 'boolean', value : false, description : 'enable GCC static analysis support') option('firmware-packager', type : 'boolean', value : true, description : 'enable firmware-packager installation') option('docs', type : 'combo', choices : ['none', 'gtkdoc', 'docgen'], value : 'docgen', description : 'developer documentation type') option('introspection', type : 'boolean', value : true, description : 'generate GObject Introspection data') 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 : 'boolean', value : true, description : 'enable libarchive support') option('gudev', type : 'boolean', value : true, description : 'enable GUdev support') option('gusb', type : 'boolean', value : true, description : 'enable GUsb support') option('bluez', type : 'boolean', value : false, description : 'enable BlueZ support') option('polkit', type: 'boolean', value : true, description : 'enable PolKit support in daemon') option('gnutls', type: 'boolean', value : true, description : 'enable GnuTLS support') option('sqlite', type: 'boolean', value : true, description : 'enable sqlite support') option('lzma', type: 'boolean', value : false, description : 'enable LZMA support') option('plugin_amt', type : 'boolean', value : true, description : 'enable Intel AMT support') option('plugin_acpi_phat', type : 'boolean', value : true, description : 'enable ACPI PHAT support') option('plugin_bcm57xx', type : 'boolean', value : true, description : 'enable BCM57xx support') option('plugin_cfu', type : 'boolean', value : false, description : 'enable CFU support') option('plugin_cpu', type : 'boolean', value : true, description : 'enable CPU support') option('plugin_dell', type : 'boolean', value : true, description : 'enable Dell-specific support') option('plugin_dummy', type : 'boolean', value : false, description : 'enable the dummy device') option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC support') option('plugin_ep963x', type : 'boolean', value : true, description : 'enable EP963x support') option('plugin_fastboot', type : 'boolean', value : true, description : 'enable Fastboot support') option('plugin_logitech_bulkcontroller', type : 'boolean', value : true, description : 'enable Logitech bulk controller support') option('plugin_parade_lspcon', type : 'boolean', value : true, description : 'enable Parade LSPCON support') option('plugin_pixart_rf', type : 'boolean', value : true, description : 'enable PixartRF support') option('plugin_realtek_mst', type : 'boolean', value : true, description : 'enable Realtek MST hub support') option('plugin_synaptics_mst', type: 'boolean', value: true, description : 'enable Synaptics MST hub support') option('plugin_synaptics_rmi', type: 'boolean', value: true, description : 'enable Synaptics RMI support') option('plugin_tpm', type : 'boolean', value : true, description : 'enable TPM support') option('plugin_thunderbolt', type : 'boolean', value : true, description : 'enable Thunderbolt support') option('plugin_redfish', type : 'boolean', value : true, description : 'enable Redfish support') option('plugin_uefi_capsule', type : 'boolean', value : true, description : 'enable UEFI capsule support') option('plugin_uefi_capsule_splash', type : 'boolean', value : true, description : 'enable UEFI capsule splash support') option('plugin_uefi_pk', type : 'boolean', value : true, description : 'enable UEFI PK support') option('plugin_nitrokey', type : 'boolean', value : true, description : 'enable Nitrokey support') option('plugin_nvme', type : 'boolean', value : true, description : 'enable NVMe support') option('plugin_modem_manager', type : 'boolean', value : false, description : 'enable ModemManager support') option('plugin_msr', type : 'boolean', value : true, description : 'enable MSR support') option('plugin_mtd', type : 'boolean', value : true, description : 'enable MTD support') option('plugin_flashrom', type : 'boolean', value : false, description : 'enable libflashrom support') option('plugin_platform_integrity', type : 'boolean', value : false, description : 'enable platform integrity support') option('plugin_intel_spi', type : 'boolean', value : false, description : 'enable Intel SPI support') option('plugin_uf2', type : 'boolean', value : true, description : 'enable support for UF2') option('plugin_upower', type : 'boolean', value : true, description : 'enable support for UPower') option('plugin_powerd', type : 'boolean', value : true, description : 'enable support for powerd') option('qubes', type : 'boolean', value : false, description : 'build packages for Qubes OS') option('supported_build', type : 'boolean', value : false, description: 'distribution package with upstream support') option('systemd', type : 'boolean', value : true, description : 'enable systemd support') option('systemd_root_prefix', type: 'string', value: '', description: 'Directory to base systemd’s installation directories on') option('elogind', type : 'boolean', value : false, description : 'enable elogind support') option('tests', type : 'boolean', value : true, description : 'enable tests') option('soup_session_compat', type : 'boolean', value : true, description : 'enable SoupSession runtime compatibility support') option('curl', type : 'boolean', value : true, description : 'enable libcurl support') option('udevdir', type: 'string', value: '', description: 'Directory for udev rules') 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: 'boolean', value : true, description : 'enable installing firmware using a pre-boot systemd target') option('compat_cli', type: 'boolean', value : true, description : 'enable legacy commands: fwupdagent,dfu-tool,fwupdate') option('hsi', type: 'boolean', value : true, description : 'enable plugins used just for HSI') fwupd-1.7.5/meson_post_install.sh000077500000000000000000000005361420024370600171260ustar00rootroot00000000000000#!/bin/sh if [ -z $MESON_INSTALL_PREFIX ]; then echo 'This is meant to be ran from Meson only!' exit 1 fi SYSTEMDUNITDIR=$1 echo 'Updating systemd deps' mkdir -p ${DESTDIR}${SYSTEMDUNITDIR}/system-update.target.wants ln -sf ../fwupd-offline-update.service ${DESTDIR}${SYSTEMDUNITDIR}/system-update.target.wants/fwupd-offline-update.service fwupd-1.7.5/plugins/000077500000000000000000000000001420024370600143305ustar00rootroot00000000000000fwupd-1.7.5/plugins/README.md000066400000000000000000000021611420024370600156070ustar00rootroot00000000000000# 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.7.5/plugins/acpi-dmar/000077500000000000000000000000001420024370600161655ustar00rootroot00000000000000fwupd-1.7.5/plugins/acpi-dmar/README.md000066400000000000000000000004111420024370600174400ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/acpi-dmar/fu-acpi-dmar.c000066400000000000000000000041021420024370600205730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-acpi-dmar.h" struct _FuAcpiDmar { GObject parent_instance; gboolean opt_in; }; G_DEFINE_TYPE(FuAcpiDmar, fu_acpi_dmar, G_TYPE_OBJECT) #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); gchar creator_id[5] = {'\0'}; gchar oem_table_id[9] = {'\0'}; gchar signature[5] = {'\0'}; gsize bufsz = 0; guint8 flags = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); /* parse table */ if (!fu_memcpy_safe((guint8 *)signature, sizeof(signature), 0x0, /* dst */ buf, bufsz, 0x00, /* src */ sizeof(signature) - 1, error)) return NULL; if (strcmp(signature, "DMAR") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not a DMAR table, got %s", signature); return NULL; } if (!fu_memcpy_safe((guint8 *)oem_table_id, sizeof(oem_table_id), 0x0, /* dst */ buf, bufsz, 0x10, /* src */ sizeof(oem_table_id) - 1, error)) return NULL; g_debug("OemTableId: %s", oem_table_id); if (!fu_memcpy_safe((guint8 *)creator_id, sizeof(creator_id), 0x0, /* dst */ buf, bufsz, 0x1c, /* src */ sizeof(creator_id) - 1, error)) return NULL; g_debug("CreatorId: %s", creator_id); if (!fu_memcpy_safe(&flags, sizeof(flags), 0x0, /* dst */ buf, bufsz, 0x25, /* src */ sizeof(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.7.5/plugins/acpi-dmar/fu-acpi-dmar.h000066400000000000000000000005751420024370600206120ustar00rootroot00000000000000/* * 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, GObject) FuAcpiDmar * fu_acpi_dmar_new(GBytes *blob, GError **error); gboolean fu_acpi_dmar_get_opt_in(FuAcpiDmar *self); fwupd-1.7.5/plugins/acpi-dmar/fu-plugin-acpi-dmar.c000066400000000000000000000035271420024370600221010ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-acpi-dmar.h" static void fu_plugin_acpi_dmar_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_common_get_cpu_vendor() != FU_CPU_VENDOR_INTEL) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_ACPI_DMAR); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fu_security_attrs_append(attrs, attr); /* load DMAR table */ path = fu_common_get_path(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "DMAR", NULL); blob = fu_common_get_contents_bytes(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_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->add_security_attrs = fu_plugin_acpi_dmar_add_security_attrs; } fwupd-1.7.5/plugins/acpi-dmar/fu-self-test.c000066400000000000000000000035121420024370600206500ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_contents_bytes(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_common_get_contents_bytes(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) { 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); 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.7.5/plugins/acpi-dmar/meson.build000066400000000000000000000021231420024370600203250ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiDmar"'] shared_module('fu_plugin_acpi_dmar', fu_hash, sources : [ 'fu-plugin-acpi-dmar.c', 'fu-acpi-dmar.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-acpi-dmar.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('acpi-dmar-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/acpi-facp/000077500000000000000000000000001420024370600161535ustar00rootroot00000000000000fwupd-1.7.5/plugins/acpi-facp/README.md000066400000000000000000000003501420024370600174300ustar00rootroot00000000000000# ACPI FACP ## 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`. fwupd-1.7.5/plugins/acpi-facp/fu-acpi-facp.c000066400000000000000000000020441420024370600205520ustar00rootroot00000000000000/* * 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_common_read_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.7.5/plugins/acpi-facp/fu-acpi-facp.h000066400000000000000000000005721420024370600205630ustar00rootroot00000000000000/* * 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.7.5/plugins/acpi-facp/fu-plugin-acpi-facp.c000066400000000000000000000034071420024370600220520ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-acpi-facp.h" static void fu_plugin_acpi_facp_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 = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fu_security_attrs_append(attrs, attr); /* load FACP table */ path = fu_common_get_path(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "FACP", NULL); blob = fu_common_get_contents_bytes(fn, &error_local); if (blob == NULL) { g_warning("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; } if (!fu_acpi_facp_get_s2i(facp)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->add_security_attrs = fu_plugin_acpi_facp_add_security_attrs; } fwupd-1.7.5/plugins/acpi-facp/fu-self-test.c000066400000000000000000000035351420024370600206430ustar00rootroot00000000000000/* * 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_common_get_contents_bytes(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_common_get_contents_bytes(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) { 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); 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.7.5/plugins/acpi-facp/meson.build000066400000000000000000000021231420024370600203130ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiFacp"'] shared_module('fu_plugin_acpi_facp', fu_hash, sources : [ 'fu-plugin-acpi-facp.c', 'fu-acpi-facp.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-acpi-facp.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('acpi-facp-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/acpi-phat/000077500000000000000000000000001420024370600161765ustar00rootroot00000000000000fwupd-1.7.5/plugins/acpi-phat/README.md000066400000000000000000000016001420024370600174520ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/acpi-phat/fu-acpi-phat-health-record.c000066400000000000000000000153051420024370600233430ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-acpi-phat-health-record.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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); gsize bufsz = 0; guint16 rcdlen = 0; guint32 dataoff = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); fwupd_guid_t guid = {0x0}; /* record length */ if (!fu_common_read_uint16_safe(buf, bufsz, 2, &rcdlen, G_LITTLE_ENDIAN, error)) return FALSE; 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; } /* am healthy */ if (!fu_common_read_uint8_safe(buf, bufsz, 7, &self->am_healthy, error)) return FALSE; /* device signature */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, 8, /* src */ sizeof(guid), error)) return FALSE; self->guid = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); /* read the data offset to work out the size of the middle part */ if (!fu_common_read_uint32_safe(buf, bufsz, 24, &dataoff, G_LITTLE_ENDIAN, error)) return FALSE; /* device path */ if (bufsz > 28) { gsize ubufsz; /* bytes */ g_autofree gunichar2 *ubuf = NULL; /* header -> devicepath -> data */ if (dataoff == 0x0) { ubufsz = bufsz - 28; } else { ubufsz = dataoff - 28; } if (ubufsz > bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "device path too large: 0x%x", (guint)ubufsz); return FALSE; } /* check this is an even number of bytes */ if (ubufsz % 2 != 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 = g_new0(gunichar2, ubufsz / 2); if (!fu_memcpy_safe((guint8 *)ubuf, ubufsz, 0x0, /* dst */ buf, bufsz, 28, /* src */ ubufsz, error)) return FALSE; self->device_path = g_utf16_to_utf8(ubuf, ubufsz / 2, NULL, NULL, error); if (self->device_path == NULL) return FALSE; } /* success */ return TRUE; } static GBytes * fu_acpi_phat_health_record_write(FuFirmware *firmware, GError **error) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); fwupd_guid_t guid = {0x0}; glong device_path_utf16sz = 0; g_autofree gunichar2 *device_path_utf16 = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); /* convert device path ahead of time to get total record length */ if (self->device_path != NULL) { device_path_utf16 = g_utf8_to_utf16(self->device_path, -1, NULL, &device_path_utf16sz, error); if (device_path_utf16 == NULL) return NULL; device_path_utf16sz *= 2; } /* data record */ fu_byte_array_append_uint16(buf, FU_ACPI_PHAT_RECORD_TYPE_HEALTH, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 28 + device_path_utf16sz, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, fu_firmware_get_version_raw(firmware)); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint8(buf, self->am_healthy); /* device signature */ if (self->guid != NULL) { if (!fwupd_guid_from_string(self->guid, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; } g_byte_array_append(buf, guid, sizeof(guid)); /* device-specific data unsupported */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* device path */ if (self->device_path != NULL) { g_byte_array_append(buf, (const guint8 *)device_path_utf16, device_path_utf16sz); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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) { } 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.7.5/plugins/acpi-phat/fu-acpi-phat-health-record.h000066400000000000000000000006461420024370600233520ustar00rootroot00000000000000/* * 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.7.5/plugins/acpi-phat/fu-acpi-phat-version-element.c000066400000000000000000000120471420024370600237360ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); fwupd_guid_t component_id = {0x0}; gchar producer_id[4] = {'\0'}; gsize bufsz = 0; guint64 version_value = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* hardcoded */ fu_firmware_set_size(firmware, 28); if (!fu_memcpy_safe((guint8 *)&component_id, sizeof(component_id), 0x0, /* dst */ buf, bufsz, 0, /* src */ sizeof(component_id), error)) return FALSE; self->guid = fwupd_guid_to_string(&component_id, FWUPD_GUID_FLAG_MIXED_ENDIAN); if (!fu_common_read_uint64_safe(buf, bufsz, 16, &version_value, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_version_raw(firmware, version_value); if (!fu_memcpy_safe((guint8 *)producer_id, sizeof(producer_id), 0x0, /* dst */ buf, bufsz, 24, /* src */ sizeof(producer_id), error)) return FALSE; if (memcmp(producer_id, "\0\0\0\0", 4) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PHAT version element invalid"); return FALSE; } self->producer_id = fu_common_strsafe((const gchar *)producer_id, sizeof(producer_id)); return TRUE; } static GBytes * fu_acpi_phat_version_element_write(FuFirmware *firmware, GError **error) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); fwupd_guid_t guid = {0x0}; guint8 producer_id[4] = {'\0'}; g_autoptr(GByteArray) buf = g_byte_array_new(); /* component ID */ if (self->guid != NULL) { if (!fwupd_guid_from_string(self->guid, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; } g_byte_array_append(buf, guid, sizeof(guid)); /* version value */ fu_byte_array_append_uint64(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); /* producer ID */ if (self->producer_id != NULL) { gsize producer_idsz = strlen(self->producer_id); if (!fu_memcpy_safe(producer_id, sizeof(producer_id), 0x0, /* dst */ (const guint8 *)self->producer_id, producer_idsz, 0x0, /* src */ producer_idsz, error)) return NULL; } g_byte_array_append(buf, producer_id, sizeof(producer_id)); /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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) { } 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.7.5/plugins/acpi-phat/fu-acpi-phat-version-element.h000066400000000000000000000006621420024370600237430ustar00rootroot00000000000000/* * 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.7.5/plugins/acpi-phat/fu-acpi-phat-version-record.c000066400000000000000000000056541420024370600235710ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; gsize offset = 0; guint32 record_count = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); if (!fu_common_read_uint32_safe(buf, bufsz, offset + 8, &record_count, G_LITTLE_ENDIAN, error)) return FALSE; 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_common_bytes_new_offset(fw, offset + 12, 28, error); if (fw_tmp == NULL) return FALSE; fu_firmware_set_offset(firmware_tmp, offset + 12); if (!fu_firmware_parse(firmware_tmp, fw_tmp, flags, error)) return FALSE; fu_firmware_add_image(firmware, firmware_tmp); offset += fu_firmware_get_size(firmware_tmp); } return TRUE; } static GBytes * fu_acpi_phat_version_record_write(FuFirmware *firmware, GError **error) { 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 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_byte_array_append_uint16(buf, FU_ACPI_PHAT_RECORD_TYPE_VERSION, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 12 + buf2->len, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, fu_firmware_get_version_raw(firmware)); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint32(buf, images->len, G_LITTLE_ENDIAN); /* element data */ g_byte_array_append(buf, buf2->data, buf2->len); return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_acpi_phat_version_record_init(FuAcpiPhatVersionRecord *self) { } 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.7.5/plugins/acpi-phat/fu-acpi-phat-version-record.h000066400000000000000000000006541420024370600235710ustar00rootroot00000000000000/* * 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.7.5/plugins/acpi-phat/fu-acpi-phat.c000066400000000000000000000230061420024370600206210ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_read_uint16_safe(buf, bufsz, *offset, &record_type, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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_common_read_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_common_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; fu_firmware_add_image(firmware, firmware_rcd); } *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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuAcpiPhat *self = FU_ACPI_PHAT(firmware); gchar oem_id[6] = {'\0'}; gchar oem_table_id[8] = {'\0'}; gchar signature[4] = {'\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_memcpy_safe((guint8 *)signature, sizeof(signature), 0x0, /* dst */ buf, bufsz, 0x00, /* src */ sizeof(signature), error)) return FALSE; if (memcmp(signature, "PHAT", 4) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not a PHAT table, got %s", signature); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, 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_common_read_uint8_safe(buf, bufsz, 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_common_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, 10, /* src */ sizeof(oem_id), error)) return FALSE; oem_id_safe = fu_common_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, 16, /* src */ sizeof(oem_table_id), error)) return FALSE; oem_table_id_safe = fu_common_strsafe((const gchar *)oem_table_id, sizeof(oem_table_id)); fu_firmware_set_id(firmware, oem_table_id_safe); if (!fu_common_read_uint32_safe(buf, bufsz, 24, &oem_revision, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_version_raw(firmware, oem_revision); /* platform telemetry records */ for (gsize offset = 36; offset < length;) { if (!fu_acpi_phat_record_parse(firmware, fw, &offset, flags, error)) return FALSE; } /* success */ return TRUE; } static GBytes * 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_common_sum8(buf->data, buf->len); /* success */ return g_byte_array_free_to_bytes(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_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->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.7.5/plugins/acpi-phat/fu-acpi-phat.h000066400000000000000000000007671420024370600206370ustar00rootroot00000000000000/* * 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.7.5/plugins/acpi-phat/fu-plugin-acpi-phat.c000066400000000000000000000027361420024370600221240ustar00rootroot00000000000000/* * 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-element.h" #include "fu-acpi-phat-version-record.h" #include "fu-acpi-phat.h" static void fu_plugin_acpi_phat_init(FuPlugin *plugin) { 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 gboolean fu_plugin_acpi_phat_coldplug(FuPlugin *plugin, 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_common_get_path(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "PHAT", NULL); blob = fu_common_get_contents_bytes(fn, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(phat, blob, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; str = fu_acpi_phat_to_report_string(FU_ACPI_PHAT(phat)); fu_plugin_add_report_metadata(plugin, "PHAT", str); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_acpi_phat_init; vfuncs->coldplug = fu_plugin_acpi_phat_coldplug; } fwupd-1.7.5/plugins/acpi-phat/fu-self-test.c000066400000000000000000000022751420024370600206660ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_contents_bytes(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ret = fu_firmware_parse(phat, blob, FWUPD_INSTALL_FLAG_FORCE, &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) { 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); 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.7.5/plugins/acpi-phat/meson.build000066400000000000000000000025771420024370600203530ustar00rootroot00000000000000if get_option('plugin_acpi_phat') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiPhat"'] shared_module('fu_plugin_acpi_phat', fu_hash, sources : [ 'fu-plugin-acpi-phat.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-acpi-phat.c', 'fu-acpi-phat-health-record.c', 'fu-acpi-phat-version-element.c', 'fu-acpi-phat-version-record.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('acpi-phat-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/acpi-phat/tests/000077500000000000000000000000001420024370600173405ustar00rootroot00000000000000fwupd-1.7.5/plugins/acpi-phat/tests/acpi-phat.bin000066400000000000000000000003101420024370600216720ustar00rootroot00000000000000PHATHUGHESSUDODAVEFWUP0000D3@fN2Ą#HUGHൂ dzG@Om$LENO(KuRF31%DELL ,KuRF31/dev/foofwupd-1.7.5/plugins/acpi-phat/tests/acpi-phat.builder.xml000066400000000000000000000022761420024370600233640ustar00rootroot00000000000000 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.7.5/plugins/amt/000077500000000000000000000000001420024370600151115ustar00rootroot00000000000000fwupd-1.7.5/plugins/amt/README.md000066400000000000000000000015461420024370600163760ustar00rootroot00000000000000# Intel Management Engine ## Introduction This plugin is used to get the version number on the Intel Management Engine. 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 GUID provided by the AMT host interface. ## Vendor ID Security The device is not upgradable and thus requires no vendor ID set. ## External Interface Access This plugin requires read only access to `/dev/mei0`. fwupd-1.7.5/plugins/amt/fu-plugin-amt.c000066400000000000000000000344401420024370600177470ustar00rootroot00000000000000/* * 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 #include #include #include #include #include typedef struct { uuid_le guid; guint buf_size; guchar prot_ver; gint fd; } mei_context; static void mei_context_free(mei_context *cl) { if (cl->fd != -1) close(cl->fd); g_free(cl); } static gboolean mei_context_new(mei_context *ctx, const uuid_le *guid, guchar req_protocol_version, GError **error) { gint result; struct mei_client *cl; struct mei_connect_client_data data; ctx->fd = open("/dev/mei0", O_RDWR); if (ctx->fd == -1 && errno == ENOENT) ctx->fd = open("/dev/mei", O_RDWR); if (ctx->fd == -1) { if (errno == ENOENT) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Unable to find a ME interface"); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Cannot open /dev/mei0"); } return FALSE; } memcpy(&ctx->guid, guid, sizeof(*guid)); memset(&data, 0, sizeof(data)); memcpy(&data.in_client_uuid, &ctx->guid, sizeof(ctx->guid)); result = ioctl(ctx->fd, IOCTL_MEI_CONNECT_CLIENT, &data); if (result != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ME refused connection"); 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; } ctx->buf_size = cl->max_msg_length; ctx->prot_ver = cl->protocol_version; return TRUE; } static gboolean mei_recv_msg(mei_context *ctx, guchar *buffer, gssize len, guint32 *readsz, unsigned long timeout, GError **error) { gssize rc; rc = read(ctx->fd, buffer, len); if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "read failed with status %zd %s", rc, strerror(errno)); return FALSE; } if (readsz != NULL) *readsz = rc; return TRUE; } static gboolean mei_send_msg(mei_context *ctx, const guchar *buffer, gssize len, unsigned long timeout, GError **error) { struct timeval tv; gssize written; gssize rc; fd_set set; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000000; written = write(ctx->fd, buffer, len); if (written < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed with status %zd %s", written, strerror(errno)); return FALSE; } if (written != len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "only wrote %" G_GSSIZE_FORMAT " of %" G_GSSIZE_FORMAT, written, len); return FALSE; } FD_ZERO(&set); FD_SET(ctx->fd, &set); rc = select(ctx->fd + 1, &set, NULL, NULL, &tv); if (rc > 0 && FD_ISSET(ctx->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 %zd", rc); return FALSE; } /*************************************************************************** * Intel Advanced Management Technology ME Client ***************************************************************************/ #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 { mei_context mei_cl; }; static gboolean amt_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 amt_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 amt_host_if_call(mei_context *mei_cl, const guchar *command, gssize command_sz, guint8 **read_buf, guint32 rcmd, guint expected_sz, unsigned long send_timeout, GError **error) { guint32 in_buf_sz; guint32 out_buf_sz; struct amt_host_if_resp_header *msg_hdr; in_buf_sz = mei_cl->buf_size; *read_buf = (guint8 *)g_malloc0(in_buf_sz); msg_hdr = (struct amt_host_if_resp_header *)*read_buf; if (!mei_send_msg(mei_cl, command, command_sz, send_timeout, error)) return FALSE; if (!mei_recv_msg(mei_cl, *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 %" G_GUINT32_FORMAT, expected_sz, out_buf_sz); return FALSE; } if (!amt_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 amt_get_provisioning_state(mei_context *mei_cl, guint8 *state, GError **error) { g_autofree struct amt_host_if_resp_header *response = NULL; if (!amt_host_if_call(mei_cl, (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; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(mei_context, mei_context_free) #pragma clang diagnostic pop static FuDevice * fu_plugin_amt_create_device(FuPlugin *plugin, GError **error) { guint8 state; struct amt_code_versions ver; fwupd_guid_t uu; g_autofree gchar *guid_buf = NULL; g_autofree struct amt_host_if_resp_header *response = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GString) version_bl = g_string_new(NULL); g_autoptr(GString) version_fw = g_string_new(NULL); g_autoptr(mei_context) ctx = g_new0(mei_context, 1); const uuid_le MEI_IAMTHIF = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, 0x81, 0x4c); /* create context */ if (!mei_context_new(ctx, &MEI_IAMTHIF, 0, error)) return NULL; /* check version */ if (!amt_host_if_call(ctx, (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 NULL; } if (!amt_verify_code_versions(response, error)) { g_prefix_error(error, "failed to verify code versions: "); return NULL; } memcpy(&ver, response->data, sizeof(struct amt_code_versions)); dev = fu_device_new_with_context(fu_plugin_get_context(plugin)); fu_device_set_id(dev, "/dev/mei0"); fu_device_set_vendor(dev, "Intel Corporation"); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(dev, "computer"); fu_device_add_parent_guid(dev, "main-system-firmware"); if (!amt_get_provisioning_state(ctx, &state, error)) return NULL; switch (state) { case 0: fu_device_set_name(dev, "Intel AMT [unprovisioned]"); break; case 1: fu_device_set_name(dev, "Intel AMT [being provisioned]"); break; case 2: fu_device_set_name(dev, "Intel AMT [provisioned]"); break; default: fu_device_set_name(dev, "Intel AMT [unknown]"); break; } fu_device_set_summary(dev, "Hardware and firmware technology for remote " "out-of-band management"); /* add guid */ memcpy(&uu, &ctx->guid, 16); guid_buf = fwupd_guid_to_string((const fwupd_guid_t *)&uu, FWUPD_GUID_FLAG_NONE); fu_device_add_guid(dev, guid_buf); /* 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; } } fu_device_set_version_format(dev, FWUPD_VERSION_FORMAT_INTEL_ME); if (version_fw->len > 0) fu_device_set_version(dev, version_fw->str); if (version_bl->len > 0) fu_device_set_version_bootloader(dev, version_bl->str); return g_steal_pointer(&dev); } static gboolean fu_plugin_amt_coldplug(FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) dev = NULL; dev = fu_plugin_amt_create_device(plugin, error); if (dev == NULL) return FALSE; fu_plugin_device_add(plugin, dev); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->coldplug = fu_plugin_amt_coldplug; } fwupd-1.7.5/plugins/amt/meson.build000066400000000000000000000006251420024370600172560ustar00rootroot00000000000000if get_option('plugin_amt') cargs = ['-DG_LOG_DOMAIN="FuPluginAmt"'] shared_module('fu_plugin_amt', fu_hash, sources : [ 'fu-plugin-amt.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/analogix/000077500000000000000000000000001420024370600161325ustar00rootroot00000000000000fwupd-1.7.5/plugins/analogix/README.md000066400000000000000000000017041420024370600174130ustar00rootroot00000000000000# 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` ## 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` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. fwupd-1.7.5/plugins/analogix/analogix.quirk000066400000000000000000000003271420024370600210130ustar00rootroot00000000000000# 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 fwupd-1.7.5/plugins/analogix/fu-analogix-common.c000066400000000000000000000007071420024370600220020ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-analogix-common.h" const gchar * fu_analogix_update_status_to_string(AnxUpdateStatus status) { if (status == UPDATE_STATUS_INVALID) return "invalid"; if (status == UPDATE_STATUS_START) return "start"; if (status == UPDATE_STATUS_FINISH) return "finish"; if (status == UPDATE_STATUS_ERROR) return "error"; return NULL; } fwupd-1.7.5/plugins/analogix/fu-analogix-common.h000066400000000000000000000026011420024370600220020ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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; typedef enum { UPDATE_STATUS_INVALID, UPDATE_STATUS_START, UPDATE_STATUS_FINISH, UPDATE_STATUS_ERROR = 0xFF, } AnxUpdateStatus; const gchar * fu_analogix_update_status_to_string(AnxUpdateStatus status); fwupd-1.7.5/plugins/analogix/fu-analogix-device.c000066400000000000000000000276071420024370600217610ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-analogix-common.h" #include "fu-analogix-device.h" #include "fu-analogix-firmware.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_common_string_append_kx(str, idt, "OcmVersion", self->ocm_version); fu_common_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, AnxUpdateStatus *status, GError **error) { for (guint i = 0; i < 3000; i++) { guint8 status_tmp = 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; if ((status_tmp != UPDATE_STATUS_ERROR) && (status_tmp != UPDATE_STATUS_INVALID)) { if (status != NULL) *status = status_tmp; return TRUE; } /* wait 1ms */ g_usleep(1000); } 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_common_read_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_common_read_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) { #if G_USB_CHECK_VERSION(0, 3, 3) 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) { g_autoptr(GPtrArray) endpoints = NULL; endpoints = g_usb_interface_get_endpoints(intf); if (endpoints == NULL) continue; 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; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif } static gboolean fu_analogix_device_probe(FuDevice *device, GError **error) { /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_analogix_device_parent_class)->probe(device, error)) return FALSE; 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_image(FuAnalogixDevice *self, FuFirmware *image, guint16 req_val, FuProgress *progress, GError **error) { AnxUpdateStatus status = UPDATE_STATUS_INVALID; guint8 buf_init[4] = {0x0}; g_autoptr(GBytes) block_bytes = NULL; g_autoptr(GPtrArray) 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); /* initialization */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* offset into firmware */ block_bytes = fu_firmware_get_bytes(image, error); if (block_bytes == NULL) return FALSE; /* initialization */ fu_common_write_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, 0x00, BILLBOARD_MAX_PACKET_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_set_percentage_full(fu_progress_get_child(progress), i + 1, chunks->len); } 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); g_autoptr(FuFirmware) fw_cus = NULL; g_autoptr(FuFirmware) fw_ocm = NULL; g_autoptr(FuFirmware) fw_srx = NULL; g_autoptr(FuFirmware) fw_stx = 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, 25); /* cus */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 25); /* stx */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 25); /* srx */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 25); /* ocm */ /* CUSTOM_DEF */ fw_cus = fu_firmware_get_image_by_id(firmware, "custom", NULL); 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 */ fw_stx = fu_firmware_get_image_by_id(firmware, "stx", NULL); 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 */ fw_srx = fu_firmware_get_image_by_id(firmware, "srx", NULL); 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 */ fw_ocm = fu_firmware_get_image_by_id(firmware, "ocm", NULL); 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 void fu_analogix_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_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_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ANALOGIX_FIRMWARE); } 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->setup = fu_analogix_device_setup; klass_device->probe = fu_analogix_device_probe; klass_device->set_progress = fu_analogix_device_set_progress; } fwupd-1.7.5/plugins/analogix/fu-analogix-device.h000066400000000000000000000005651420024370600217600ustar00rootroot00000000000000/* * 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) struct _FuAnalogixDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.7.5/plugins/analogix/fu-analogix-firmware.c000066400000000000000000000077531420024370600223360ustar00rootroot00000000000000/* * 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, guint64 addr_start, guint64 addr_end, 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, addr_start, addr_end, 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_common_bytes_new_offset(blob, 0, CUSTOM_FLASH_SIZE, error); } else { blob_ocm = fu_common_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_common_read_uint8_safe(buf, bufsz, OCM_FW_VERSION_ADDR - FLASH_OCM_ADDR + 8, &version_hi, error)) return FALSE; if (!fu_common_read_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_common_bytes_new_offset(blob, FLASH_TXFW_ADDR - FLASH_OCM_ADDR, SECURE_OCM_TX_SIZE, NULL); if (blob_stx != NULL && !fu_common_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_common_bytes_new_offset(blob, FLASH_RXFW_ADDR - FLASH_OCM_ADDR, SECURE_OCM_RX_SIZE, NULL); if (blob_srx != NULL && !fu_common_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_common_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; } FuFirmware * fu_analogix_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ANALOGIX_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/analogix/fu-analogix-firmware.h000066400000000000000000000006101420024370600223240ustar00rootroot00000000000000/* * 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) FuFirmware * fu_analogix_firmware_new(void); fwupd-1.7.5/plugins/analogix/fu-plugin-analogix.c000066400000000000000000000010451420024370600220040ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-analogix-device.h" #include "fu-analogix-firmware.h" static void fu_plugin_analogix_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_ANALOGIX_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ANALOGIX_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_analogix_init; } fwupd-1.7.5/plugins/analogix/meson.build000066400000000000000000000011201420024370600202660ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginAnalogix"'] install_data(['analogix.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_analogix', fu_hash, sources : [ 'fu-plugin-analogix.c', 'fu-analogix-device.c', 'fu-analogix-common.c', 'fu-analogix-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/ata/000077500000000000000000000000001420024370600150755ustar00rootroot00000000000000fwupd-1.7.5/plugins/ata/README.md000066400000000000000000000030151420024370600163530ustar00rootroot00000000000000# 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. fwupd-1.7.5/plugins/ata/ata.quirk000066400000000000000000000025361420024370600167250ustar00rootroot00000000000000# match all devices with this udev subsystem [BLOCK] Plugin = ata [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.7.5/plugins/ata/fu-ata-device.c000066400000000000000000000632371420024370600176660ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) 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_common_string_append_kx(str, idt, "TransferMode", self->transfer_mode); fu_common_string_append_kx(str, idt, "TransferBlocks", self->transfer_blocks); if (self->oui != 0x0) fu_common_string_append_kx(str, idt, "OUI", self->oui); fu_common_string_append_ku(str, idt, "PciDepth", self->pci_depth); fu_common_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); fu_common_string_replace(str, " ", "_"); 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 *)(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"}, {"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 (fu_common_fnmatch(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 (fu_common_fnmatch(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 (fu_common_fnmatch(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_common_read_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_ONLY_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); } 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)); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_ata_device_parent_class)->probe(device, error)) return FALSE; /* 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; if (g_getenv("FWUPD_ATA_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "CBD", cdb, sizeof(cdb)); if (dxfer_direction == SG_DXFER_TO_DEV && dxferp != NULL) { fu_common_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, error)) return FALSE; if (g_getenv("FWUPD_ATA_VERBOSE") != NULL) { 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_common_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]; if (g_getenv("FWUPD_ATA_VERBOSE") != NULL) { 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; } if (g_getenv("FWUPD_ATA_VERBOSE") != NULL) fu_common_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(GPtrArray) 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, 0x00, chunksz); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len); } /* 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_common_strtoull_full(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_common_strtoull_full(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_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.7.5/plugins/ata/fu-ata-device.h000066400000000000000000000010211420024370600176520ustar00rootroot00000000000000/* * 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.7.5/plugins/ata/fu-plugin-ata.c000066400000000000000000000007261420024370600177170ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ata-device.h" static void fu_plugin_ata_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_ATA_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_ata_init; } fwupd-1.7.5/plugins/ata/fu-self-test.c000066400000000000000000000061031420024370600175570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_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/ata/id", fu_ata_id_func); g_test_add_func("/fwupd/ata/oui", fu_ata_oui_func); return g_test_run(); } fwupd-1.7.5/plugins/ata/meson.build000066400000000000000000000023531420024370600172420ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginAta"'] install_data([ 'ata.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ata', fu_hash, sources : [ 'fu-plugin-ata.c', 'fu-ata-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-ata-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('ata-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/bcm57xx/000077500000000000000000000000001420024370600156255ustar00rootroot00000000000000fwupd-1.7.5/plugins/bcm57xx/README.md000066400000000000000000000022241420024370600171040ustar00rootroot00000000000000# 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. fwupd-1.7.5/plugins/bcm57xx/bcm57xx.quirk000066400000000000000000000003131420024370600201740ustar00rootroot00000000000000# Broadcom BCM5719 [PCI\VEN_14E4&DEV_1657] Plugin = bcm57xx # Dell PCI card [PCI\VEN_14E4&DEV_1657&SUBSYS_14E41904] FirmwareSize = 0x80000 [PCI\VEN_14E4&DEV_1657&SUBSYS_94E41904] FirmwareSize = 0x80000 fwupd-1.7.5/plugins/bcm57xx/fu-bcm57xx-common.c000066400000000000000000000051671420024370600211750ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #include #include "fu-bcm57xx-common.h" guint32 fu_bcm57xx_nvram_crc(const guint8 *buf, gsize bufsz) { return fu_common_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_common_read_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_common_read_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.7.5/plugins/bcm57xx/fu-bcm57xx-common.h000066400000000000000000000035221420024370600211730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/plugins/bcm57xx/fu-bcm57xx-device.c000066400000000000000000000507461420024370600211470ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: GPL-2+ */ #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 #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_common_string_append_kv(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; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_bcm57xx_device_parent_class)->probe(device, error)) return FALSE; /* 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"); g_usleep(50 * 1000); } ifaces = fu_common_filename_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); /* sanity check */ 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_progress_sleep(progress, 5000); 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_NONE, 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_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, flags, error)) { g_prefix_error(error, "failed to parse existing firmware: "); return NULL; } if (g_getenv("FWUPD_BCM57XX_VERBOSE") != NULL) { g_autofree gchar *str = fu_firmware_to_string(firmware); g_debug("existing device firmware: %s", str); } /* 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++; } } if (g_getenv("FWUPD_BCM57XX_VERBOSE") != NULL) { g_autofree gchar *str = fu_firmware_to_string(firmware); g_debug("proposed device firmware: %s", str); } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_bcm57xx_device_write_chunks(FuBcm57xxDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error) { 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_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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 19); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2); /* 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, 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_common_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_debug("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) { g_autofree gchar *fwversion_str = NULL; /* this is only set on the OSS firmware */ fwversion_str = fu_common_version_from_uint32(GUINT32_FROM_BE(fwversion), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, fwversion_str); fu_device_set_version_raw(device, 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 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_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.7.5/plugins/bcm57xx/fu-bcm57xx-device.h000066400000000000000000000004551420024370600211440ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-bcm57xx-dict-image.c000066400000000000000000000104701420024370600217010ustar00rootroot00000000000000/* * 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" 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, guint64 addr_start, guint64 addr_end, 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_common_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 GBytes * 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_byte_array_free_to_bytes(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); 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.7.5/plugins/bcm57xx/fu-bcm57xx-dict-image.h000066400000000000000000000012151420024370600217030ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-bcm57xx-firmware.c000066400000000000000000000435761420024370600215270ustar00rootroot00000000000000/* * 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" #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_common_read_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_common_read_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_common_read_uint16_safe(buf, bufsz, BCM_NVRAM_INFO_VENDOR, &self->vendor, G_BIG_ENDIAN, error)) return NULL; if (!fu_common_read_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_common_read_uint32_safe(buf, bufsz, BCM_NVRAM_HEADER_BASE + BCM_NVRAM_HEADER_SIZE_WRDS, &stage1_wrds, G_BIG_ENDIAN, error)) return NULL; if (!fu_common_read_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_common_bytes_new_offset(fw, stage1_off, stage1_sz, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(img, blob, flags, 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_common_read_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_common_bytes_new_offset(fw, stage2_off + 0x8, stage2_sz, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(img, blob, flags, 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_common_read_uint32_safe(buf, bufsz, base + BCM_NVRAM_DIRECTORY_ADDR, &dict_addr, G_BIG_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, base + BCM_NVRAM_DIRECTORY_SIZE_WRDS, &dict_info, G_BIG_ENDIAN, error)) return FALSE; if (!fu_common_read_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_common_bytes_new_offset(fw, dict_off, dict_sz, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(img, blob, flags, error)) return FALSE; /* success */ fu_firmware_add_image(FU_FIRMWARE(self), img); return TRUE; } static gboolean fu_bcm57xx_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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_common_read_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_bytes(img, fw); 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_common_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_common_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_common_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_common_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) { GByteArray *tmp = g_byte_array_sized_new(sz); for (gsize i = 0; i < sz; i++) fu_byte_array_append_uint8(tmp, 0x0); return g_byte_array_free_to_bytes(tmp); } 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 GBytes * 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 { 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_common_write_uint16(tmp->data + BCM_NVRAM_INFO_VENDOR, self->vendor, G_BIG_ENDIAN); fu_common_write_uint16(tmp->data + BCM_NVRAM_INFO_DEVICE, self->model, G_BIG_ENDIAN); blob_info = g_byte_array_free_to_bytes(tmp); } 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_byte_array_free_to_bytes(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->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.7.5/plugins/bcm57xx/fu-bcm57xx-firmware.h000066400000000000000000000010441420024370600215140ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-bcm57xx-recovery-device.c000066400000000000000000000607741420024370600230050ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_MMAN_H #include #endif #ifdef HAVE_VALGRIND #include #endif /* HAVE_VALGRIND */ #include #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) { 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_set_percentage_full(progress, i + 1, bufsz); } /* 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; } 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_set_percentage_full(progress, i + 1, bufsz_dwrds); } /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5); /* 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) { g_autofree gchar *fwversion_str = NULL; /* this is only set on the OSS firmware */ fwversion_str = fu_common_version_from_uint32(GUINT32_FROM_BE(fwversion), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, fwversion_str); fu_device_set_version_raw(device, 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 */ if (g_getenv("FWUPD_BCM57XX_VERBOSE") != NULL) 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, 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; if (g_getenv("FWUPD_BCM57XX_VERBOSE") != NULL) 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_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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_bcm57xx_recovery_device_parent_class)->probe(device, error)) return FALSE; 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.7.5/plugins/bcm57xx/fu-bcm57xx-recovery-device.h000066400000000000000000000006661420024370600230040ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-bcm57xx-stage1-image.c000066400000000000000000000103431420024370600221410ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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, guint64 addr_start, guint64 addr_end, 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_common_read_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_common_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_common_read_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_common_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 GBytes * 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)); /* payload */ fu_byte_array_append_bytes(buf, fw_nocrc); /* update version */ if (!fu_common_write_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))); /* add CRC */ crc = fu_bcm57xx_nvram_crc(buf->data, buf->len); fu_byte_array_append_uint32(buf, crc, G_LITTLE_ENDIAN); return g_byte_array_free_to_bytes(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.7.5/plugins/bcm57xx/fu-bcm57xx-stage1-image.h000066400000000000000000000006251420024370600221500ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-bcm57xx-stage2-image.c000066400000000000000000000042401420024370600221410ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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, guint64 addr_start, guint64 addr_end, 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_common_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 GBytes * 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_byte_array_free_to_bytes(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.7.5/plugins/bcm57xx/fu-bcm57xx-stage2-image.h000066400000000000000000000006251420024370600221510ustar00rootroot00000000000000/* * 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.7.5/plugins/bcm57xx/fu-plugin-bcm57xx.c000066400000000000000000000017361420024370600212010ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-bcm57xx-device.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_plugin_bcm57xx_init(FuPlugin *plugin) { 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"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_bcm57xx_init; } fwupd-1.7.5/plugins/bcm57xx/fu-self-test.c000066400000000000000000000112371420024370600203130ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_contents_bytes(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ret = fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, &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_common_set_contents_bytes(fn_out, blob_out, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_common_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) { 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.7.5/plugins/bcm57xx/meson.build000066400000000000000000000033231420024370600177700ustar00rootroot00000000000000if get_option('plugin_bcm57xx') if not get_option('gudev') error('gudev is required for plugin_bcm57xx') endif cargs = ['-DG_LOG_DOMAIN="FuPluginBcm57xx"'] install_data(['bcm57xx.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_bcm57xx', fu_hash, sources : [ 'fu-plugin-bcm57xx.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, valgrind, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-bcm57xx-common.c', 'fu-bcm57xx-dict-image.c', 'fu-bcm57xx-firmware.c', 'fu-bcm57xx-stage1-image.c', 'fu-bcm57xx-stage2-image.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('bcm57xx-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/bcm57xx/tests/000077500000000000000000000000001420024370600167675ustar00rootroot00000000000000fwupd-1.7.5/plugins/bcm57xx/tests/bcm57xx.bin000066400000000000000000010000001420024370600207450ustar00rootroot00000000000000fU8ŏ.o{fUfwupd-1.7.5/plugins/bcm57xx/tests/bcm57xx.builder.xml000066400000000000000000000007541420024370600224410ustar00rootroot00000000000000 1.2.3 0x123456 stage1 0x01 aGVsbG8gd29ybGQ= stage2 ape 0x7 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/bios/000077500000000000000000000000001420024370600152645ustar00rootroot00000000000000fwupd-1.7.5/plugins/bios/fu-plugin-bios.c000066400000000000000000000032421420024370600202710ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include static gboolean fu_plugin_bios_startup(FuPlugin *plugin, 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_plugin_bios_coldplug(FuPlugin *plugin, 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_common_get_path(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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->startup = fu_plugin_bios_startup; vfuncs->coldplug = fu_plugin_bios_coldplug; } fwupd-1.7.5/plugins/bios/meson.build000066400000000000000000000006411420024370600174270ustar00rootroot00000000000000if get_option('plugin_uefi_capsule') cargs = ['-DG_LOG_DOMAIN="FuPluginBios"'] shared_module('fu_plugin_bios', fu_hash, sources : [ 'fu-plugin-bios.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/ccgx/000077500000000000000000000000001420024370600152545ustar00rootroot00000000000000fwupd-1.7.5/plugins/ccgx/README.md000066400000000000000000000061251420024370600165370ustar00rootroot00000000000000# Cypress ## Introduction This plugin can flash firmware on Cypress CCGx USB-C controller family of devices used in docks. ## Supported Protocols This plugin supports the following protocol IDs: * com.cypress.ccgx * com.cypress.ccgx.dmc ## 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. ### DMC(Dock Management Controller) Composite Firmware In composite firmware topology, a single firmware image contains metadata and firmware images of multiple devices including DMC itself in a dock system. ## Firmware Format There are two kinds of 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. ### DMC composite firmware format 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` 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 a DMC or 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 DMC/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` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. fwupd-1.7.5/plugins/ccgx/ccgx-ids.quirk000066400000000000000000000132651420024370600200410ustar00rootroot00000000000000# 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.7.5/plugins/ccgx/ccgx.quirk000066400000000000000000000042121420024370600172540ustar00rootroot00000000000000# 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) # HP USB-C Dock G5 [USB\VID_03F0&PID_046B] Plugin = ccgx GType = FuCcgxDmcDevice Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_0363 Name = HP USB-C Dock G5 CcgxImageKind = dmc-composite InstallDuration = 233 RemoveDelay = 203000 # HP USB-C/A Universal Dock G2 [USB\VID_03F0&PID_0A6B] Plugin = ccgx GType = FuCcgxDmcDevice Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_096B Name = HP USB-C/A Universal Dock G2 CcgxImageKind = dmc-composite InstallDuration = 180 RemoveDelay = 162000 # HP Thunderbolt Dock G4 [USB\VID_03F0&PID_0488] Plugin = ccgx GType = FuCcgxDmcDevice Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_0363 Name = HP Thunderbolt Dock G4 CcgxImageKind = dmc-composite InstallDuration = 800 RemoveDelay = 203000 fwupd-1.7.5/plugins/ccgx/fu-ccgx-common.c000066400000000000000000000033751420024370600202520ustar00rootroot00000000000000/* * 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); } const gchar * fu_ccgx_fw_mode_to_string(FWMode val) { if (val == FW_MODE_BOOT) return "BOOT"; if (val == FW_MODE_FW1) return "FW1"; if (val == FW_MODE_FW2) return "FW2"; return NULL; } const gchar * fu_ccgx_fw_image_type_to_string(FWImageType val) { if (val == FW_IMAGE_TYPE_SINGLE) return "single"; if (val == FW_IMAGE_TYPE_DUAL_SYMMETRIC) return "dual-symmetric"; if (val == FW_IMAGE_TYPE_DUAL_ASYMMETRIC) return "dual-asymmetric"; if (val == FW_IMAGE_TYPE_DUAL_ASYMMETRIC_VARIABLE) return "dual-asymmetric-variable"; if (val == FW_IMAGE_TYPE_DMC_COMPOSITE) return "dmc-composite"; return NULL; } FWImageType fu_ccgx_fw_image_type_from_string(const gchar *val) { if (g_strcmp0(val, "single") == 0) return FW_IMAGE_TYPE_SINGLE; if (g_strcmp0(val, "dual-symmetric") == 0) return FW_IMAGE_TYPE_DUAL_SYMMETRIC; if (g_strcmp0(val, "dual-asymmetric") == 0) return FW_IMAGE_TYPE_DUAL_ASYMMETRIC; if (g_strcmp0(val, "dual-asymmetric-variable") == 0) return FW_IMAGE_TYPE_DUAL_ASYMMETRIC_VARIABLE; if (g_strcmp0(val, "dmc-composite") == 0) return FW_IMAGE_TYPE_DMC_COMPOSITE; return FW_IMAGE_TYPE_UNKNOWN; } FWMode fu_ccgx_fw_mode_get_alternate(FWMode fw_mode) { if (fw_mode == FW_MODE_FW1) return FW_MODE_FW2; if (fw_mode == FW_MODE_FW2) return FW_MODE_FW1; return FW_MODE_BOOT; } fwupd-1.7.5/plugins/ccgx/fu-ccgx-common.h000066400000000000000000000027011420024370600202470ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* metadata valid signature "CY" */ #define CCGX_METADATA_VALID_SIG 0x4359 typedef struct __attribute__((packed)) { guint8 fw_checksum; /* firmware checksum */ guint32 fw_entry; /* firmware entry address */ guint16 last_boot_row; /* last flash row of bootloader or previous firmware */ guint8 reserved1[2]; /* reserved */ guint32 fw_size; /* firmware size */ guint8 reserved2[9]; /* reserved */ guint16 metadata_valid; /* metadata valid "CY" */ guint8 reserved3[4]; /* reserved */ guint32 boot_seq; /* boot sequence number */ } CCGxMetaData; /* firmware mode in device */ typedef enum { FW_MODE_BOOT = 0, FW_MODE_FW1, FW_MODE_FW2, FW_MODE_LAST } FWMode; /* firmware image type */ typedef enum { FW_IMAGE_TYPE_UNKNOWN = 0, FW_IMAGE_TYPE_SINGLE, FW_IMAGE_TYPE_DUAL_SYMMETRIC, /* A/B runtime */ FW_IMAGE_TYPE_DUAL_ASYMMETRIC, /* A=bootloader (fixed), B=runtime */ FW_IMAGE_TYPE_DUAL_ASYMMETRIC_VARIABLE, /* A=bootloader (variable), B=runtime */ FW_IMAGE_TYPE_DMC_COMPOSITE, /* composite firmware image for dmc */ } FWImageType; gchar * fu_ccgx_version_to_string(guint32 val); const gchar * fu_ccgx_fw_mode_to_string(FWMode val); FWMode fu_ccgx_fw_mode_get_alternate(FWMode val); const gchar * fu_ccgx_fw_image_type_to_string(FWImageType val); FWImageType fu_ccgx_fw_image_type_from_string(const gchar *val); fwupd-1.7.5/plugins/ccgx/fu-ccgx-dmc-common.c000066400000000000000000000007121420024370600210030ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-dmc-common.h" const gchar * fu_ccgx_dmc_update_model_type_to_string(DmcUpdateModel val) { if (val == DMC_UPDATE_MODEL_UNKNOWN) return "Unknown"; if (val == DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER) return "Download Trigger"; if (val == DMC_UPDATE_MODEL_PENDING_RESET) return "Pending Reset"; return NULL; } fwupd-1.7.5/plugins/ccgx/fu-ccgx-dmc-common.h000066400000000000000000000222301420024370600210070ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* maximum number of programmable devices expected to be connected in dock. * this is design limitation. This shall not be edited, unless stated by C Y*/ #define DMC_DOCK_MAX_DEV_COUNT 8 /* size of FW version structure in bytes */ #define DMC_DOCK_FW_VERSION_SIZE 8 /* indicates length of string in dock identity*/ #define DMC_IDENTITY_STRING_LEN 32 /* interrupt end point for DMC Dock */ #define DMC_INTERRUPT_PIPE_ID 0x82 /* USB bulk end point for DMC Dock */ #define DMC_BULK_PIPE_ID 1 /* indicates length of interrupt structure's data array filed */ #define DMC_INTERRUPT_DATA_LEN 8 /* status of the dmc will have different length. so first few bytes of status * is read, which will contain actual length of status. this value indicates * how much byte should read at first stage */ #define DMC_GET_STATUS_MIN_LEN 32 #define DMC_HASH_SIZE 32 /* time out to be set in control in/out pipe policy in ms */ #define DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT 5000 /* time out to be set in bulk out pipe policy in ms */ #define DMC_BULK_OUT_PIPE_TIMEOUT 2000 /* time out to be set in bulk out pipe policy in ms */ #define DMC_GET_REQUEST_TIMEOUT 20000 #define DMC_FWCT_SIGN 0x54435746 /* 'F' 'W' 'C' 'T' */ /* first we have to read few bytes to know the actual length of FWCT * this constant defines, number bytest to be read for getting length */ #define DMC_FWCT_MIN_LENGTH 6 #define DMC_FWCT_LENGTH_OFFSET 4 #define DMC_FWCT_MAX_SIZE 2048 #define DMC_CUSTOM_META_LENGTH_FIELD_SIZE 2 #define DMC_CUSTOM_META_LENGTH_OFFSET 0 #define DMC_CUSTOM_META_MAX_SIZE 256 /* this data type enumerates the image types */ typedef enum { DMC_IMG_TYPE_INVALID = 0, DMC_IMG_TYPE_IMAGE_0, DMC_IMG_TYPE_IMAGE_1 } DmcImgType; /* this data type enumerates the image status */ typedef enum { DMC_IMG_STATUS_UNKNOWN = 0, DMC_IMG_STATUS_VALID, DMC_IMG_STATUS_INVALID, DMC_IMG_STATUS_RECOVERY, DMC_IMG_STATUS_RECOVERED_FROM_SECONDARY, DMC_IMG_STATUS_NOT_SUPPORTED = 0x0F } DmcImgStatus; /* this data type enumerates the image modes or flash architecture */ typedef enum { /* indicates that the device has a single image */ DMC_IMG_MODE_SINGLE_IMG = 0, /* the device supports symmetric boot. In symmetric mode the bootloader * boots the image with higher version, when they are valid */ DMC_IMG_MODE_DUAL_IMG_SYM, /* 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 */ DMC_IMG_MODE_DUAL_IMG_ASYM, DMC_IMG_MODE_SINGLE_IMG_WITH_RAM_IMG, } DmcImgMode; /* this data type enumerates the dock status */ typedef enum { /* status code indicating DOCK IDLE state. SUCCESS: no malfunctioning * no outstanding request or event */ DMC_DEVICE_STATUS_IDLE = 0, /* status code indicating dock FW update in progress */ DMC_DEVICE_STATUS_UPDATE_IN_PROGRESS, /* status code indicating dock FW update is partially complete */ DMC_DEVICE_STATUS_UPDATE_PARTIAL, /* status code indicating dock FW update SUCCESS - all m_images of all * devices are valid */ DMC_DEVICE_STATUS_UPDATE_COMPLETE_FULL, /* status code indicating dock FW update SUCCESS - not all m_images of all * devices are valid */ DMC_DEVICE_STATUS_UPDATE_COMPLETE_PARTIAL, /* fw download status */ DMC_DEVICE_STATUS_UPDATE_PHASE_1_COMPLETE, DMC_DEVICE_STATUS_FW_DOWNLOADED_UPDATE_PEND, DMC_DEVICE_STATUS_FW_DOWNLOADED_PARTIAL_UPDATE_PEND, DMC_DEVICE_STATUS_PHASE2_UPDATE_IN_PROGRESS = 0x81, DMC_DEVICE_STATUS_PHASE2_UPDATE_PARTIAL, DMC_DEVICE_STATUS_PHASE2_UPDATE_FACTORY_BACKUP, DMC_DEVICE_STATUS_PHASE2_UPDATE_COMPLETE_PARTIAL, DMC_DEVICE_STATUS_PHASE2_UPDATE_COMPLETE_FULL, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_INVALID_FWCT, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_INVALID_DOCK_IDENTITY, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_INVALID_COMPOSITE_VER, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_AUTHENTICATION_FAILED, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_INVALID_ALGORITHM, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_SPI_READ_FAILED, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_NO_VALID_KEY, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_NO_VALID_SPI_PACKAGE, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_RAM_INIT_FAILED, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_FACTORY_BACKUP_FAILED, DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_NO_VALID_FACTORY_PACKAGE, /* status code indicating dock FW update FAILED */ DMC_DEVICE_STATUS_UPDATE_FAIL = 0xFF } DmcDeviceStatus; /* this data type enumerates the request codes for vendor interface */ typedef enum { DMC_RQT_CODE_UPGRADE_START = 0xD0, DMC_RQT_CODE_RESERV_0, DMC_RQT_CODE_FWCT_WRITE, DMC_RQT_CODE_IMG_WRITE, DMC_RQT_CODE_RESERV_1, DMC_RQT_CODE_RESERV_2, DMC_RQT_CODE_DOCK_STATUS, DMC_RQT_CODE_DOCK_IDENTITY, /* command to reset dmc state machine of DMC */ DMC_RQT_CODE_RESET_STATE_MACHINE, /* command to reset for online enhanced mode (no reset during update) */ DMC_RQT_CODE_SOFT_RESET = 0xDC, /* Update Trigger command for offline mode */ DMC_RQT_CODE_TRIGGER = 0xDA } DmcRqtCode; /* this data type enumerates the opcode of triggering the download, in case of * 2 stage update */ typedef enum { DMC_TRIGGER_CODE_DONT_UPDATE, DMC_TRIGGER_CODE_UPDATE_NOW, DMC_TRIGGER_CODE_UPDATE_ON_DISCONNECT, DMC_TRIGGER_CODE_UNKNOWN } DmcTriggerCode; /* this data type enumerates the opcode of interrupt read */ typedef enum { DMC_INT_OPCODE_FW_UPGRADE_RQT = 1, DMC_INT_OPCODE_FW_UPGRADE_STATUS = 0x80, DMC_INT_OPCODE_IMG_WRITE_STATUS, DMC_INT_OPCODE_REENUM, DMC_INT_OPCODE_FWCT_ANALYSIS_STATUS } DmcIntOpcode; /* this data type enumerates the fwct analysis status */ typedef enum { DMC_FWCT_ANALYSIS_STATUS_INVALID_FWCT = 0, DMC_FWCT_ANALYSIS_STATUS_INVALID_DOCK_IDENTITY, DMC_FWCT_ANALYSIS_STATUS_INVALID_COMPOSITE_VERSION, DMC_FWCT_ANALYSIS_STATUS_AUTHENTICATION_FAILED, DMC_FWCT_ANALYSIS_STATUS_INVALID_ALGORITHM } DmcFwctAnalysisStatus; typedef enum { DMC_UPDATE_MODEL_UNKNOWN = 0, /* need to trigger after updating FW */ DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER, /* need to set soft reset after updating FW */ DMC_UPDATE_MODEL_PENDING_RESET, } DmcUpdateModel; /* this structure defines the fields of data returned when reading dock_identity * for new firmware */ typedef struct __attribute__((packed)) { /* this field indicates both validity and structure version * 0 : invalid * 1 : old structure * 2 : new structure */ guint8 structure_version; guint8 cdtt_version; guint16 vid; guint16 pid; guint16 device_id; gchar vendor_string[DMC_IDENTITY_STRING_LEN]; gchar product_string[DMC_IDENTITY_STRING_LEN]; guint8 custom_meta_data_flag; /* 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 */ guint8 model; } DmcDockIdentity; /* this structure defines the fields of status of a specific device */ typedef struct __attribute__((packed)) { /* device ID of the device */ guint8 device_type; /* component ID of the device */ guint8 component_id; /* image mode of the device - single image/ dual symmetric/ dual * asymmetric image > */ guint8 image_mode; /* current running image */ guint8 current_image; /* image status * b7:b4 => Image 2 status * b3:b0 => Image 1 status * 0 = Unknown * 1 = Valid * 2 = Invalid * 3-0xF = Reserved */ guint8 img_status; /* padding */ guint8 reserved_0[3]; /* complete fw version 8 bytes for bootload, image1 and image2. 8 byte * for fw version and application version */ guint8 fw_version[24]; } DmcDevxStatus; /* this structure defines the fields of data returned when reading dock_status */ typedef struct __attribute__((packed)) { /* overall status of dock. see DmcDeviceStatus */ guint8 device_status; /* eevice count */ guint8 device_count; /* length of status bytes including dock_status, devx_status for each device */ guint16 status_length; /* dock composite version m_fwct_info */ guint32 composite_version; /* fw status of device of interest */ DmcDevxStatus devx_status[DMC_DOCK_MAX_DEV_COUNT]; } DmcDockStatus; /* This structure defines the fields of data returned when reading an interrupt * from DMC */ typedef struct __attribute__((packed)) { guint8 opcode; guint8 length; guint8 data[DMC_INTERRUPT_DATA_LEN]; } DmcIntRqt; /* this structure defines header structure of FWCT */ typedef struct __attribute__((packed)) { guint32 signature; guint16 size; guint8 checksum; guint8 version; guint8 custom_meta_type; guint8 cdtt_version; guint16 vid; guint16 pid; guint16 device_id; guint8 reserv0[16]; guint32 composite_version; guint8 image_count; guint8 reserv1[3]; } FwctInfo; typedef struct __attribute__((packed)) { guint8 device_type; guint8 img_type; guint8 comp_id; guint8 row_size; guint8 reserv0[4]; guint32 fw_version; guint32 app_version; guint32 img_offset; guint32 img_size; guint8 img_digest[32]; guint8 num_img_segments; guint8 reserv1[3]; } FwctImageInfo; typedef struct __attribute__((packed)) { guint8 img_id; guint8 type; guint16 start_row; guint16 num_rows; /* size */ guint8 reserv0[2]; } FwctSegmentationInfo; const gchar * fu_ccgx_dmc_update_model_type_to_string(DmcUpdateModel val); fwupd-1.7.5/plugins/ccgx/fu-ccgx-dmc-device.c000066400000000000000000000540451420024370600207620ustar00rootroot00000000000000/* * 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-dmc-common.h" #include "fu-ccgx-dmc-device.h" #include "fu-ccgx-dmc-firmware.h" #define DMC_FW_WRITE_STATUS_RETRY_COUNT 3 #define DMC_FW_WRITE_STATUS_RETRY_DELAY_MS 30 struct _FuCcgxDmcDevice { FuUsbDevice parent_instance; FWImageType fw_image_type; DmcDockIdentity dock_id; guint8 ep_intr_in; guint8 ep_bulk_out; DmcUpdateModel update_model; }; /** * 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_get_dock_id(FuCcgxDmcDevice *self, DmcDockIdentity *dock_id, GError **error) { g_return_val_if_fail(dock_id != NULL, 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, DMC_RQT_CODE_DOCK_IDENTITY, /* request */ 0, /* value */ 0, /* index */ (guint8 *)dock_id, /* data */ sizeof(DmcDockIdentity), /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_id error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_get_dock_status(FuCcgxDmcDevice *self, DmcDockStatus *dock_status, GError **error) { g_return_val_if_fail(dock_status != NULL, FALSE); /* read minimum status length */ 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, DMC_RQT_CODE_DOCK_STATUS, /* request */ 0, /* value */ 0, /* index */ (guint8 *)dock_status, /* data */ DMC_GET_STATUS_MIN_LEN, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_status min size error: "); return FALSE; } if (dock_status->status_length <= sizeof(DmcDockStatus)) { /* read full status length */ 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, DMC_RQT_CODE_DOCK_STATUS, /* request */ 0, /* value */ 0, /* index */ (guint8 *)dock_status, /* data */ sizeof(DmcDockStatus), /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_status actual size error: "); return FALSE; } } 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, 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_sort_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, 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, const guint8 *custom_meta_data, guint16 custom_meta_bufsz, GError **error) { guint16 value = 0; if (custom_meta_bufsz > 0) value = 1; if (custom_meta_bufsz > 0 && custom_meta_data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid metadata, buffer is NULL but size = %d", custom_meta_bufsz); 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, DMC_RQT_CODE_UPGRADE_START, /* request */ value, /* value */ 1, /* index, forced update for Adicora only, other dock will ignore it */ (guint8 *)custom_meta_data, /* data */ custom_meta_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, DmcTriggerCode 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, 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 *fwct_buf, guint16 fwct_sz, GError **error) { g_return_val_if_fail(fwct_buf != NULL, 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, DMC_RQT_CODE_FWCT_WRITE, /* request */ 0, /* value */ 0, /* index */ (guint8 *)fwct_buf, /* data */ fwct_sz, /* 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, DmcIntRqt *intr_rqt, GError **error) { 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, (guint8 *)intr_rqt, sizeof(DmcIntRqt), NULL, DMC_GET_REQUEST_TIMEOUT, NULL, error)) { g_prefix_error(error, "read intr rqt error: "); return FALSE; } 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, 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_common_string_append_kv(str, idt, "UpdateModel", fu_ccgx_dmc_update_model_type_to_string(self->update_model)); fu_common_string_append_kv(str, idt, "FwImageType", fu_ccgx_fw_image_type_to_string(self->fw_image_type)); fu_common_string_append_kx(str, idt, "EpBulkOut", self->ep_bulk_out); fu_common_string_append_kx(str, idt, "EpIntrIn", self->ep_intr_in); } static gboolean fu_ccgx_dmc_get_image_write_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); DmcIntRqt dmc_int_req = {0}; /* get interrupt request */ if (!fu_ccgx_dmc_device_read_intr_req(self, &dmc_int_req, error)) { g_prefix_error(error, "read intr req error in image write status: "); return FALSE; } /* check opcode for fw write */ if (dmc_int_req.opcode != DMC_INT_OPCODE_IMG_WRITE_STATUS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc intr req opcode in image write status = %d", dmc_int_req.opcode); return FALSE; } /* retry if data[0] is 1 otherwise error */ if (dmc_int_req.data[0] != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc intr req data in image write status = %d", dmc_int_req.data[0]); g_usleep(DMC_FW_WRITE_STATUS_RETRY_DELAY_MS * 1000); 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99); /* 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; DmcIntRqt dmc_int_rqt = {0}; GBytes *custom_meta_blob; GBytes *fwct_blob; GPtrArray *image_records; const guint8 *custom_meta_data = NULL; const guint8 *fwct_buf = NULL; gsize custom_meta_bufsz = 0; gsize fwct_sz = 0; gsize fw_data_size = 0; gsize fw_data_written = 0; guint8 img_index = 0; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98); /* 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; } /* get custom meta record */ custom_meta_blob = fu_ccgx_dmc_firmware_get_custom_meta_record(FU_CCGX_DMC_FIRMWARE(firmware)); if (custom_meta_blob != NULL) custom_meta_data = g_bytes_get_data(custom_meta_blob, &custom_meta_bufsz); /* 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 */ if (!fu_ccgx_dmc_device_send_start_upgrade(self, custom_meta_data, custom_meta_bufsz, 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; /* fw upgrade request */ if (dmc_int_rqt.opcode != DMC_INT_OPCODE_FW_UPGRADE_RQT) break; img_index = dmc_int_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 */ 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 (dmc_int_rqt.opcode != DMC_INT_OPCODE_FW_UPGRADE_STATUS) { if (dmc_int_rqt.opcode == DMC_INT_OPCODE_FWCT_ANALYSIS_STATUS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "fwct analysis failed with status = %d", dmc_int_rqt.data[0]); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc intr req opcode = %d with status = %d", dmc_int_rqt.opcode, dmc_int_rqt.data[0]); return FALSE; } if (dmc_int_rqt.data[0] == DMC_DEVICE_STATUS_UPDATE_PHASE_1_COMPLETE) { self->update_model = DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER; } else if (dmc_int_rqt.data[0] == DMC_DEVICE_STATUS_FW_DOWNLOADED_UPDATE_PEND) { self->update_model = DMC_UPDATE_MODEL_PENDING_RESET; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid status code = %u", dmc_int_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->dock_id.custom_meta_data_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); if (fu_device_get_update_state(self) != FWUPD_UPDATE_STATE_SUCCESS) return TRUE; if (self->update_model == DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER) { DmcTriggerCode trigger_code = DMC_TRIGGER_CODE_UPDATE_NOW; if (manual_replug) trigger_code = DMC_TRIGGER_CODE_UPDATE_ON_DISCONNECT; if (!fu_ccgx_dmc_device_send_download_trigger(self, trigger_code, error)) { g_prefix_error(error, "download trigger error: "); return FALSE; } } else if (self->update_model == DMC_UPDATE_MODEL_PENDING_RESET) { if (!fu_ccgx_dmc_device_send_sort_reset(self, manual_replug, error)) { g_prefix_error(error, "soft reset error: "); return FALSE; } } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid update model = %u", self->update_model); return FALSE; } if (manual_replug) return TRUE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_ccgx_dmc_device_setup(FuDevice *device, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); DmcDockStatus dock_status = {0}; DmcDockIdentity dock_id = {0}; guint32 version_raw = 0; g_autofree gchar *version = NULL; /* 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_get_dock_id(self, &dock_id, error)) return FALSE; /* store dock identity */ if (!fu_memcpy_safe((guint8 *)&self->dock_id, sizeof(DmcDockIdentity), 0x0, /* dst */ (guint8 *)&dock_id, sizeof(DmcDockIdentity), 0, /* src */ sizeof(DmcDockIdentity), error)) return FALSE; /* get dock status */ if (!fu_ccgx_dmc_device_get_dock_status(self, &dock_status, error)) return FALSE; /* set composite version */ version_raw = dock_status.composite_version; version = fu_common_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(FU_DEVICE(self), version); fu_device_set_version_raw(FU_DEVICE(self), version_raw); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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, "CcgxImageKind") == 0) { self->fw_image_type = fu_ccgx_fw_image_type_from_string(value); if (self->fw_image_type != FW_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 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_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; fu_device_add_protocol(FU_DEVICE(self), "com.cypress.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_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_hid_device_set_progress; } fwupd-1.7.5/plugins/ccgx/fu-ccgx-dmc-device.h000066400000000000000000000005511420024370600207600ustar00rootroot00000000000000/* * 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.7.5/plugins/ccgx/fu-ccgx-dmc-firmware.c000066400000000000000000000370531420024370600213370ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-ccgx-common.h" #include "fu-ccgx-dmc-common.h" #include "fu-ccgx-dmc-firmware.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) 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; /* read segment info */ seg_rcd = g_new0(FuCcgxDmcFirmwareSegmentRecord, 1); if (!fu_common_read_uint16_safe( buf, bufsz, *seg_off + G_STRUCT_OFFSET(FwctSegmentationInfo, start_row), &seg_rcd->start_row, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, *seg_off + G_STRUCT_OFFSET(FwctSegmentationInfo, num_rows), &seg_rcd->num_rows, G_LITTLE_ENDIAN, error)) return FALSE; /* 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 += sizeof(FwctSegmentationInfo); } /* 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 = sizeof(FwctInfo); gsize seg_off = sizeof(FwctInfo) + image_count * sizeof(FwctImageInfo); /* set initial segment info offset */ for (guint32 i = 0; i < image_count; i++) { g_autoptr(FuCcgxDmcFirmwareRecord) img_rcd = NULL; /* read image info */ img_rcd = g_new0(FuCcgxDmcFirmwareRecord, 1); if (!fu_common_read_uint8_safe(buf, bufsz, img_off + G_STRUCT_OFFSET(FwctImageInfo, row_size), &img_rcd->row_size, error)) return FALSE; 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; } if (!fu_common_read_uint32_safe(buf, bufsz, img_off + G_STRUCT_OFFSET(FwctImageInfo, img_offset), &img_rcd->img_offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, img_off + G_STRUCT_OFFSET(FwctImageInfo, num_img_segments), &img_rcd->num_img_segments, error)) return FALSE; 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; } if (!fu_memcpy_safe((guint8 *)&img_rcd->img_digest, sizeof(img_rcd->img_digest), 0x0, /* dst */ buf, bufsz, img_off + G_STRUCT_OFFSET(FwctImageInfo, img_digest), /* src */ sizeof(img_rcd->img_digest), 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 += sizeof(FwctImageInfo); } return TRUE; } static gboolean fu_ccgx_dmc_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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; guint32 hdr_signature = 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); /* check for 'F' 'W' 'C' 'T' in signature */ if (!fu_common_read_uint32_safe(buf, bufsz, 0x0, &hdr_signature, G_LITTLE_ENDIAN, error)) return FALSE; if (hdr_signature != DMC_FWCT_SIGN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc signature, expected 0x%04X got 0x%04X", (guint32)DMC_FWCT_SIGN, (guint32)hdr_signature); return FALSE; } /* check fwct size */ if (!fu_common_read_uint16_safe(buf, bufsz, G_STRUCT_OFFSET(FwctInfo, size), &hdr_size, G_LITTLE_ENDIAN, error)) return FALSE; 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 */ if (!fu_common_read_uint32_safe(buf, bufsz, G_STRUCT_OFFSET(FwctInfo, composite_version), &hdr_composite_version, G_LITTLE_ENDIAN, error)) return FALSE; if (hdr_composite_version != 0) { g_autofree gchar *ver = NULL; ver = fu_common_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_common_bytes_new_offset(fw, 0x0, hdr_size, error); if (self->fwct_blob == NULL) return FALSE; /* create custom meta binary */ if (!fu_common_read_uint16_safe(buf, bufsz, 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_common_bytes_new_offset(fw, 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 */ if (!fu_common_read_uint8_safe(buf, bufsz, G_STRUCT_OFFSET(FwctInfo, image_count), &hdr_image_count, error)) return FALSE; 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 GBytes * fu_ccgx_dmc_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* add header */ fu_byte_array_append_uint32(buf, DMC_FWCT_SIGN, G_LITTLE_ENDIAN); fu_byte_array_append_uint16( buf, sizeof(FwctInfo) + (images->len * (sizeof(FwctImageInfo) + sizeof(FwctSegmentationInfo))), /* size */ G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, 0x0); /* checksum, unused */ fu_byte_array_append_uint8(buf, 0x2); /* version */ fu_byte_array_append_uint8(buf, 0x3); /* custom_meta_type */ fu_byte_array_append_uint8(buf, 0x1); /* cdtt_version */ fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); /* vid, unused */ fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); /* pid, unused */ fu_byte_array_append_uint16(buf, 0x1, G_LITTLE_ENDIAN); /* device_id */ for (guint j = 0; j < 16; j++) fu_byte_array_append_uint8(buf, 0x0); /* reserv0 */ fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, images->len); for (guint j = 0; j < 3; j++) fu_byte_array_append_uint8(buf, 0x0); /* reserv1 */ /* add image headers */ for (guint i = 0; i < images->len; i++) { fu_byte_array_append_uint8(buf, 0x2); /* device_type, unknown */ fu_byte_array_append_uint8(buf, 0x1); /* img_type, unknown */ fu_byte_array_append_uint8(buf, 0x0); /* comp_id, unknown */ fu_byte_array_append_uint8(buf, 0x1); /* row_size, multiplier for num_rows */ for (guint j = 0; j < 4; j++) fu_byte_array_append_uint8(buf, 0x0); /* reserv0 */ fu_byte_array_append_uint32(buf, 0x330006d2, G_LITTLE_ENDIAN); /* fw_version, hardcoded */ fu_byte_array_append_uint32(buf, 0x14136161, G_LITTLE_ENDIAN); /* app_version, hardcoded */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* start of element data */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* img_size */ for (guint j = 0; j < 32; j++) fu_byte_array_append_uint8(buf, 0x0); /* img_digest */ fu_byte_array_append_uint8(buf, 0x1); /* num_img_segments */ for (guint j = 0; j < 3; j++) fu_byte_array_append_uint8(buf, 0x0); /* reserv1 */ } /* add segments */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GPtrArray) 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, 0x0, 64); fu_byte_array_append_uint8(buf, 0x0); /* img_id */ fu_byte_array_append_uint8(buf, 0x0); /* type */ fu_byte_array_append_uint16(buf, 0x0, G_LITTLE_ENDIAN); /* start_row, unknown */ fu_byte_array_append_uint16(buf, MAX(chunks->len, 1), G_LITTLE_ENDIAN); /* num_rows */ for (guint j = 0; j < 2; j++) fu_byte_array_append_uint8(buf, 0x0); /* reserv0 */ } /* 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 = sizeof(FwctInfo) + (i * sizeof(FwctImageInfo)); 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(GPtrArray) 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, 0x0, 64); img_padded = fu_common_bytes_pad(img_bytes, MAX(chunks->len, 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 + G_STRUCT_OFFSET(FwctImageInfo, img_digest), csumbuf, sizeof(csumbuf), 0x0, /* src */ sizeof(csumbuf), error)) return NULL; } return g_byte_array_free_to_bytes(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->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.7.5/plugins/ccgx/fu-ccgx-dmc-firmware.h000066400000000000000000000020011420024370600213250ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-ccgx-dmc-common.h" #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.7.5/plugins/ccgx/fu-ccgx-firmware.c000066400000000000000000000340431420024370600205720ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-ccgx-common.h" #include "fu-ccgx-firmware.h" struct _FuCcgxFirmware { FuFirmwareClass parent_instance; GPtrArray *records; guint16 app_type; guint16 silicon_id; FWMode 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; } FWMode 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_byte_array_free_to_bytes(g_steal_pointer(&data)); /* 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; CCGxMetaData metadata; const guint8 *buf; gsize bufsz = 0; gsize md_offset = 0; guint32 fw_size = 0; guint32 rcd_version_idx = 0; guint32 version = 0; guint8 checksum_calc = 0; /* 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 ofsset */ rcd = g_ptr_array_index(self->records, self->records->len - 1); buf = g_bytes_get_data(rcd->data, &bufsz); 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; } if (!fu_memcpy_safe((guint8 *)&metadata, sizeof(metadata), 0x0, /* dst */ buf, bufsz, md_offset, sizeof(metadata), error)) /* src */ return FALSE; /* sanity check */ if (metadata.metadata_valid != CCGX_METADATA_VALID_SIG) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid metadata 0x@%x, expected 0x%04x, got 0x%04x", (guint)md_offset, (guint)CCGX_METADATA_VALID_SIG, (guint)metadata.metadata_valid); return FALSE; } for (guint i = 0; i < self->records->len - 1; i++) { rcd = g_ptr_array_index(self->records, i); checksum_calc += fu_common_sum8_bytes(rcd->data); fw_size += g_bytes_get_size(rcd->data); } if (fw_size != metadata.fw_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware size invalid, got %02x, expected %02x", fw_size, metadata.fw_size); return FALSE; } checksum_calc = 1 + ~checksum_calc; if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (metadata.fw_checksum != checksum_calc) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", checksum_calc, metadata.fw_checksum); 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; 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_common_read_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 FWMode */ 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 = FW_MODE_FW1; if ((rcd->row_number & 0xFF) == 0xFE) /* penultimate row */ self->fw_mode = 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_common_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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); FuCcgxFirmwareTokenHelper helper = {.self = self, .flags = flags}; /* tokenize */ if (!fu_common_strnsplit_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; } /* add something, although we'll use the records for the update */ fu_firmware_set_bytes(firmware, fw); 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 GBytes * fu_ccgx_firmware_write(FuFirmware *firmware, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); CCGxMetaData metadata = {0x0}; gsize fwbufsz = 0; guint8 checksum_img = 0xff; const guint8 *fwbuf; g_autoptr(GByteArray) mdbuf = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) 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, 0x0, 0x100); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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]; metadata.fw_checksum = ~checksum_img; metadata.fw_entry = 0x0; /* unknown */ metadata.last_boot_row = 0x13; metadata.fw_size = fwbufsz; metadata.metadata_valid = CCGX_METADATA_VALID_SIG; metadata.boot_seq = 0x0; /* unknown */ /* copy into place */ fu_byte_array_set_size(mdbuf, 0x80); if (!fu_memcpy_safe(mdbuf->data, mdbuf->len, 0x40, /* dst */ (const guint8 *)&metadata, sizeof(metadata), 0x0, /* src */ sizeof(metadata), error)) return NULL; fu_ccgx_firmware_write_record(str, 0x0, 0xFE, /* FW2: penultimate row */ mdbuf->data, mdbuf->len); return g_string_free_to_bytes(g_steal_pointer(&str)); } 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.7.5/plugins/ccgx/fu-ccgx-firmware.h000066400000000000000000000013751420024370600206010ustar00rootroot00000000000000/* * 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); FWMode fu_ccgx_firmware_get_fw_mode(FuCcgxFirmware *self); fwupd-1.7.5/plugins/ccgx/fu-ccgx-hid-device.c000066400000000000000000000063721420024370600207630ustar00rootroot00000000000000/* * 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_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WILL_DISAPPEAR); 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.7.5/plugins/ccgx/fu-ccgx-hid-device.h000066400000000000000000000005511420024370600207610ustar00rootroot00000000000000/* * 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.7.5/plugins/ccgx/fu-ccgx-hpi-common.c000066400000000000000000000070401420024370600210210ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-hpi-common.h" const gchar * fu_ccgx_pd_resp_to_string(CyPDResp val) { if (val == CY_PD_RESP_NO_RESPONSE) return "resp-no-response"; if (val == CY_PD_RESP_SUCCESS) return "resp-success"; if (val == CY_PD_RESP_FLASH_DATA_AVAILABLE) return "resp-flash-data-available"; if (val == CY_PD_RESP_INVALID_COMMAND) return "resp-invalid-command"; if (val == CY_PD_RESP_COLLISION_DETECTED) return "resp-collision-detected"; if (val == CY_PD_RESP_FLASH_UPDATE_FAILED) return "resp-flash-update-failed"; if (val == CY_PD_RESP_INVALID_FW) return "resp-invalid-fw"; if (val == CY_PD_RESP_INVALID_ARGUMENTS) return "resp-invalid-arguments"; if (val == CY_PD_RESP_NOT_SUPPORTED) return "resp-not-supported"; if (val == CY_PD_RESP_TRANSACTION_FAILED) return "resp-transaction-failed"; if (val == CY_PD_RESP_PD_COMMAND_FAILED) return "resp-pd-command-failed"; if (val == CY_PD_RESP_UNDEFINED) return "resp-undefined"; if (val == CY_PD_RESP_RA_DETECT) return "resp-ra-detect"; if (val == CY_PD_RESP_RA_REMOVED) return "resp-ra-removed"; if (val == CY_PD_RESP_RESET_COMPLETE) return "resp-reset-complete"; if (val == CY_PD_RESP_MESSAGE_QUEUE_OVERFLOW) return "resp-message-queue-overflow"; if (val == CY_PD_RESP_OVER_CURRENT_DETECTED) return "resp-over-current-detected"; if (val == CY_PD_RESP_OVER_VOLTAGE_DETECTED) return "resp-over-voltage-detected"; if (val == CY_PD_RESP_TYPC_C_CONNECTED) return "resp-typc-c-connected"; if (val == CY_PD_RESP_TYPE_C_DISCONNECTED) return "resp-type-c-disconnected"; if (val == CY_PD_RESP_PD_CONTRACT_ESTABLISHED) return "resp-pd-contract-established"; if (val == CY_PD_RESP_DR_SWAP) return "resp-dr-swap"; if (val == CY_PD_RESP_PR_SWAP) return "resp-pr-swap"; if (val == CY_PD_RESP_VCON_SWAP) return "resp-vcon-swap"; if (val == CY_PD_RESP_PS_RDY) return "resp-ps-rdy"; if (val == CY_PD_RESP_GOTOMIN) return "resp-gotomin"; if (val == CY_PD_RESP_ACCEPT_MESSAGE) return "resp-accept-message"; if (val == CY_PD_RESP_REJECT_MESSAGE) return "resp-reject-message"; if (val == CY_PD_RESP_WAIT_MESSAGE) return "resp-wait-message"; if (val == CY_PD_RESP_HARD_RESET) return "resp-hard-reset"; if (val == CY_PD_RESP_VDM_RECEIVED) return "resp-vdm-received"; if (val == CY_PD_RESP_SRC_CAP_RCVD) return "resp-src-cap-rcvd"; if (val == CY_PD_RESP_SINK_CAP_RCVD) return "resp-sink-cap-rcvd"; if (val == CY_PD_RESP_DP_ALTERNATE_MODE) return "resp-dp-alternate-mode"; if (val == CY_PD_RESP_DP_DEVICE_CONNECTED) return "resp-dp-device-connected"; if (val == CY_PD_RESP_DP_DEVICE_NOT_CONNECTED) return "resp-dp-device-not-connected"; if (val == CY_PD_RESP_DP_SID_NOT_FOUND) return "resp-dp-sid-not-found"; if (val == CY_PD_RESP_MULTIPLE_SVID_DISCOVERED) return "resp-multiple-svid-discovered"; if (val == CY_PD_RESP_DP_FUNCTION_NOT_SUPPORTED) return "resp-dp-function-not-supported"; if (val == CY_PD_RESP_DP_PORT_CONFIG_NOT_SUPPORTED) return "resp-dp-port-config-not-supported"; if (val == CY_PD_HARD_RESET_SENT) return "hard-reset-sent"; if (val == CY_PD_SOFT_RESET_SENT) return "soft-reset-sent"; if (val == CY_PD_CABLE_RESET_SENT) return "cable-reset-sent"; if (val == CY_PD_SOURCE_DISBALED_STATE_ENTERED) return "source-disbaled-state-entered"; if (val == CY_PD_SENDER_RESPONSE_TIMER_TIMEOUT) return "sender-response-timer-timeout"; if (val == CY_PD_NO_VDM_RESPONSE_RECEIVED) return "no-vdm-response-received"; return NULL; } fwupd-1.7.5/plugins/ccgx/fu-ccgx-hpi-common.h000066400000000000000000000325041420024370600210310ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define I2C_READ_WRITE_DELAY_US 10000 /* 10 msec */ #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, /* rerform 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 { CY_PD_REG_DEVICE_MODE_ADDR, CY_PD_BOOT_MODE_REASON, CY_PD_SILICON_ID, CY_PD_BL_LAST_ROW = 0x04, CY_PD_REG_INTR_REG_ADDR = 0x06, CY_PD_JUMP_TO_BOOT_REG_ADDR, CY_PD_REG_RESET_ADDR, CY_PD_REG_ENTER_FLASH_MODE_ADDR = 0x0A, CY_PD_REG_VALIDATE_FW_ADDR, CY_PD_REG_FLASH_READ_WRITE_ADDR, CY_PD_GET_VERSION = 0x10, CY_PD_REG_DBG_PD_INIT = 0x12, CY_PD_REG_U_VDM_CTRL_ADDR = 0x20, CY_PD_REG_READ_PD_PROFILE = 0x22, CY_PD_REG_EFFECTIVE_SOURCE_PDO_MASK = 0x24, CY_PD_REG_EFFECTIVE_SINK_PDO_MASK, CY_PD_REG_SELECT_SOURCE_PDO, CY_PD_REG_SELECT_SINK_PDO, CY_PD_REG_PD_CONTROL, CY_PD_REG_PD_STATUS = 0x2C, CY_PD_REG_TYPE_C_STATUS = 0x30, CY_PD_REG_CURRENT_PDO = 0x34, CY_PD_REG_CURRENT_RDO = 0x38, CY_PD_REG_CURRENT_CABLE_VDO = 0x3C, CY_PD_REG_DISPLAY_PORT_STATUS = 0x40, CY_PD_REG_DISPLAY_PORT_CONFIG = 0x44, CY_PD_REG_ALTERNATE_MODE_MUX_SELECTION = 0X45, CY_PD_REG_EVENT_MASK = 0x48, CY_PD_REG_RESPONSE_ADDR = 0x7E, CY_PD_REG_BOOTDATA_MEMORY_ADDR = 0x80, CY_PD_REG_FWDATA_MEMEORY_ADDR = 0xC0, } CyPDReg; #define CY_PD_GET_SILICON_ID_CMD_SIG 0x53 #define CY_PD_REG_INTR_REG_CLEAR_RQT 0x01 #define CY_PD_JUMP_TO_BOOT_CMD_SIG 0x4A #define CY_PD_JUMP_TO_ALT_FW_CMD_SIG 0x41 #define CY_PD_DEVICE_RESET_CMD_SIG 0x52 #define CY_PD_REG_RESET_DEVICE_CMD 0x01 #define CY_PD_ENTER_FLASHING_MODE_CMD_SIG 0x50 #define CY_PD_FLASH_READ_WRITE_CMD_SIG 0x46 #define CY_PD_REG_FLASH_ROW_READ_CMD 0x00 #define CY_PD_REG_FLASH_ROW_WRITE_CMD 0x01 #define CY_PD_REG_FLASH_READ_WRITE_ROW_LSB 0x02 #define CY_PD_REG_FLASH_READ_WRITE_ROW_MSB 0x03 #define CY_PD_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 { /* responses */ CY_PD_RESP_NO_RESPONSE, CY_PD_RESP_SUCCESS = 0x02, CY_PD_RESP_FLASH_DATA_AVAILABLE, CY_PD_RESP_INVALID_COMMAND = 0x05, CY_PD_RESP_COLLISION_DETECTED, CY_PD_RESP_FLASH_UPDATE_FAILED, CY_PD_RESP_INVALID_FW, CY_PD_RESP_INVALID_ARGUMENTS, CY_PD_RESP_NOT_SUPPORTED, CY_PD_RESP_TRANSACTION_FAILED = 0x0C, CY_PD_RESP_PD_COMMAND_FAILED, CY_PD_RESP_UNDEFINED, CY_PD_RESP_RA_DETECT = 0x10, CY_PD_RESP_RA_REMOVED, /* device specific events */ CY_PD_RESP_RESET_COMPLETE = 0x80, CY_PD_RESP_MESSAGE_QUEUE_OVERFLOW, /* type-c specific events */ CY_PD_RESP_OVER_CURRENT_DETECTED, CY_PD_RESP_OVER_VOLTAGE_DETECTED, CY_PD_RESP_TYPC_C_CONNECTED, CY_PD_RESP_TYPE_C_DISCONNECTED, /* pd specific events and asynchronous messages */ CY_PD_RESP_PD_CONTRACT_ESTABLISHED, CY_PD_RESP_DR_SWAP, CY_PD_RESP_PR_SWAP, CY_PD_RESP_VCON_SWAP, CY_PD_RESP_PS_RDY, CY_PD_RESP_GOTOMIN, CY_PD_RESP_ACCEPT_MESSAGE, CY_PD_RESP_REJECT_MESSAGE, CY_PD_RESP_WAIT_MESSAGE, CY_PD_RESP_HARD_RESET, CY_PD_RESP_VDM_RECEIVED, CY_PD_RESP_SRC_CAP_RCVD, CY_PD_RESP_SINK_CAP_RCVD, CY_PD_RESP_DP_ALTERNATE_MODE, CY_PD_RESP_DP_DEVICE_CONNECTED, CY_PD_RESP_DP_DEVICE_NOT_CONNECTED, CY_PD_RESP_DP_SID_NOT_FOUND, CY_PD_RESP_MULTIPLE_SVID_DISCOVERED, CY_PD_RESP_DP_FUNCTION_NOT_SUPPORTED, CY_PD_RESP_DP_PORT_CONFIG_NOT_SUPPORTED, CY_PD_HARD_RESET_SENT, CY_PD_SOFT_RESET_SENT, CY_PD_CABLE_RESET_SENT, CY_PD_SOURCE_DISBALED_STATE_ENTERED, CY_PD_SENDER_RESPONSE_TIMER_TIMEOUT, CY_PD_NO_VDM_RESPONSE_RECEIVED } CyPDResp; 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; const gchar * fu_ccgx_pd_resp_to_string(CyPDResp val); fwupd-1.7.5/plugins/ccgx/fu-ccgx-hpi-device.c000066400000000000000000001330611420024370600207730ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-ccgx-common.h" #include "fu-ccgx-firmware.h" #include "fu-ccgx-hpi-common.h" #include "fu-ccgx-hpi-device.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 */ FWMode fw_mode; FWImageType 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_US 10000 #define HPI_CMD_ENTER_FLASH_MODE_DELAY_US 20000 #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_US 150000 #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_common_string_append_kx(str, idt, "ScbIndex", self->scb_index); fu_common_string_append_kx(str, idt, "SiliconId", self->silicon_id); fu_common_string_append_kx(str, idt, "FwAppType", self->fw_app_type); fu_common_string_append_kx(str, idt, "HpiAddrsz", self->hpi_addrsz); fu_common_string_append_kx(str, idt, "NumPorts", self->num_ports); fu_common_string_append_kv(str, idt, "FWMode", fu_ccgx_fw_mode_to_string(self->fw_mode)); fu_common_string_append_kv(str, idt, "FwImageType", fu_ccgx_fw_image_type_to_string(self->fw_image_type)); fu_common_string_append_kx(str, idt, "EpBulkIn", self->ep_bulk_in); fu_common_string_append_kx(str, idt, "EpBulkOut", self->ep_bulk_out); fu_common_string_append_kx(str, idt, "EpIntrIn", self->ep_intr_in); if (self->flash_row_size > 0) fu_common_string_append_kx(str, idt, "CcgxFlashRowSize", self->flash_row_size); if (self->flash_size > 0) fu_common_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_common_read_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 */ g_usleep(I2C_READ_WRITE_DELAY_US); 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 */ g_usleep(I2C_READ_WRITE_DELAY_US); 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; } g_usleep(HPI_CMD_REG_READ_WRITE_DELAY_US); 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; } g_usleep(HPI_CMD_REG_READ_WRITE_DELAY_US); 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; } g_usleep(HPI_CMD_REG_READ_WRITE_DELAY_US); 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, CY_PD_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, CY_PD_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, CyPDResp *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; CyPDResp 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, CY_PD_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 != CY_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); CyPDResp hpi_event = 0; guint8 buf[] = {CY_PD_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, CY_PD_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 != CY_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 */ g_usleep(HPI_CMD_ENTER_FLASH_MODE_DELAY_US); 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); CyPDResp 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, CY_PD_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 != CY_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 */ g_usleep(HPI_CMD_ENTER_FLASH_MODE_DELAY_US); 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; CyPDResp hpi_event = 0; guint16 addr_tmp = 0; guint8 bufhw[] = { CY_PD_FLASH_READ_WRITE_CMD_SIG, CY_PD_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 : CY_PD_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, CY_PD_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 != CY_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; CyPDResp hpi_event = 0; guint16 addr_tmp; guint8 bufhw[] = { CY_PD_FLASH_READ_WRITE_CMD_SIG, CY_PD_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, CY_PD_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 != CY_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 : CY_PD_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[] = { CY_PD_JUMP_TO_ALT_FW_CMD_SIG, }; /* not required */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) || self->fw_image_type == FW_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, CY_PD_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[] = { CY_PD_DEVICE_RESET_CMD_SIG, CY_PD_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, CY_PD_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); FWMode 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, "FWMode 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, FWMode 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 FW_MODE_FW1: *addr = addr_max - 1; break; case 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, FWMode fw_mode, CCGxMetaData *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((guint8 *)metadata, sizeof(*metadata), 0x0, buf, self->flash_row_size, md_offset, sizeof(metadata), error); } /* this will only work after fu_ccgx_hpi_enter_flash_mode() has been used */ static gboolean fu_ccgx_hpi_save_metadata(FuCcgxHpiDevice *self, FWMode fw_mode, CCGxMetaData *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, (guint8 *)metadata, sizeof(*metadata), 0x0, sizeof(metadata), 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); CCGxMetaData metadata = {0x0}; GPtrArray *records = fu_ccgx_firmware_get_records(FU_CCGX_FIRMWARE(firmware)); FWMode fw_mode_alt = fu_ccgx_fw_mode_get_alternate(self->fw_mode); 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_BUSY, 5); /* invalidate metadata */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10); 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, &metadata, error)) return FALSE; metadata.metadata_valid = 0x00; if (!fu_ccgx_hpi_save_metadata(self, fw_mode_alt, &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}; g_autofree gchar *instance_id = NULL; if (!fu_ccgx_hpi_device_reg_read(self, CY_PD_SILICON_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "get silicon id error: "); return FALSE; } if (!fu_common_read_uint16_safe(buf, sizeof(buf), 0x0, &self->silicon_id, G_LITTLE_ENDIAN, error)) return FALSE; /* add quirks */ instance_id = g_strdup_printf("CCGX\\SID_%X", self->silicon_id); fu_device_add_instance_id_full(FU_DEVICE(self), instance_id, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* 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 Instance ID %s: 0x%x/0x%x", instance_id, self->flash_row_size, self->flash_size); return FALSE; } /* success */ return TRUE; } static void fu_ccgx_hpi_device_set_version_raw(FuCcgxHpiDevice *self, guint32 version_raw) { g_autofree gchar *version = fu_ccgx_version_to_string(version_raw); fu_device_set_version(FU_DEVICE(self), version); fu_device_set_version_raw(FU_DEVICE(self), version_raw); } static void fu_ccgx_hpi_device_setup_with_fw_mode(FuCcgxHpiDevice *self) { fu_device_set_logical_id(FU_DEVICE(self), fu_ccgx_fw_mode_to_string(self->fw_mode)); } static void fu_ccgx_hpi_device_setup_with_app_type(FuCcgxHpiDevice *self) { if (self->silicon_id != 0x0 && self->fw_app_type != 0x0) { g_autofree gchar *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; /* we get fw_image_type from the quirk */ instance_id1 = g_strdup_printf("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X", fu_usb_device_get_vid(FU_USB_DEVICE(self)), fu_usb_device_get_pid(FU_USB_DEVICE(self)), self->silicon_id, self->fw_app_type); fu_device_add_instance_id(FU_DEVICE(self), instance_id1); instance_id2 = g_strdup_printf("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X&MODE_%s", fu_usb_device_get_vid(FU_USB_DEVICE(self)), fu_usb_device_get_pid(FU_USB_DEVICE(self)), self->silicon_id, self->fw_app_type, fu_ccgx_fw_mode_to_string(self->fw_mode)); fu_device_add_instance_id(FU_DEVICE(self), instance_id2); } } 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, CY_PD_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 = (FWMode)(mode & 0x03); fu_ccgx_hpi_device_setup_with_fw_mode(self); /* 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 != FW_MODE_BOOT) { guint16 bufsz; guint32 versions[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, CY_PD_GET_VERSION, bufver, bufsz, error)) { g_prefix_error(error, "get version error: "); return FALSE; } /* fw1 */ if (!fu_common_read_uint32_safe(bufver, sizeof(bufver), 0x0c, &versions[FW_MODE_FW1], G_LITTLE_ENDIAN, error)) return FALSE; /* fw2 */ if (!fu_common_read_uint32_safe(bufver, sizeof(bufver), 0x14, &versions[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; fu_ccgx_hpi_device_setup_with_app_type(self); /* if running in bootloader force an upgrade to any version */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_ccgx_hpi_device_set_version_raw(self, 0x0); } else { fu_ccgx_hpi_device_set_version_raw(self, versions[self->fw_mode]); } } /* not supported in boot mode */ if (self->fw_mode == 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"); } /* 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 == CY_PD_RESP_RESET_COMPLETE) g_usleep(HPI_CMD_RESET_COMPLETE_DELAY_US); } /* 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->silicon_id = tmp; return TRUE; } if (g_strcmp0(key, "CcgxFlashRowSize") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->flash_row_size = tmp; return TRUE; } if (g_strcmp0(key, "CcgxFlashSize") == 0) { if (!fu_common_strtoull_full(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_fw_image_type_from_string(value); if (self->fw_image_type != FW_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_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_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_retry_set_delay(FU_DEVICE(self), HPI_CMD_RETRY_DELAY); /* 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; } fwupd-1.7.5/plugins/ccgx/fu-ccgx-hpi-device.h000066400000000000000000000005511420024370600207750ustar00rootroot00000000000000/* * 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.7.5/plugins/ccgx/fu-plugin-ccgx.c000066400000000000000000000017771420024370600202640ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ccgx-dmc-device.h" #include "fu-ccgx-dmc-firmware.h" #include "fu-ccgx-firmware.h" #include "fu-ccgx-hid-device.h" #include "fu-ccgx-hpi-device.h" static void fu_plugin_ccgx_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CCGX_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CCGX_DMC_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_HPI_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_DMC_DEVICE); fu_context_add_quirk_key(ctx, "CcgxFlashRowSize"); fu_context_add_quirk_key(ctx, "CcgxFlashSize"); fu_context_add_quirk_key(ctx, "CcgxImageKind"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_ccgx_init; } fwupd-1.7.5/plugins/ccgx/fu-self-test.c000066400000000000000000000060451420024370600177430ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ccgx-dmc-firmware.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); } 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) { 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); g_test_add_func("/ccgx-dmc/firmware{xml}", fu_ccgx_dmc_firmware_xml_func); return g_test_run(); } fwupd-1.7.5/plugins/ccgx/meson.build000066400000000000000000000031361420024370600174210ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginCcgx"'] install_data([ 'ccgx-ids.quirk', 'ccgx.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ccgx', fu_hash, sources : [ 'fu-plugin-ccgx.c', 'fu-ccgx-common.c', # fuzzing 'fu-ccgx-firmware.c', # fuzzing 'fu-ccgx-hid-device.c', 'fu-ccgx-hpi-common.c', 'fu-ccgx-hpi-device.c', 'fu-ccgx-dmc-device.c', 'fu-ccgx-dmc-firmware.c', # fuzzing 'fu-ccgx-dmc-common.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, gudev, ], ) endif if get_option('tests') install_data(['tests/ccgx.builder.xml', '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-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-ccgx-common.c', 'fu-ccgx-firmware.c', 'fu-ccgx-dmc-common.c', 'fu-ccgx-dmc-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('ccgx-self-test', e, env : env) endif fwupd-1.7.5/plugins/ccgx/tests/000077500000000000000000000000001420024370600164165ustar00rootroot00000000000000fwupd-1.7.5/plugins/ccgx/tests/ccgx-dmc.bin000066400000000000000000000000531420024370600205730ustar00rootroot00000000000000FWCT(fwupd-1.7.5/plugins/ccgx/tests/ccgx-dmc.builder.xml000066400000000000000000000001701420024370600222500ustar00rootroot00000000000000 0x1000800 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/ccgx/tests/ccgx.bin000066400000000000000000000004771420024370600200440ustar00rootroot000000000000001F0011AF0000 :00000E000B68656C6C6F20776F726C648B :00000C008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A400000000130000000B00000000000000000000000059430000000000000000000000000000000000000000000000000000000000000000000000000000000016 fwupd-1.7.5/plugins/ccgx/tests/ccgx.builder.xml000066400000000000000000000001601420024370600215060ustar00rootroot00000000000000 0x1F00 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/cfu/000077500000000000000000000000001420024370600151055ustar00rootroot00000000000000fwupd-1.7.5/plugins/cfu/README.md000066400000000000000000000023351420024370600163670ustar00rootroot00000000000000# Component Firmware update ## Introduction CFU is a protocol from Microsoft to make it easy to install firmware on HID devices. This protocol is unique in that it requires has a pre-download phase before sending the firmware to the microcontroller. This is 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. See for more details. This plugin supports the following protocol ID: * com.microsoft.cfu ## 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` ## 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`. fwupd-1.7.5/plugins/cfu/cfu.quirk000066400000000000000000000000451420024370600167360ustar00rootroot00000000000000[USB\VID_273F&PID_100A] Plugin = cfu fwupd-1.7.5/plugins/cfu/fu-cfu-device.c000066400000000000000000000170731420024370600177030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cfu-device.h" #include "fu-cfu-module.h" struct _FuCfuDevice { FuHidDevice parent_instance; guint8 protocol_version; }; G_DEFINE_TYPE(FuCfuDevice, fu_cfu_device, FU_TYPE_HID_DEVICE) #define FU_CFU_DEVICE_TIMEOUT 5000 /* ms */ #define FU_CFU_FEATURE_SIZE 60 /* bytes */ #define FU_CFU_CMD_GET_FIRMWARE_VERSION 0x00 #define FU_CFU_CMD_SEND_OFFER 0x00 // TODO 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_common_string_append_kx(str, idt, "ProtocolVersion", self->protocol_version); } static gboolean fu_cfu_device_write_offer(FuCfuDevice *self, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; guint8 buf2[FU_CFU_FEATURE_SIZE] = {0}; g_autofree guint8 *buf_tmp = 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; /* send it to the hardware */ buf = g_bytes_get_data(blob, &bufsz); buf_tmp = fu_memdup_safe(buf, bufsz, error); if (buf_tmp == NULL) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_CFU_CMD_SEND_OFFER, buf_tmp, bufsz, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to send offer: "); return FALSE; } if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_CFU_CMD_SEND_OFFER, buf2, sizeof(buf2), FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } g_debug("status:%s reject:%s", fu_cfu_device_offer_to_string(buf2[13]), fu_cfu_device_reject_to_string(buf2[9])); if (buf2[13] != FU_CFU_DEVICE_OFFER_ACCEPT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not supported: %s", fu_cfu_device_offer_to_string(buf2[13])); return FALSE; } /* success */ return TRUE; } static gboolean fu_cfu_device_write_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); guint8 databuf[60] = {0}; guint8 buf2[60] = {0}; /* flags */ if (i == 0) databuf[0] = FU_CFU_DEVICE_FLAG_FIRST_BLOCK; else if (i == chunks->len - 1) databuf[0] = FU_CFU_DEVICE_FLAG_LAST_BLOCK; /* length */ databuf[1] = fu_chunk_get_data_sz(chk); /* sequence number */ if (!fu_common_write_uint16_safe(databuf, sizeof(databuf), 0x2, i + 1, G_LITTLE_ENDIAN, error)) return FALSE; /* address */ if (!fu_common_write_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; } // send // revc if (buf2[5] != FU_CFU_DEVICE_STATUS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send chunk %u: %s", i + 1, fu_cfu_device_status_to_string(buf2[5])); 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); /* offer */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98); /* payload */ /* send offer */ fw_offer = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_offer == NULL) return FALSE; if (!fu_cfu_device_write_offer(self, fw_offer, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* send payload */ fw_payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_payload == NULL) return FALSE; if (!fu_cfu_device_write_payload(self, fw_payload, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_cfu_device_setup(FuDevice *device, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); guint8 buf[FU_CFU_FEATURE_SIZE] = {0}; guint8 component_cnt = 0; guint8 tmp = 0; gsize offset = 0; g_autoptr(GHashTable) modules_by_cid = NULL; /* FuHidDevice->setup */ if (!FU_DEVICE_CLASS(fu_cfu_device_parent_class)->setup(device, error)) return FALSE; /* get version */ if (!fu_hid_device_get_report(FU_HID_DEVICE(device), FU_CFU_CMD_GET_FIRMWARE_VERSION, buf, sizeof(buf), FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x0, &component_cnt, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x3, &tmp, error)) return FALSE; self->protocol_version = tmp & 0b1111; /* keep track of all modules so we can work out which are dual bank */ modules_by_cid = g_hash_table_new(g_int_hash, g_int_equal); /* read each component module version */ offset += 4; 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, sizeof(buf), 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 += 0x8; } /* success */ return TRUE; } static void fu_cfu_device_init(FuCfuDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } 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; } fwupd-1.7.5/plugins/cfu/fu-cfu-device.h000066400000000000000000000004301420024370600176750ustar00rootroot00000000000000/* * 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.7.5/plugins/cfu/fu-cfu-module.c000066400000000000000000000135341420024370600177270ustar00rootroot00000000000000/*# * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cfu-module.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_common_string_append_kx(str, idt, "ComponentId", self->component_id); fu_common_string_append_kx(str, idt, "Bank", self->bank); } guint8 fu_cfu_module_get_component_id(FuCfuModule *self) { return self->component_id; } guint8 fu_cfu_module_get_bank(FuCfuModule *self) { return self->bank; } gboolean fu_cfu_module_setup(FuCfuModule *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { FuDevice *parent = fu_device_get_proxy(FU_DEVICE(self)); guint32 version_raw = 0; guint8 tmp = 0; g_autofree gchar *instance_id0 = NULL; g_autofree gchar *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; g_autofree gchar *logical_id = NULL; g_autofree gchar *version = NULL; /* component ID */ if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x5, &self->component_id, error)) return FALSE; /* these GUIDs may cause the name or version-format to be overwritten */ instance_id0 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X", fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)), fu_udev_device_get_model(FU_UDEV_DEVICE(parent))); fu_device_add_instance_id(FU_DEVICE(self), instance_id0); instance_id1 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&CID_%02X", fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)), fu_udev_device_get_model(FU_UDEV_DEVICE(parent)), self->component_id); fu_device_add_instance_id(FU_DEVICE(self), instance_id1); /* bank */ if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x4, &tmp, error)) return FALSE; self->bank = tmp & 0b11; instance_id2 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&CID_%02X&BANK_%01X", fu_udev_device_get_vendor(FU_UDEV_DEVICE(parent)), fu_udev_device_get_model(FU_UDEV_DEVICE(parent)), self->component_id, self->bank); fu_device_add_instance_id(FU_DEVICE(self), instance_id2); /* set name, if not already set using a quirk */ if (fu_device_get_name(FU_DEVICE(self)) == 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(FU_DEVICE(self), name); } /* version */ if (!fu_common_read_uint32_safe(buf, bufsz, offset, &version_raw, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_set_version_raw(FU_DEVICE(self), version_raw); version = fu_common_version_from_uint32(version_raw, fu_device_get_version_format(FU_DEVICE(self))); fu_device_set_version(FU_DEVICE(self), version); /* logical ID */ logical_id = g_strdup_printf("CID:0x%02x,BANK:0x%02x", self->component_id, self->bank); fu_device_set_logical_id(FU_DEVICE(self), 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) offer = fu_cfu_offer_new(); g_autoptr(FuFirmware) payload = fu_cfu_payload_new(); g_autoptr(GBytes) fw_offset = NULL; /* offer */ if (!fu_firmware_parse(offer, fw, flags, error)) return NULL; fu_firmware_set_id(offer, FU_FIRMWARE_ID_HEADER); fu_firmware_add_image(firmware, offer); /* payload */ fw_offset = fu_common_bytes_new_offset(fw, 0x10, g_bytes_get_size(fw) - 0x10, error); if (fw_offset == NULL) return NULL; if (!fu_firmware_parse(payload, fw_offset, 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; g_autoptr(GBytes) fw = NULL; /* get the whole image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* 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; } return fu_device_write_firmware(proxy, fw, 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_QUAD); } 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, "ctx", fu_device_get_context(parent), "proxy", parent, NULL); return self; } fwupd-1.7.5/plugins/cfu/fu-cfu-module.h000066400000000000000000000010741420024370600177300ustar00rootroot00000000000000/* * 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); guint8 fu_cfu_module_get_bank(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.7.5/plugins/cfu/fu-plugin-cfu.c000066400000000000000000000006461420024370600177400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cfu-device.h" static void fu_plugin_cfu_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_CFU_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_cfu_init; } fwupd-1.7.5/plugins/cfu/meson.build000066400000000000000000000011561420024370600172520ustar00rootroot00000000000000if get_option('plugin_cfu') if not get_option('gudev') error('gudev is required for plugin_cfu') endif cargs = ['-DG_LOG_DOMAIN="FuPluginCfu"'] install_data([ 'cfu.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_cfu', fu_hash, sources : [ 'fu-cfu-device.c', 'fu-cfu-module.c', 'fu-plugin-cfu.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/colorhug/000077500000000000000000000000001420024370600161525ustar00rootroot00000000000000fwupd-1.7.5/plugins/colorhug/README.md000066400000000000000000000023161420024370600174330ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_273F&PID_1001` * `USB\VID_273F` ## 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`. fwupd-1.7.5/plugins/colorhug/colorhug.quirk000066400000000000000000000026551420024370600210610ustar00rootroot00000000000000# 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 [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.7.5/plugins/colorhug/fu-colorhug-common.c000066400000000000000000000057411420024370600220450ustar00rootroot00000000000000/* * 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.7.5/plugins/colorhug/fu-colorhug-common.h000066400000000000000000000023121420024370600220410ustar00rootroot00000000000000/* * 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.7.5/plugins/colorhug/fu-colorhug-device.c000066400000000000000000000375621420024370600220220ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 */ if (g_getenv("FWUPD_COLORHUG_VERBOSE") != NULL) fu_common_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; } if (g_getenv("FWUPD_COLORHUG_VERBOSE") != NULL) fu_common_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"); 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_common_write_uint16(buf + 0, addr, G_LITTLE_ENDIAN); fu_common_write_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_common_read_uint16(buf + 0, G_LITTLE_ENDIAN), fu_common_read_uint16(buf + 2, G_LITTLE_ENDIAN), fu_common_read_uint16(buf + 4, G_LITTLE_ENDIAN)); } static gboolean fu_colorhug_device_probe(FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_colorhug_device_parent_class)->probe(device, error)) return FALSE; /* 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 */ fu_device_set_version_format(device, fu_common_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(device, version); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); } 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_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 19); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35); /* 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, 0x00, /* page_sz */ CH_FLASH_TRANSFER_BLOCK_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 buf[CH_FLASH_TRANSFER_BLOCK_SIZE + 4]; g_autoptr(GError) error_local = NULL; /* set address, length, checksum, data */ fu_common_write_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_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)chunks->len); } fu_progress_step_done(progress); /* verify each block */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_common_write_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_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)chunks->len); } 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_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); 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.7.5/plugins/colorhug/fu-colorhug-device.h000066400000000000000000000004611420024370600220130ustar00rootroot00000000000000/* * 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.7.5/plugins/colorhug/fu-plugin-colorhug.c000066400000000000000000000006721420024370600220510ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-colorhug-device.h" static void fu_plugin_colorhug_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_COLORHUG_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_colorhug_init; } fwupd-1.7.5/plugins/colorhug/meson.build000066400000000000000000000010711420024370600203130ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginColorHug"'] install_data([ 'colorhug.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_colorhug', fu_hash, sources : [ 'fu-colorhug-common.c', 'fu-colorhug-device.c', 'fu-plugin-colorhug.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/cpu/000077500000000000000000000000001420024370600151175ustar00rootroot00000000000000fwupd-1.7.5/plugins/cpu/README.md000066400000000000000000000007071420024370600164020ustar00rootroot00000000000000# 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` * `CPUID\PRO_0&FAM_06&MOD_0E` * `CPUID\PRO_0&FAM_06&MOD_0E&STP_3` ## External Interface Access This plugin requires no extra access. fwupd-1.7.5/plugins/cpu/cpu.quirk000066400000000000000000000001211420024370600167550ustar00rootroot00000000000000# Intel Atom Bay Trail [Silvermont] [CPUID\PRO_0&FAM_06&MOD_37] PciBcrAddr = 0x0 fwupd-1.7.5/plugins/cpu/fu-cpu-device.c000066400000000000000000000320031420024370600177150ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #include "fu-cpu-device.h" struct _FuCpuDevice { FuDevice parent_instance; FuCpuDeviceFlag flags; }; G_DEFINE_TYPE(FuCpuDevice, fu_cpu_device, FU_TYPE_DEVICE) 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_common_string_append_kb(str, idt, "HasSHSTK", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SHSTK)); fu_common_string_append_kb(str, idt, "HasIBT", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_IBT)); fu_common_string_append_kb(str, idt, "HasTME", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_TME)); fu_common_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 "AMD"; 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_guid_full(FU_DEVICE(self), "cpu", FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); 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; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; g_autofree gchar *devid3 = NULL; /* decode according to https://en.wikipedia.org/wiki/CPUID */ if (!fu_common_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; devid1 = g_strdup_printf("CPUID\\PRO_%01X&FAM_%02X", processor_id, family_id); fu_device_add_instance_id(device, devid1); devid2 = g_strdup_printf("CPUID\\PRO_%01X&FAM_%02X&MOD_%02X", processor_id, family_id, model_id); fu_device_add_instance_id(device, devid2); devid3 = g_strdup_printf("CPUID\\PRO_%01X&FAM_%02X&MOD_%02X&STP_%01X", processor_id, family_id, model_id, stepping_id); fu_device_add_instance_id(device, devid3); 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_common_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_common_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; if (!fu_common_cpuid(0x7, NULL, &ebx, &ecx, NULL, 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 ((ecx >> 13) & 0x1) self->flags |= FU_CPU_DEVICE_FLAG_TME; if ((ecx >> 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_common_strtoull_full(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 void fu_cpu_device_add_security_attrs_intel_cet_enabled(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self))); fu_security_attrs_append(attrs, attr); /* check for CET */ 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_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_cpu_device_add_security_attrs_intel_cet_active(FuCpuDevice *self, FuSecurityAttrs *attrs) { gint exit_status = 0xff; g_autofree gchar *toolfn = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* check for CET */ if (!fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SHSTK) || !fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_IBT)) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self))); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fu_security_attrs_append(attrs, attr); /* check that userspace has been compiled for CET support */ toolfn = g_build_filename(FWUPD_LIBEXECDIR, "fwupd", "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); } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_SUPPORTED); } static void fu_cpu_device_add_security_attrs_intel_tme(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION); fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self))); 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_cpu_device_add_security_attrs_intel_smap(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_SMAP); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION); fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self))); 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_cpu_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuCpuDevice *self = FU_CPU_DEVICE(device); /* only Intel */ if (fu_common_get_cpu_vendor() != FU_CPU_VENDOR_INTEL) return; fu_cpu_device_add_security_attrs_intel_cet_enabled(self, attrs); fu_cpu_device_add_security_attrs_intel_cet_active(self, attrs); fu_cpu_device_add_security_attrs_intel_tme(self, attrs); fu_cpu_device_add_security_attrs_intel_smap(self, attrs); } 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.7.5/plugins/cpu/fu-cpu-device.h000066400000000000000000000011471420024370600177270ustar00rootroot00000000000000/* * 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) 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; FuCpuDevice * fu_cpu_device_new(FuContext *ctx); gboolean fu_cpu_device_has_flag(FuCpuDevice *self, FuCpuDeviceFlag flag); fwupd-1.7.5/plugins/cpu/fu-cpu-helper-cet-common.c000066400000000000000000000010251420024370600217740ustar00rootroot00000000000000/* * 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.7.5/plugins/cpu/fu-cpu-helper-cet-common.h000066400000000000000000000003241420024370600220020ustar00rootroot00000000000000/* * 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.7.5/plugins/cpu/fu-cpu-helper-cet.c000066400000000000000000000013641420024370600205140ustar00rootroot00000000000000/* * 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.7.5/plugins/cpu/fu-plugin-cpu.c000066400000000000000000000015721420024370600177630ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cpu-device.h" static void fu_plugin_cpu_init(FuPlugin *plugin) { fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_BEFORE, "msr"); } static gboolean fu_plugin_cpu_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuCpuDevice) dev = fu_cpu_device_new(ctx); if (!fu_device_probe(FU_DEVICE(dev), error)) return FALSE; if (!fu_device_setup(FU_DEVICE(dev), error)) return FALSE; fu_plugin_cache_add(plugin, "cpu", dev); fu_plugin_device_add(plugin, FU_DEVICE(dev)); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_cpu_init; vfuncs->coldplug = fu_plugin_cpu_coldplug; } fwupd-1.7.5/plugins/cpu/meson.build000066400000000000000000000027041420024370600172640ustar00rootroot00000000000000if get_option('hsi') and get_option('plugin_cpu') cargs = ['-DG_LOG_DOMAIN="FuPluginCpu"'] install_data(['cpu.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_cpu', fu_hash, sources : [ 'fu-plugin-cpu.c', 'fu-cpu-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], 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.7.5/plugins/cros-ec/000077500000000000000000000000001420024370600156635ustar00rootroot00000000000000fwupd-1.7.5/plugins/cros-ec/README.md000066400000000000000000000031271420024370600171450ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/cros-ec/cros-ec.quirk000066400000000000000000000007321420024370600202750ustar00rootroot00000000000000# 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 fwupd-1.7.5/plugins/cros-ec/data/000077500000000000000000000000001420024370600165745ustar00rootroot00000000000000fwupd-1.7.5/plugins/cros-ec/data/lsusb-servo-micro.txt000066400000000000000000000205531420024370600227350ustar00rootroot00000000000000 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.7.5/plugins/cros-ec/fu-cros-ec-common.c000066400000000000000000000034031420024370600212600ustar00rootroot00000000000000/* * 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) { g_auto(GStrv) v_split = NULL; 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 */ v_split = g_strsplit(version_raw, "_v", 2); if (g_strv_length(v_split) < 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "version marker not found"); return FALSE; } marker_split = g_strsplit_set(v_split[1], "-+", 2); if (g_strv_length(marker_split) < 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "hash marker not found: %s", v_split[1]); 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; } g_strlcpy(version->triplet, marker_split[0], 32); if (g_strlcpy(version->boardname, v_split[0], 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(v_split[1], "+") != NULL); return TRUE; } fwupd-1.7.5/plugins/cros-ec/fu-cros-ec-common.h000066400000000000000000000101751420024370600212710ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/plugins/cros-ec/fu-cros-ec-firmware.c000066400000000000000000000125751420024370600216160ustar00rootroot00000000000000/* * 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" #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, guint64 addr_start, guint64 addr_end, 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.7.5/plugins/cros-ec/fu-cros-ec-firmware.h000066400000000000000000000025051420024370600216130ustar00rootroot00000000000000/* * 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.7.5/plugins/cros-ec/fu-cros-ec-usb-device.c000066400000000000000000000761461420024370600220340ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; #if G_USB_CHECK_VERSION(0, 3, 5) index = g_usb_device_get_configuration_index(usb_device); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif 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) { #if G_USB_CHECK_VERSION(0, 3, 3) 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 = NULL; 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; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif } static gboolean fu_cros_ec_usb_device_probe(FuDevice *device, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_cros_ec_usb_device_parent_class)->probe(device, error)) return FALSE; /* 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; gsize rxed_size = 0; memset(&ufh, 0, sizeof(ufh)); 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; 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_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%zx bytes to %#x", 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; guint16 subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; guint8 command_body[2]; /* 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; guint16 subcommand = UPDATE_EXTRA_CMD_JUMP_TO_RW; guint8 command_body[2]; /* 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_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)) { gsize response_size = 1; guint8 response; guint16 subcommand = UPDATE_EXTRA_CMD_STAY_IN_RO; guint8 command_body[2]; /* Max command body size. */ gsize command_body_size = 0; START_RESP start_resp; fu_device_remove_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)) { 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) { g_debug("skipping immediate reboot in case of already in bootloader"); /* in RO so skip reboot */ return TRUE; } else 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_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_common_string_append_kv(str, idt, "GitHash", self->version.sha1); fu_common_string_append_kb(str, idt, "Dirty", self->version.dirty); fu_common_string_append_ku(str, idt, "ProtocolVersion", self->protocol_version); fu_common_string_append_ku(str, idt, "HeaderType", self->header_type); fu_common_string_append_ku(str, idt, "MaxPDUSize", self->targ.common.maximum_pdu_size); fu_common_string_append_kx(str, idt, "FlashProtectionStatus", self->targ.common.flash_protection); fu_common_string_append_kv(str, idt, "RawVersion", self->targ.common.version); fu_common_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_common_string_append_kv(str, idt, "MinRollback", min_rollback); fu_common_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_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_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; } fwupd-1.7.5/plugins/cros-ec/fu-cros-ec-usb-device.h000066400000000000000000000006011420024370600220200ustar00rootroot00000000000000/* * 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) struct _FuCrosEcUsbDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.7.5/plugins/cros-ec/fu-plugin-cros-ec.c000066400000000000000000000010441420024370600212650ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cros-ec-firmware.h" #include "fu-cros-ec-usb-device.h" static void fu_plugin_cros_ec_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_CROS_EC_USB_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CROS_EC_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_cros_ec_init; } fwupd-1.7.5/plugins/cros-ec/meson.build000066400000000000000000000011501420024370600200220ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginCrosEc"'] install_data(['cros-ec.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_cros_ec', fu_hash, sources : [ 'fu-plugin-cros-ec.c', 'fu-cros-ec-usb-device.c', 'fu-cros-ec-common.c', # fuzzing 'fu-cros-ec-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/cros-ec/tests/000077500000000000000000000000001420024370600170255ustar00rootroot00000000000000fwupd-1.7.5/plugins/cros-ec/tests/cros-ec.bin000066400000000000000000000005401420024370600210510ustar00rootroot00000000000000__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.7.5/plugins/cros-ec/tests/cros-ec.builder.xml000066400000000000000000000007721420024370600225350ustar00rootroot00000000000000 0x3000 RO_FRID Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= RW_FWID Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= EC_RO Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= EC_RW Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= fwupd-1.7.5/plugins/dell-dock/000077500000000000000000000000001420024370600161665ustar00rootroot00000000000000fwupd-1.7.5/plugins/dell-dock/README.md000066400000000000000000000054711420024370600174540ustar00rootroot00000000000000# 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. ## 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`. fwupd-1.7.5/plugins/dell-dock/dell-dock.quirk000066400000000000000000000136411420024370600211060ustar00rootroot00000000000000# # 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\VID_8087&PID_0B40] Name = Unprobed USB3 endpoint for USB4 hub Plugin = dell_dock # 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.00.00.00 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 = skips-restart,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 Icon = thunderbolt InstallDuration = 46 fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-common.c000066400000000000000000000031271420024370600220710ustar00rootroot00000000000000/* * 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" #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); } void fu_dell_dock_will_replug(FuDevice *device) { guint64 timeout = fu_device_get_install_duration(device); g_return_if_fail(FU_IS_DEVICE(device)); g_debug("Activated %" G_GUINT64_FORMAT "s replug delay for %s", timeout, fu_device_get_name(device)); fu_device_set_remove_delay(device, timeout * 1000); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-common.h000066400000000000000000000034271420024370600221010ustar00rootroot00000000000000/* * 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 "config.h" #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" #include "fu-dell-dock-usb-usb4.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 WD19_BASE 0x04 #define ATOMIC_BASE 0x05 gboolean fu_dell_dock_set_power(FuDevice *device, guint8 target, gboolean enabled, GError **error); void fu_dell_dock_will_replug(FuDevice *device); fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-hid.c000066400000000000000000000320551420024370600213470ustar00rootroot00000000000000/* * 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 #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_get_ec_status(FuDevice *self, guint8 *status1, guint8 *status2, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE(27), .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 get EC status: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to get EC status: "); return FALSE; } *status1 = cmd_buffer.data[25]; *status2 = cmd_buffer.data[26]; 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); else 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 */ g_usleep(2000000); 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); g_usleep(500000); } 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.7.5/plugins/dell-dock/fu-dell-dock-hid.h000066400000000000000000000044331420024370600213530ustar00rootroot00000000000000/* * 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 "config.h" #include #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_get_ec_status(FuDevice *self, guint8 *status1, guint8 *status2, 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.7.5/plugins/dell-dock/fu-dell-dock-hub.c000066400000000000000000000147311420024370600213620ustar00rootroot00000000000000/* * 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 _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 ec_type) { g_autofree gchar *devid = NULL; if (ec_type == ATOMIC_BASE) { 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 49); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 50); /* 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_debug("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_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_common_strtoull_full(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_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.7.5/plugins/dell-dock/fu-dell-dock-hub.h000066400000000000000000000017351420024370600213670ustar00rootroot00000000000000/* * 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 "config.h" #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 ec_type); fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-i2c-ec.c000066400000000000000000000743521420024370600216530ustar00rootroot00000000000000/* * 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-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_ec_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_debug("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 == WD19_BASE) { fu_device_add_instance_id(device, DELL_DOCK_EC_INSTANCE_ID); return TRUE; } else if (self->base_type == ATOMIC_BASE) { 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_debug("%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_common_vercmp_full(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 expecations */ 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_inhibit(device, "update-pending", "A pending update will be completed next time the dock " "is unplugged from your computer"); } } 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_common_string_append_ku(str, idt, "BaseType", self->base_type); fu_common_string_append_ku(str, idt, "BoardId", self->data->board_id); fu_common_string_append_ku(str, idt, "PowerSupply", self->data->power_supply_wattage); fu_common_string_append_kx(str, idt, "StatusPort0", self->data->port0_dock_status); fu_common_string_append_kx(str, idt, "StatusPort1", self->data->port1_dock_status); memcpy(service_tag, self->data->service_tag, 7); fu_common_string_append_kv(str, idt, "ServiceTag", service_tag); fu_common_string_append_ku(str, idt, "Configuration", self->data->dock_configuration); fu_common_string_append_kx(str, idt, "PackageFirmwareVersion", self->data->dock_firmware_pkg_ver); fu_common_string_append_ku(str, idt, "ModuleSerial", self->data->module_serial); fu_common_string_append_ku(str, idt, "OriginalModuleSerial", self->data->original_module_serial); fu_common_string_append_ku(str, idt, "Type", self->data->dock_type); fu_common_string_append_kx(str, idt, "ModuleType", self->data->module_type); fu_common_string_append_kv(str, idt, "MinimumEc", self->ec_minimum_version); fu_common_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_debug("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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85); /* 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_debug("writing EC firmware version %s", dynamic_version); /* meet the minimum EC version */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && (self->data->module_type != MODULE_TYPE_130_USB4) && (fu_common_vercmp_full(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_debug("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_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBoardMin") == 0) { if (!fu_common_strtoull_full(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_common_strtoull_full(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); g_usleep(2 * G_USEC_PER_SEC); 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_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_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_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; self = g_object_new(FU_TYPE_DELL_DOCK_EC, 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.7.5/plugins/dell-dock/fu-dell-dock-i2c-ec.h000066400000000000000000000027271420024370600216550ustar00rootroot00000000000000/* * 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 "config.h" #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 *self, 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_ec_type(FuDevice *device); fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-i2c-mst.c000066400000000000000000001075541420024370600220700ustar00rootroot00000000000000/* * 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 #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; } g_usleep(2000); } 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; else 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"); g_usleep(5000000); 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"); g_usleep(5000000); 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 */ g_usleep(200); /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 84); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1); 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_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10); /* stop esm */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* 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_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 15); /* erase */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); 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_debug("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_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_minor_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobBuildOffset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_build_offset = tmp; return TRUE; } else if (g_strcmp0(key, "DellDockInstallDurationI2C") == 0) { if (!fu_common_strtoull_full(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; 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; 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->probe = fu_dell_dock_mst_probe; 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(void) { FuDellDockMst *device = NULL; device = g_object_new(FU_TYPE_DELL_DOCK_MST, NULL); return device; } fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-i2c-mst.h000066400000000000000000000013521420024370600220620ustar00rootroot00000000000000/* * 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 "config.h" #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(void); fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-i2c-tbt.c000066400000000000000000000210501420024370600220400ustar00rootroot00000000000000/* * 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 #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_debug("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; g_usleep(2000000); 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_debug("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_common_strtoull_full(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } else if (g_strcmp0(key, "DellDockInstallDurationI2C") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, 60 * 60 * 24, error)) return FALSE; fu_device_set_install_duration(device, tmp); return TRUE; } else if (g_strcmp0(key, "DellDockHubVersionLowest") == 0) { self->hub_minimum_version = g_strdup(value); return TRUE; } else if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } else if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_common_strtoull_full(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_common_vercmp_full(version, MIN_NVM, FWUPD_VERSION_FORMAT_PAIR) < 0) { fu_device_set_update_error( device, "Updates over I2C are disabled due to insuffient NVM version"); return TRUE; } /* minimum Hub2 version that supports this feature */ hub_version = fu_device_get_version(fu_device_get_proxy(device)); if (fu_common_vercmp_full(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); } 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) { FuDellDockTbt *self = g_object_new(FU_TYPE_DELL_DOCK_TBT, NULL); fu_device_set_proxy(FU_DEVICE(self), proxy); return self; } fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-i2c-tbt.h000066400000000000000000000014361420024370600220530ustar00rootroot00000000000000/* * 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 "config.h" #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.7.5/plugins/dell-dock/fu-dell-dock-status.c000066400000000000000000000122371420024370600221260ustar00rootroot00000000000000/* * 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); FuDevice *parent; 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_debug("writing status firmware version %s", dynamic_version); parent = fu_device_get_parent(device); if (!fu_dell_dock_ec_commit_package(parent, 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) { FuDevice *parent = fu_device_get_parent(device); g_return_val_if_fail(parent != NULL, FALSE); return fu_device_open(parent, error); } static gboolean fu_dell_dock_status_close(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); return fu_device_close(parent, 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_common_strtoull_full(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); } 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(void) { FuDellDockStatus *self = NULL; self = g_object_new(FU_TYPE_DELL_DOCK_STATUS, NULL); return self; } fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-status.h000066400000000000000000000013771420024370600221360ustar00rootroot00000000000000/* * 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 "config.h" #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(void); fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-usb-usb4.c000066400000000000000000000431151420024370600222460ustar00rootroot00000000000000/* * 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 "fu-dell-dock-common.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 /* HUB operation OP codes */ #define OP_NVM_WRITE 0x20 #define OP_NVM_AUTH_WRITE 0x21 #define OP_NVM_READ 0x22 #define OP_NVM_SET_OFFSET 0x23 #define OP_DROM_READ 0x24 /* 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 /* NVM offset */ #define NVM_VER_OFFSET_MAJOR 0xa #define NVM_VER_OFFSET_MINOR 0x9 #define NVM_VID_OFFSET_MAJOR 0x221 #define NVM_VID_OFFSET_MINOR 0x220 #define NVM_PID_OFFSET_MAJOR 0x223 #define NVM_PID_OFFSET_MINOR 0x222 struct mbox_regx { guint16 opcode; guint8 rsvd; guint8 status; } __attribute__((packed)); struct _FuDellDockUsb4 { FuUsbDevice parent_instance; guint blocksz; guint8 intf_nr; }; G_DEFINE_TYPE(FuDellDockUsb4, fu_dell_dock_usb4, 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_dell_dock_usb4_hub_get_mmio(FuDevice *device, guint16 mbox_reg, guchar *buf, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); struct mbox_regx *regx; 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 */ (guchar *)buf, /* data */ 4, /* length */ 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) { regx = (struct mbox_regx *)buf; /* error status bit */ if (regx->status & 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]", regx->opcode, regx->status); return FALSE; } /* operation valid (OV) bit should be 0'b */ if (regx->status & 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]", regx->opcode, regx->status); return FALSE; } } return TRUE; } static gboolean fu_dell_dock_usb4_hub_set_mmio(FuDevice *device, guint16 mbox_reg, guchar *buf, 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 */ (guchar *)buf, /* data */ 4, /* length */ 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_dell_dock_usb4_mbox_data_read(FuDevice *device, guchar *data, guint8 length, GError **error) { guchar *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_dell_dock_usb4_hub_get_mmio(device, i, ptr, 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_dell_dock_usb4_mbox_data_write(FuDevice *device, const guchar *data, guint8 length, GError **error) { guchar *ptr = (guchar *)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_dell_dock_usb4_hub_set_mmio(device, i, ptr, error)) return FALSE; ptr += 4; } return TRUE; } static gboolean fu_dell_dock_usb4_hub_operation(FuDevice *device, guint16 opcode, guchar *metadata, GError **error) { struct mbox_regx *regx; gint max_tries = 100; guchar buf[4] = {0x0}; regx = (struct mbox_regx *)buf; regx->opcode = GUINT16_TO_LE(opcode); regx->status = MBOX_OPVALID; /* Write metadata register for operations that use it */ switch (opcode) { case OP_NVM_WRITE: case OP_NVM_AUTH_WRITE: break; case OP_NVM_READ: case OP_NVM_SET_OFFSET: case OP_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_dell_dock_usb4_hub_set_mmio(device, MBOX_REG_METADATA, metadata, 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 */ if (!fu_dell_dock_usb4_hub_set_mmio(device, MBOX_REG, buf, error)) return FALSE; /* leave early as successful USB4 AUTH resets the device immediately */ if (opcode == OP_NVM_AUTH_WRITE) return TRUE; for (gint i = 0; i <= max_tries; i++) { g_autoptr(GError) error_local = NULL; if (fu_dell_dock_usb4_hub_get_mmio(device, MBOX_REG, buf, &error_local)) return TRUE; if (i == max_tries) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "maximum tries exceeded: "); } g_usleep((gulong)10000); } return FALSE; } /** * fu_dell_dock_usb4_hub_nvm_read: * @device: a #FuDevice * @buf: array of bytes to store the read from registers * @length: length of buf array * @nvm_addr: nvm read offset, e.g. `0` * @error: (nullable): optional return location for an error * * Read NVM over USB interface **/ static gboolean fu_dell_dock_usb4_hub_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_common_write_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_dell_dock_usb4_hub_operation(device, OP_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_dell_dock_usb4_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_dell_dock_usb4_hub_nvm_write(FuDevice *device, const guint8 *buf, guint32 length, guint32 nvm_addr, FuProgress *progress, GError **error) { guint8 metadata[4]; guint32 bytes_done = 0; guint32 bytes_total = length; if (nvm_addr % 4) { 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 (length < 64 || length % 64) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid NVM length 0x%x, must be 64 byte aligned: ", length); return FALSE; } /* 1. Set initial offset, must be DW aligned */ fu_common_write_uint32(metadata, NVM_OFFSET_TO_METADATA(nvm_addr), G_LITTLE_ENDIAN); if (!fu_dell_dock_usb4_hub_operation(device, OP_NVM_SET_OFFSET, metadata, error)) { g_prefix_error(error, "hub NVM set offset error: "); return FALSE; } /* 2 Write data in 64 byte blocks */ fu_progress_set_percentage_full(progress, bytes_done, bytes_total); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); while (length > 0) { /* write data to mbox data regs */ if (!fu_dell_dock_usb4_mbox_data_write(device, buf, 64, 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_dell_dock_usb4_hub_operation(device, OP_NVM_WRITE, NULL, error)) { g_prefix_error(error, "hub NVM write operation error: "); return FALSE; } buf += 64; length -= 64; fu_progress_set_percentage_full(progress, bytes_done += 64, bytes_total); } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); return TRUE; } static gboolean fu_dell_dock_usb4_activate(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_dell_dock_usb4_hub_operation(device, OP_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 gboolean fu_dell_dock_usb4_write_fw(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const guint8 *fw_buf; gsize fw_blob_size = 0; guchar nvm_buf[NVM_READ_LENGTH] = {0x0}; guint32 fw_header_offset = 0; guint8 *tmp_header = NULL; g_autofree gchar *fw_product_id = NULL; g_autofree gchar *fw_vendor_id = NULL; g_autofree gchar *fw_version = NULL; g_autofree gchar *nvm_product_id = NULL; g_autofree gchar *nvm_vendor_id = NULL; 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 default image */ fw_image = fu_firmware_get_bytes(firmware, error); if (fw_image == NULL) return FALSE; fw_buf = g_bytes_get_data(fw_image, &fw_blob_size); g_debug("total image size: %" G_GSIZE_FORMAT, fw_blob_size); /* get header offset */ tmp_header = (guint8 *)&fw_header_offset; if (!fu_memcpy_safe(tmp_header, sizeof(guint32), 0x0, fw_buf, fw_blob_size, 0x0, sizeof(guint32), error)) return FALSE; g_debug("image header size: %" G_GUINT32_FORMAT, fw_header_offset); if (fw_header_offset > fw_blob_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "image header is too big: %" G_GUINT32_FORMAT, fw_header_offset); return FALSE; } /* get firmware version, vendor-id, product-id */ fw_version = g_strdup_printf("%02x.%02x", fw_buf[fw_header_offset + NVM_VER_OFFSET_MAJOR], fw_buf[fw_header_offset + NVM_VER_OFFSET_MINOR]); fw_vendor_id = g_strdup_printf("%02x%02x", fw_buf[fw_header_offset + NVM_VID_OFFSET_MAJOR], fw_buf[fw_header_offset + NVM_VID_OFFSET_MINOR]); fw_product_id = g_strdup_printf("%02x%02x", fw_buf[fw_header_offset + NVM_PID_OFFSET_MAJOR], fw_buf[fw_header_offset + NVM_PID_OFFSET_MINOR]); g_debug("writing Thunderbolt firmware version %s", fw_version); g_debug("writing Thunderbolt product-id %s", fw_product_id); g_debug("writing Thunderbolt vendor-id %s", fw_vendor_id); /* compare vendor-id, product-id between firmware blob and NVM */ if (!fu_dell_dock_usb4_hub_nvm_read(device, nvm_buf, NVM_READ_LENGTH, 0, error)) { g_prefix_error(error, "NVM READ error: "); return FALSE; } nvm_vendor_id = g_strdup_printf("%02x%02x", nvm_buf[NVM_VID_OFFSET_MAJOR], nvm_buf[NVM_VID_OFFSET_MINOR]); nvm_product_id = g_strdup_printf("%02x%02x", nvm_buf[NVM_PID_OFFSET_MAJOR], nvm_buf[NVM_PID_OFFSET_MINOR]); if (((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) && (g_strcmp0(nvm_vendor_id, fw_vendor_id) != 0 || g_strcmp0(nvm_product_id, fw_product_id) != 0)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Thunderbolt firmware vendor_id %s, product_id %s" "doesn't match NVM vendor_id %s, product_id %s", fw_vendor_id, fw_product_id, nvm_vendor_id, nvm_product_id); return FALSE; } /* firmware install */ fw_buf += fw_header_offset; fw_blob_size -= fw_header_offset; if (!fu_dell_dock_usb4_hub_nvm_write(device, fw_buf, fw_blob_size, 0, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_version(device, fw_version); return TRUE; } static gboolean fu_dell_dock_usb4_setup(FuDevice *device, GError **error) { guchar buf[NVM_READ_LENGTH] = {0x0}; g_autofree gchar *name = NULL; g_autofree gchar *nvm_product_id = NULL; g_autofree gchar *nvm_vendor_id = NULL; g_autofree gchar *nvm_version = NULL; if (!fu_dell_dock_usb4_hub_nvm_read(device, buf, NVM_READ_LENGTH, 0, error)) { g_prefix_error(error, "NVM READ error: "); return FALSE; } nvm_version = g_strdup_printf("%02x.%02x", buf[NVM_VER_OFFSET_MAJOR], buf[NVM_VER_OFFSET_MINOR]), nvm_vendor_id = g_strdup_printf("%02x%02x", buf[NVM_VID_OFFSET_MAJOR], buf[NVM_VID_OFFSET_MINOR]); nvm_product_id = g_strdup_printf("%02x%02x", buf[NVM_PID_OFFSET_MAJOR], buf[NVM_PID_OFFSET_MINOR]); /* only add known supported thunderbolt devices */ name = g_strdup_printf("TBT-%s%s", nvm_vendor_id, nvm_product_id); if (g_strcmp0(name, DELL_DOCK_USB4_INSTANCE_ID) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no supported device found"); return FALSE; } fu_device_add_instance_id(device, name); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(device, nvm_version); return TRUE; } static gboolean fu_dell_dock_usb4_probe(FuDevice *device, GError **error) { FuDellDockUsb4 *self = FU_DELL_DOCK_USB4(device); self->intf_nr = GR_USB_INTERFACE_NUMBER; self->blocksz = GR_USB_BLOCK_SIZE; fu_device_set_logical_id(FU_DEVICE(device), "usb4"); return TRUE; } static void fu_dell_dock_usb4_finalize(GObject *object) { G_OBJECT_CLASS(fu_dell_dock_usb4_parent_class)->finalize(object); } static void fu_dell_dock_usb4_init(FuDellDockUsb4 *self) { fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt"); 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_INHERIT_ACTIVATION); } static void fu_dell_dock_usb4_class_init(FuDellDockUsb4Class *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_dell_dock_usb4_finalize; klass_device->probe = fu_dell_dock_usb4_probe; klass_device->setup = fu_dell_dock_usb4_setup; klass_device->write_firmware = fu_dell_dock_usb4_write_fw; klass_device->activate = fu_dell_dock_usb4_activate; } /** * fu_dell_dock_usb4_new: * * Creates a new USB4 device object. * * Returns: a new #FuDellDockUsb4 **/ FuDellDockUsb4 * fu_dell_dock_usb4_new(FuUsbDevice *device) { FuDellDockUsb4 *self = g_object_new(FU_TYPE_DELL_DOCK_USB4, NULL); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return FU_DELL_DOCK_USB4(self); } fwupd-1.7.5/plugins/dell-dock/fu-dell-dock-usb-usb4.h000066400000000000000000000014541420024370600222530ustar00rootroot00000000000000/* * 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 "config.h" #include #define FU_TYPE_DELL_DOCK_USB4 (fu_dell_dock_usb4_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockUsb4, fu_dell_dock_usb4, FU, DELL_DOCK_USB4, FuUsbDevice) FuDellDockUsb4 * fu_dell_dock_usb4_new(FuUsbDevice *device); fwupd-1.7.5/plugins/dell-dock/fu-plugin-dell-dock.c000066400000000000000000000257511420024370600221060ustar00rootroot00000000000000/* * 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" static void fu_plugin_dell_dock_init(FuPlugin *plugin) { 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 gboolean fu_plugin_dell_dock_create_node(FuPlugin *plugin, FuDevice *device, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuDeviceLocker) locker = NULL; fu_device_set_context(device, ctx); locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, device); return TRUE; } static gboolean fu_plugin_dell_dock_probe(FuPlugin *plugin, FuDevice *proxy, GError **error) { const gchar *instance; 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_plugin_dell_dock_create_node(plugin, FU_DEVICE(ec_device), error)) return FALSE; /* create mst endpoint */ mst_device = fu_dell_dock_mst_new(); if (fu_dell_dock_get_ec_type(FU_DEVICE(ec_device)) == ATOMIC_BASE) instance = DELL_DOCK_VMM6210_INSTANCE_ID; else instance = DELL_DOCK_VM5331_INSTANCE_ID; fu_device_set_context(FU_DEVICE(mst_device), ctx); fu_device_add_guid(FU_DEVICE(mst_device), fwupd_guid_hash_string(instance)); fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(mst_device)); fu_device_add_instance_id(FU_DEVICE(mst_device), instance); if (!fu_plugin_dell_dock_create_node(plugin, FU_DEVICE(mst_device), error)) return FALSE; /* create package version endpoint */ status_device = fu_dell_dock_status_new(); if (fu_dell_dock_get_ec_type(FU_DEVICE(ec_device)) == ATOMIC_BASE) instance = DELL_DOCK_ATOMIC_STATUS_INSTANCE_ID; else if (fu_dell_dock_module_is_usb4(FU_DEVICE(ec_device))) instance = DELL_DOCK_DOCK2_INSTANCE_ID; else instance = DELL_DOCK_DOCK1_INSTANCE_ID; fu_device_set_context(FU_DEVICE(status_device), ctx); fu_device_add_guid(FU_DEVICE(status_device), fwupd_guid_hash_string(instance)); fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(status_device)); fu_device_add_instance_id(FU_DEVICE(status_device), instance); if (!fu_plugin_dell_dock_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); fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(tbt_device)); if (!fu_plugin_dell_dock_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_plugin_dell_dock_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_plugin_dell_dock_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDellDockHub) hub = NULL; const gchar *key = NULL; GPtrArray *devices; FuDevice *ec_device; guint device_vid; guint device_pid; /* not interesting */ if (!FU_IS_USB_DEVICE(device)) return TRUE; device_vid = (guint)fu_usb_device_get_vid(FU_USB_DEVICE(device)); device_pid = (guint)fu_usb_device_get_pid(FU_USB_DEVICE(device)); g_debug("%s: processing usb device, vid: 0x%x, pid: 0x%x", fu_plugin_get_name(plugin), device_vid, device_pid); /* GR controller internal USB HUB */ if (device_vid == GR_USB_VID && device_pid == GR_USB_PID) { g_autoptr(FuDellDockUsb4) usb4_dev = NULL; usb4_dev = fu_dell_dock_usb4_new(FU_USB_DEVICE(device)); locker = fu_device_locker_new(FU_DEVICE(usb4_dev), error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(usb4_dev)); 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; if (fu_device_has_private_flag(FU_DEVICE(hub), FU_DELL_DOCK_HUB_FLAG_HAS_BRIDGE)) { /* only add the device with parent to cache */ key = fu_device_get_id(FU_DEVICE(hub)); if (fu_plugin_cache_lookup(plugin, key) != NULL) { g_debug("Ignoring already added device %s", key); return TRUE; } /* probe for extended devices */ if (!fu_plugin_dell_dock_probe(plugin, FU_DEVICE(hub), error)) return FALSE; fu_plugin_cache_add(plugin, key, FU_DEVICE(hub)); } /* add hub instance id after ec probed */ devices = fu_plugin_get_devices(plugin); ec_device = fu_plugin_dell_dock_get_ec(devices); if (ec_device != NULL) { guint8 ec_type = fu_dell_dock_get_ec_type(ec_device); fu_dell_dock_hub_add_instance(FU_DEVICE(hub), ec_type); } fu_plugin_device_add(plugin, FU_DEVICE(hub)); return TRUE; } static void fu_plugin_dell_dock_separate_activation(FuPlugin *plugin) { GPtrArray *devices = fu_plugin_get_devices(plugin); FuDevice *device_ec = NULL; FuDevice *device_usb4 = NULL; for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (FU_IS_DELL_DOCK_EC(device_tmp)) device_ec = device_tmp; else if (FU_IS_DELL_DOCK_USB4(device_tmp)) device_usb4 = device_tmp; } /* 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_debug("activate for %s is inhibited by %s", fu_device_get_name(device_ec), fu_device_get_name(device_usb4)); } } } static void fu_plugin_dell_dock_device_registered(FuPlugin *plugin, FuDevice *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, "usb4-blocked", msg); return; } /* online activation is mutually exclusive between usb4 and ec */ if (g_strcmp0(fu_device_get_plugin(device), "dell_dock") == 0 && (FU_IS_DELL_DOCK_EC(device) || FU_IS_DELL_DOCK_USB4(device))) fu_plugin_dell_dock_separate_activation(plugin); } static gboolean fu_plugin_dell_dock_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_plugin_dell_dock_composite_prepare(FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_plugin_dell_dock_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_plugin_dell_dock_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_plugin_dell_dock_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), "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_plugin_dell_dock_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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_dell_dock_init; vfuncs->device_registered = fu_plugin_dell_dock_device_registered; vfuncs->backend_device_added = fu_plugin_dell_dock_backend_device_added; vfuncs->backend_device_removed = fu_plugin_dell_dock_backend_device_removed; vfuncs->composite_cleanup = fu_plugin_dell_dock_composite_cleanup; vfuncs->composite_prepare = fu_plugin_dell_dock_composite_prepare; } fwupd-1.7.5/plugins/dell-dock/meson.build000066400000000000000000000013551420024370600203340ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginDellDock"'] install_data(['dell-dock.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_dell_dock', fu_hash, sources : [ 'fu-plugin-dell-dock.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', 'fu-dell-dock-usb-usb4.c' ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, gudev, ], ) endif fwupd-1.7.5/plugins/dell-esrt/000077500000000000000000000000001420024370600162235ustar00rootroot00000000000000fwupd-1.7.5/plugins/dell-esrt/README.md000066400000000000000000000024771420024370600175140ustar00rootroot00000000000000# Dell ESRT Support ## Introduction This allows enabling the BIOS setup option for UEFI capsule updates without manually going into BIOS setup. ## GUID Generation These device uses a hardcoded GUID of `2d47f29b-83a2-4f31-a2e8-63474f4d4c2e`. ## Vendor ID Security The vendor ID is hardcoded to `PCI:0x1028`. ## Build Requirements For Dell support you will need libsmbios_c version 2.4.0 or later. * [source](https://github.com/dell/libsmbios) * [binaries](https://github.com/dell/libsmbios/releases) If you don't want or need this functionality you can use the `-Dplugin_dell=false` option. ## Devices powered by the Dell Plugin The Dell ESRT plugin allows the user to enable the UpdateCapsule functionality at runtime. A reboot will be required and the BIOS administrator password must not be set. Machines that offer this functionality will display an extra device in `fwupdmgr get-devices` output. Example: ```text UEFI dummy device Guid: 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e Plugin: dell-esrt Flags: internal|updatable|locked Version: 0 Created: 2018-06-25 ``` ## External Interface Access This plugin requires: * read/write access to `/dev/wmi/dell-smbios` and `/sys/bus/platform/devices/dcdbas`. * read access to `/sys/firmware/efi/esrt`. fwupd-1.7.5/plugins/dell-esrt/dell-esrt.conf000066400000000000000000000003661420024370600207720ustar00rootroot00000000000000[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.7.5/plugins/dell-esrt/fu-plugin-dell-esrt.c000066400000000000000000000120051420024370600221640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2017 Dell, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include /* allowed smbios class/select commands */ #define CLASS_ADMIN_PROP 10 #define SELECT_ADMIN_PROP 3 /* allowed tokens */ #define CAPSULE_EN_TOKEN 0x0461 #define CAPSULE_DIS_TOKEN 0x0462 /* these aren't defined upstream but used in fwupdate */ #define DELL_ADMIN_MASK 0xF #define DELL_ADMIN_INSTALLED 0 static gboolean fu_plugin_dell_esrt_query_token(guint16 token, gboolean *value, GError **error) { if (!token_is_bool(token)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "token %" G_GUINT16_FORMAT " is not boolean", token); return FALSE; } if (value != NULL) *value = token_is_active(token) > 0; return TRUE; } static gboolean fu_plugin_dell_esrt_activate_token(guint16 token, GError **error) { token_activate(token); if (token_is_active(token) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "token %" G_GUINT16_FORMAT "cannot be activated " "as the password is set", token); return FALSE; } return TRUE; } static gboolean fu_plugin_dell_esrt_admin_password_present(gboolean *password_present, GError **error) { guint32 args[4] = { 0, }, out[4] = { 0, }; if (dell_simple_ci_smi(CLASS_ADMIN_PROP, SELECT_ADMIN_PROP, args, out)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot call SMI for CLASS_ADMIN_PROP"); return FALSE; } if (out[0] != 0 || (out[1] & DELL_ADMIN_MASK) == DELL_ADMIN_INSTALLED) { *password_present = TRUE; } else { *password_present = FALSE; } return TRUE; } static void fu_plugin_dell_esrt_init(FuPlugin *plugin) { fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_BETTER_THAN, "bios"); } static gboolean fu_plugin_dell_esrt_startup(FuPlugin *plugin, GError **error) { gboolean capsule_disable = FALSE; g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; /* already exists */ sysfsfwdir = fu_common_get_path(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_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware already supported"); return FALSE; } /* is the capsule functionality disabled */ if (!fu_plugin_dell_esrt_query_token(CAPSULE_DIS_TOKEN, &capsule_disable, error)) return FALSE; if (!capsule_disable) { gboolean capsule_enable = FALSE; if (!fu_plugin_dell_esrt_query_token(CAPSULE_EN_TOKEN, &capsule_enable, error)) return FALSE; if (capsule_enable) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware will be unlocked on next boot"); return FALSE; } } return TRUE; } static gboolean fu_plugin_dell_esrt_unlock(FuPlugin *plugin, FuDevice *device, GError **error) { gboolean password_present = FALSE; /* check the admin password isn't set */ if (!fu_plugin_dell_esrt_admin_password_present(&password_present, error)) return FALSE; if (password_present) { const gchar *err_string = "Cannot be unlocked automatically as admin password set"; g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, err_string); fu_device_set_update_error(device, err_string); return FALSE; } /* disabled in BIOS, but supported to be enabled via tool */ if (!fu_plugin_dell_esrt_query_token(CAPSULE_EN_TOKEN, NULL, error)) return FALSE; if (!fu_plugin_dell_esrt_activate_token(CAPSULE_EN_TOKEN, error)) return FALSE; fu_device_set_update_error(device, NULL); return TRUE; } static gboolean fu_plugin_dell_esrt_coldplug(FuPlugin *plugin, GError **error) { g_autoptr(FuDevice) dev = fu_device_new(); /* create a dummy device so we can unlock the feature */ fu_device_set_id(dev, "UEFI-dummy"); fu_device_set_name(dev, "Dell UEFI updates"); fu_device_set_summary(dev, "UEFI update functionality"); fu_device_add_vendor_id(dev, "PCI:0x1028"); fu_device_add_instance_id(dev, "main-system-firmware"); fu_device_add_guid(dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version_format(dev, FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_version(dev, "0"); fu_device_add_icon(dev, "computer"); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_update_error(dev, "Firmware updates disabled; run 'fwupdmgr unlock' to enable"); if (!fu_device_setup(dev, error)) return FALSE; fu_plugin_device_add(plugin, dev); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_dell_esrt_init; vfuncs->startup = fu_plugin_dell_esrt_startup; vfuncs->coldplug = fu_plugin_dell_esrt_coldplug; vfuncs->unlock = fu_plugin_dell_esrt_unlock; } fwupd-1.7.5/plugins/dell-esrt/meson.build000066400000000000000000000014531420024370600203700ustar00rootroot00000000000000if get_option('plugin_dell') cargs = ['-DG_LOG_DOMAIN="FuPluginDellEsrt"'] install_data(['metadata.xml'], install_dir : join_paths(datadir, 'fwupd', 'remotes.d', 'dell-esrt') ) shared_module('fu_plugin_dell_esrt', fu_hash, sources : [ 'fu-plugin-dell-esrt.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, libsmbios_c, ], ) # replace @datadir@ con2 = configuration_data() con2.set('datadir', datadir) configure_file( input : 'dell-esrt.conf', output : 'dell-esrt.conf', configuration : con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) endif fwupd-1.7.5/plugins/dell-esrt/metadata.xml000066400000000000000000000016461420024370600205340ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e UEFI Updates Enable UEFI Update Functionality

    Applying this update will enable the UEFI firmware reporting interface on your hardware.

    You will have to restart your computer after this update is installed to be notified of any pending firmware updates.

    fwupd-1.7.5/plugins/dell/000077500000000000000000000000001420024370600152505ustar00rootroot00000000000000fwupd-1.7.5/plugins/dell/README.md000066400000000000000000000150351420024370600165330ustar00rootroot00000000000000# Dell Support ## 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 ``` ## Vendor ID Security The vendor ID is hardcoded to `TPM:DELL`. ## Build Requirements For Dell support you will need libsmbios_c version 2.4.0 or later. * [source](https://github.com/dell/libsmbios) * [binaries](https://github.com/dell/libsmbios/releases) If you don't want or need this functionality you can use the `-Dplugin_dell=false` option. ## Devices powered by the Dell Plugin The Dell plugin creates device nodes for PC's that have switchable TPMs as well as the Type-C docks (WD15/TB16). 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. ## Switchable TPM Devices Machines with switchable TPMs can operate in both TPM 1.2 and TPM 2.0 modes. Switching modes will require flashing an alternative firmware and clearing the contents of the TPM. Machines that offer this functionality will display two devices in `fwupdmgr get-devices` output. Example (from a *Precision 5510*): ```text Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|allow-offline|require-ac Version: 5.81.0.0 Created: 2016-07-19 Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|require-ac|locked Created: 2016-07-19 ``` In this example, the TPM is currently operating in **TPM 1.2 mode**. Any firmware updates posted to *LVFS* for TPM 1.2 mode will be applied. ### Switching TPM Modes In order to be offered to switch the TPM to **TPM 2.0 mode**, the virtual device representing the *TPM 2.0 mode* will need to be unlocked. ```# fwupdmgr unlock DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu``` If the TPM is currently *owned*, an error will be displayed such as this one: ERROR: Precision 5510 TPM 1.2 is currently OWNED. Ownership must be removed to switch modes. TPM Ownership can be cleared from within the BIOS setup menus. If the unlock process was successful, then the devices will be modified: ```text Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|require-ac Version: 5.81.0.0 Created: 2016-07-19 Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|allow-offline|require-ac Version: 0.0.0.0 Created: 2016-07-19 Modified: 2016-07-19 ``` Now the firmware for TPM 2.0 mode can be pulled down from LVFS and flashed: ```shell # fwupdmgr update ``` Upon the next reboot, the new TPM firmware will be flashed. If the firmware is *not offered from LVFS*, then switching modes may not work on this machine. After updating the output from ```# fwupdmgr get-devices``` will reflect the new mode. ```text Precision 5510 TPM 2.0 Guid: 475d9bbd-1b7a-554e-8ca7-54985174a962 DeviceID: DELL-475d9bbd-1b7a-554e-8ca7-54985174a962lu Plugin: dell Flags: internal|allow-offline|require-ac Version: 1.3.0.1 Created: 2016-07-20 Precision 5510 TPM 1.2 Guid: b2088ba1-51ae-514e-8f0a-64756c6e4ffc DeviceID: DELL-b2088ba1-51ae-514e-8f0a-64756c6e4ffclu Plugin: dell Flags: internal|require-ac|locked Created: 2016-07-20 ``` Keep in mind that **TPM 1.2** and **TPM 2.0** will require different userspace tools. ## Dock Devices The *TB16* and *WD15* have a variety of updatable components. Each component will create a virtual device in ```# fwupdmgr get-devices``` For example the WD15 will display these components: ```text Dell WD15 Port Controller 1 Guid: 8ba2b709-6f97-47fc-b7e7-6a87b578fe25 DeviceID: DELL-8ba2b709-6f97-47fc-b7e7-6a87b578fe25lu Plugin: dell Flags: allow-offline|require-ac Version: 0.1.1.8 Created: 2016-07-19 Dell WD15 Guid: e7ca1f36-bf73-4574-afe6-a4ccacabf479 DeviceID: DELL-e7ca1f36-bf73-4574-afe6-a4ccacabf479lu Plugin: dell Flags: allow-offline|require-ac Version: 0.0.0.67 Created: 2016-07-19 ``` Components that can be updated via UEFI capsule will have the `allow-offline` moniker applied. These updates can be performed the standard method of using `fwupdmgr update`. Some components are updatable via other plugins in fwupd such as multi stream transport hub (MST) and thunderbolt NVM. ## External Interface Access This plugin requires read/write access to `/dev/wmi/dell-smbios` and `/sys/bus/platform/devices/dcdbas`. fwupd-1.7.5/plugins/dell/dell.quirk000066400000000000000000000014621420024370600172500ustar00rootroot00000000000000# Realtek NIC in Dell docks [USB\VID_0BDA&PID_8153] Plugin = dell # 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.7.5/plugins/dell/fu-dell-smi.c000066400000000000000000000112631420024370600175350ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-dell-smi.h" /* These are for dock query capabilities */ struct dock_count_in { guint32 argument; guint32 reserved1; guint32 reserved2; guint32 reserved3; }; struct dock_count_out { guint32 ret; guint32 count; guint32 location; guint32 reserved; }; static void _dell_smi_obj_free(FuDellSmiObj *obj) { dell_smi_obj_free(obj->smi); g_free(obj); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuDellSmiObj, _dell_smi_obj_free); #pragma clang diagnostic pop /* don't actually clear if we're testing */ gboolean fu_dell_clear_smi(FuDellSmiObj *obj) { if (obj->fake_smbios) return TRUE; for (gint i = 0; i < 4; i++) { obj->input[i] = 0; obj->output[i] = 0; } return TRUE; } gboolean fu_dell_execute_smi(FuDellSmiObj *obj) { gint ret; if (obj->fake_smbios) return TRUE; ret = dell_smi_obj_execute(obj->smi); if (ret != 0) { g_debug("SMI execution failed: %i", ret); return FALSE; } return TRUE; } guint32 fu_dell_get_res(FuDellSmiObj *smi_obj, guint8 arg) { if (smi_obj->fake_smbios) return smi_obj->output[arg]; return dell_smi_obj_get_res(smi_obj->smi, arg); } gboolean fu_dell_execute_simple_smi(FuDellSmiObj *obj, guint16 class, guint16 select) { /* test suite will mean don't actually call */ if (obj->fake_smbios) return TRUE; if (dell_simple_ci_smi(class, select, obj->input, obj->output)) { g_debug("failed to run query %u/%u", class, select); return FALSE; } return TRUE; } gboolean fu_dell_detect_dock(FuDellSmiObj *smi_obj, guint32 *location) { struct dock_count_in *count_args; struct dock_count_out *count_out; /* look up dock count */ count_args = (struct dock_count_in *)smi_obj->input; count_out = (struct dock_count_out *)smi_obj->output; if (!fu_dell_clear_smi(smi_obj)) { g_debug("failed to clear SMI buffers"); return FALSE; } count_args->argument = DACI_DOCK_ARG_COUNT; if (!fu_dell_execute_simple_smi(smi_obj, DACI_DOCK_CLASS, DACI_DOCK_SELECT)) return FALSE; if (count_out->ret != 0) { g_debug("Failed to query system for dock count: " "(%" G_GUINT32_FORMAT ")", count_out->ret); return FALSE; } if (count_out->count < 1) { g_debug("no dock plugged in"); return FALSE; } if (location != NULL) *location = count_out->location; return TRUE; } gboolean fu_dell_query_dock(FuDellSmiObj *smi_obj, DOCK_UNION *buf) { gint result; guint32 location; guint buf_size; if (!fu_dell_detect_dock(smi_obj, &location)) return FALSE; fu_dell_clear_smi(smi_obj); /* look up more information on dock */ if (smi_obj->fake_smbios) buf->buf = smi_obj->fake_buffer; else { dell_smi_obj_set_class(smi_obj->smi, DACI_DOCK_CLASS); dell_smi_obj_set_select(smi_obj->smi, DACI_DOCK_SELECT); dell_smi_obj_set_arg(smi_obj->smi, cbARG1, DACI_DOCK_ARG_INFO); dell_smi_obj_set_arg(smi_obj->smi, cbARG2, location); buf_size = sizeof(DOCK_INFO_RECORD); buf->buf = dell_smi_obj_make_buffer_frombios_auto(smi_obj->smi, cbARG3, buf_size); if (!buf->buf) { g_debug("Failed to initialize buffer"); return FALSE; } } if (!fu_dell_execute_smi(smi_obj)) return FALSE; result = fu_dell_get_res(smi_obj, cbARG1); if (result != SMI_SUCCESS) { if (result == SMI_INVALID_BUFFER) { g_debug("Invalid buffer size, needed %" G_GUINT32_FORMAT, fu_dell_get_res(smi_obj, cbARG2)); } else { g_debug("SMI execution returned error: %d", result); } return FALSE; } return TRUE; } const gchar * fu_dell_get_dock_type(guint8 type) { g_autoptr(FuDellSmiObj) smi_obj = NULL; DOCK_UNION buf; /* not yet initialized, look it up */ if (type == DOCK_TYPE_NONE) { smi_obj = g_malloc0(sizeof(FuDellSmiObj)); smi_obj->smi = dell_smi_factory(DELL_SMI_DEFAULTS); if (!fu_dell_query_dock(smi_obj, &buf)) return NULL; type = buf.record->dock_info_header.dock_type; } switch (type) { case DOCK_TYPE_TB16: return "TB16"; case DOCK_TYPE_WD15: return "WD15"; default: g_debug("Dock type %d unknown", type); } return NULL; } gboolean fu_dell_toggle_dock_mode(FuDellSmiObj *smi_obj, guint32 new_mode, guint32 dock_location, GError **error) { /* Put into mode to accept AR/MST */ fu_dell_clear_smi(smi_obj); smi_obj->input[0] = DACI_DOCK_ARG_MODE; smi_obj->input[1] = dock_location; smi_obj->input[2] = new_mode; if (!fu_dell_execute_simple_smi(smi_obj, DACI_DOCK_CLASS, DACI_DOCK_SELECT)) return FALSE; if (smi_obj->output[1] != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Failed to set dock flash mode: %u", smi_obj->output[1]); return FALSE; } return TRUE; } fwupd-1.7.5/plugins/dell/fu-dell-smi.h000066400000000000000000000053641420024370600175470ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include typedef struct { struct dell_smi_obj *smi; guint32 input[4]; guint32 output[4]; gboolean fake_smbios; guint8 *fake_buffer; } FuDellSmiObj; /* Dock Info version 1 */ #pragma pack(1) #define MAX_COMPONENTS 5 typedef struct _COMPONENTS { gchar description[80]; guint32 fw_version; /* BCD format: 0x00XXYYZZ */ } COMPONENTS; typedef struct _DOCK_INFO { gchar dock_description[80]; guint32 flash_pkg_version; /* BCD format: 0x00XXYYZZ */ guint32 cable_type; /* bit0-7 cable type, bit7-31 set to 0 */ guint8 location; /* Location of the dock */ guint8 reserved; guint8 component_count; COMPONENTS components[MAX_COMPONENTS]; /* number of component_count */ } DOCK_INFO; typedef struct _DOCK_INFO_HEADER { guint8 dir_version; /* version 1, 2 … */ guint8 dock_type; guint16 reserved; } DOCK_INFO_HEADER; typedef struct _DOCK_INFO_RECORD { DOCK_INFO_HEADER dock_info_header; /* dock version specific definition */ DOCK_INFO dock_info; } DOCK_INFO_RECORD; typedef union _DOCK_UNION { guint8 *buf; DOCK_INFO_RECORD *record; } DOCK_UNION; #pragma pack() typedef enum _DOCK_TYPE { DOCK_TYPE_NONE, DOCK_TYPE_TB16, DOCK_TYPE_WD15 } DOCK_TYPE; typedef enum _CABLE_TYPE { CABLE_TYPE_NONE, CABLE_TYPE_LEGACY, CABLE_TYPE_UNIV, CABLE_TYPE_TBT } CABLE_TYPE; gboolean fu_dell_clear_smi(FuDellSmiObj *obj); guint32 fu_dell_get_res(FuDellSmiObj *smi_obj, guint8 arg); gboolean fu_dell_execute_smi(FuDellSmiObj *obj); gboolean fu_dell_execute_simple_smi(FuDellSmiObj *obj, guint16 class, guint16 select); gboolean fu_dell_detect_dock(FuDellSmiObj *obj, guint32 *location); gboolean fu_dell_query_dock(FuDellSmiObj *smi_obj, DOCK_UNION *buf); const gchar * fu_dell_get_dock_type(guint8 type); gboolean fu_dell_toggle_dock_mode(FuDellSmiObj *smi_obj, guint32 new_mode, guint32 dock_location, GError **error); /* SMI return values used */ #define SMI_SUCCESS 0 #define SMI_INVALID_BUFFER -6 /* These are DACI class/select needed for * flash capability queries */ #define DACI_FLASH_INTERFACE_CLASS 7 #define DACI_FLASH_INTERFACE_SELECT 3 #define DACI_FLASH_ARG_TPM 2 #define DACI_FLASH_ARG_FLASH_MODE 3 #define DACI_FLASH_MODE_USER 0 #define DACI_FLASH_MODE_FLASH 1 /* DACI class/select for dock capabilities */ #define DACI_DOCK_CLASS 17 #define DACI_DOCK_SELECT 22 #define DACI_DOCK_ARG_COUNT 0 #define DACI_DOCK_ARG_INFO 1 #define DACI_DOCK_ARG_MODE 2 #define DACI_DOCK_ARG_MODE_USER 0 #define DACI_DOCK_ARG_MODE_FLASH 1 /* VID/PID of ethernet controller on dock */ #define DOCK_NIC_VID 0x0bda #define DOCK_NIC_PID 0x8153 fwupd-1.7.5/plugins/dell/fu-plugin-dell.c000066400000000000000000000725531420024370600202540ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2016 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #ifdef HAVE_TSS2 #include #endif #include #include "fu-plugin-dell.h" /* These are used to indicate the status of a previous DELL flash */ #define DELL_SUCCESS 0x0000 #define DELL_CONSISTENCY_FAIL 0x0001 #define DELL_FLASH_MEMORY_FAIL 0x0002 #define DELL_FLASH_NOT_READY 0x0003 #define DELL_FLASH_DISABLED 0x0004 #define DELL_BATTERY_MISSING 0x0005 #define DELL_BATTERY_DEAD 0x0006 #define DELL_AC_MISSING 0x0007 #define DELL_CANT_SET_12V 0x0008 #define DELL_CANT_UNSET_12V 0x0009 #define DELL_FAILURE_BLOCK_ERASE 0x000A #define DELL_GENERAL_FAILURE 0x000B #define DELL_DATA_MISCOMPARE 0x000C #define DELL_IMAGE_MISSING 0x000D #define DELL_DID_NOTHING 0xFFFF /* Delay for settling */ #define DELL_FLASH_MODE_DELAY 2 typedef struct _DOCK_DESCRIPTION { const gchar *guid; const gchar *query; const gchar *desc; } DOCK_DESCRIPTION; struct da_structure { guint8 type; guint8 length; guint16 handle; guint16 cmd_address; guint8 cmd_code; guint32 supported_cmds; guint8 *tokens; } __attribute__((packed)); /* These are for matching the components */ #define WD15_EC_STR "2 0 2 2 0" #define TB16_EC_STR "2 0 2 1 0" #define TB16_PC2_STR "2 1 0 1 1" #define TB16_PC1_STR "2 1 0 1 0" #define WD15_PC1_STR "2 1 0 2 0" #define LEGACY_CBL_STR "2 2 2 1 0" #define UNIV_CBL_STR "2 2 2 2 0" #define TBT_CBL_STR "2 2 2 3 0" #define FUTURE_EC_STR "3 0 2 4 0" #define FUTURE_EC_STR2 "4 0 2 4 0" /* supported dock related GUIDs */ #define DOCK_FLASH_GUID "e7ca1f36-bf73-4574-afe6-a4ccacabf479" #define WD15_EC_GUID "e8445370-0211-449d-9faa-107906ab189f" #define TB16_EC_GUID "33cc8870-b1fc-4ec7-948a-c07496874faf" #define TB16_PC2_GUID "1b52c630-86f6-4aee-9f0c-474dc6be49b6" #define TB16_PC1_GUID "8fe183da-c94e-4804-b319-0f1ba5457a69" #define WD15_PC1_GUID "8ba2b709-6f97-47fc-b7e7-6a87b578fe25" #define LEGACY_CBL_GUID "fece1537-d683-4ea8-b968-154530bb6f73" #define UNIV_CBL_GUID "e2bf3aad-61a3-44bf-91ef-349b39515d29" #define TBT_CBL_GUID "6dc832fc-5bb0-4e63-a2ff-02aaba5bc1dc" #define EC_DESC "EC" #define PC1_DESC "Port Controller 1" #define PC2_DESC "Port Controller 2" #define LEGACY_CBL_DESC "Passive Cable" #define UNIV_CBL_DESC "Universal Cable" #define TBT_CBL_DESC "Thunderbolt Cable" /** * Devices that should allow modeswitching */ static guint16 tpm_switch_allowlist[] = { 0x06F2, 0x06F3, 0x06DD, 0x06DE, 0x06DF, 0x06DB, 0x06DC, 0x06BB, 0x06C6, 0x06BA, 0x06B9, 0x05CA, 0x06C7, 0x06B7, 0x06E0, 0x06E5, 0x06D9, 0x06DA, 0x06E4, 0x0704, 0x0720, 0x0730, 0x0758, 0x0759, 0x075B, 0x07A0, 0x079F, 0x07A4, 0x07A5, 0x07A6, 0x07A7, 0x07A8, 0x07A9, 0x07AA, 0x07AB, 0x07B0, 0x07B1, 0x07B2, 0x07B4, 0x07B7, 0x07B8, 0x07B9, 0x07BE, 0x07BF, 0x077A, 0x07CF}; /** * 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); FuPluginData *data = fu_plugin_get_data(plugin); const gchar *system_id_str = NULL; guint16 system_id = 0; gchar *endptr = NULL; /* don't care for test suite */ if (data->smi_obj->fake_smbios) return 0; 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); if (system_id == 0 || endptr == system_id_str) system_id = (guint16)sysinfo_get_dell_system_id(); return system_id; } static gboolean fu_dell_supported(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(GBytes) de_table = NULL; g_autoptr(GBytes) da_table = NULL; g_autoptr(GBytes) enclosure = NULL; const guint8 *value; const struct da_structure *da_values; gsize len; /* make sure that Dell SMBIOS methods are available */ de_table = fu_context_get_smbios_data(ctx, 0xDE); if (de_table == NULL) return FALSE; value = g_bytes_get_data(de_table, &len); if (len == 0) return FALSE; if (*value != 0xDE) return FALSE; da_table = fu_context_get_smbios_data(ctx, 0xDA); if (da_table == NULL) return FALSE; da_values = (struct da_structure *)g_bytes_get_data(da_table, &len); if (len == 0) return FALSE; if (!(da_values->supported_cmds & (1 << DACI_FLASH_INTERFACE_CLASS))) { g_debug("unable to access flash interface. supported commands: 0x%x", da_values->supported_cmds); return FALSE; } /* only run on intended Dell hw types */ enclosure = fu_context_get_smbios_data(ctx, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS); if (enclosure == NULL) return FALSE; value = g_bytes_get_data(enclosure, &len); if (len == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS(enclosure_allowlist); i++) { if (enclosure_allowlist[i] == value[0]) return TRUE; } return FALSE; } static gboolean fu_plugin_dell_match_dock_component(const gchar *query_str, const gchar **guid_out, const gchar **name_out) { const DOCK_DESCRIPTION list[] = { {WD15_EC_GUID, WD15_EC_STR, EC_DESC}, {TB16_EC_GUID, TB16_EC_STR, EC_DESC}, {WD15_PC1_GUID, WD15_PC1_STR, PC1_DESC}, {TB16_PC1_GUID, TB16_PC1_STR, PC1_DESC}, {TB16_PC2_GUID, TB16_PC2_STR, PC2_DESC}, {TBT_CBL_GUID, TBT_CBL_STR, TBT_CBL_DESC}, {UNIV_CBL_GUID, UNIV_CBL_STR, UNIV_CBL_DESC}, {LEGACY_CBL_GUID, LEGACY_CBL_STR, LEGACY_CBL_DESC}, {NULL, FUTURE_EC_STR, NULL}, {NULL, FUTURE_EC_STR2, NULL}, }; for (guint i = 0; i < G_N_ELEMENTS(list); i++) { if (g_strcmp0(query_str, list[i].query) == 0) { *guid_out = list[i].guid; *name_out = list[i].desc; return TRUE; } } return FALSE; } void fu_plugin_dell_inject_fake_data(FuPlugin *plugin, guint32 *output, guint16 vid, guint16 pid, guint8 *buf, gboolean can_switch_modes) { FuPluginData *data = fu_plugin_get_data(plugin); if (!data->smi_obj->fake_smbios) return; for (guint i = 0; i < 4; i++) data->smi_obj->output[i] = output[i]; data->fake_vid = vid; data->fake_pid = pid; data->smi_obj->fake_buffer = buf; data->can_switch_modes = TRUE; } static gboolean fu_plugin_dell_capsule_supported(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); return data->smi_obj->fake_smbios || data->capsule_supported; } static gboolean fu_plugin_dock_node(FuPlugin *plugin, const gchar *platform, guint8 type, const gchar *component_guid, const gchar *component_desc, const gchar *version, FwupdVersionFormat version_format) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *dock_type; g_autofree gchar *dock_name = NULL; g_autoptr(FuDevice) dev = NULL; dock_type = fu_dell_get_dock_type(type); if (dock_type == NULL) { g_debug("Unknown dock type %d", type); return FALSE; } dev = fu_device_new_with_context(ctx); fu_device_set_physical_id(dev, platform); fu_device_set_logical_id(dev, component_guid); if (component_desc != NULL) { dock_name = g_strdup_printf("Dell %s %s", dock_type, component_desc); fu_device_add_parent_guid(dev, DOCK_FLASH_GUID); } else { dock_name = g_strdup_printf("Dell %s", dock_type); } fu_device_set_vendor(dev, "Dell Inc."); fu_device_add_vendor_id(dev, "PCI:0x1028"); fu_device_set_name(dev, dock_name); fu_device_set_metadata(dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "device-firmware"); if (type == DOCK_TYPE_TB16) { fu_device_set_summary(dev, "Thunderbolt™ 3 docking station"); } else if (type == DOCK_TYPE_WD15) { fu_device_set_summary(dev, "USB type-C docking station"); } fu_device_add_icon(dev, "computer"); fu_device_add_guid(dev, component_guid); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_set_version_format(dev, version_format); if (version != NULL) { fu_device_set_version(dev, version); if (fu_plugin_dell_capsule_supported(plugin)) { fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } } fu_plugin_device_register(plugin, dev); return TRUE; } gboolean fu_plugin_dell_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); FwupdVersionFormat version_format = FWUPD_VERSION_FORMAT_DELL_BIOS; guint16 pid; guint16 vid; const gchar *query_str; const gchar *component_guid = NULL; const gchar *component_name = NULL; const gchar *platform; DOCK_UNION buf; DOCK_INFO *dock_info; gboolean old_ec = FALSE; g_autofree gchar *flash_ver_str = NULL; /* not interesting */ if (!FU_IS_USB_DEVICE(device)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not a USB device"); return FALSE; } /* don't look up immediately if a dock is connected as that would mean a SMI on every USB device that showed up on the system */ if (!data->smi_obj->fake_smbios) { vid = fu_usb_device_get_vid(FU_USB_DEVICE(device)); pid = fu_usb_device_get_pid(FU_USB_DEVICE(device)); platform = fu_device_get_physical_id(FU_DEVICE(device)); } else { vid = data->fake_vid; pid = data->fake_pid; platform = "fake"; } /* we're going to match on the Realtek NIC in the dock */ if (vid != DOCK_NIC_VID || pid != DOCK_NIC_PID) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrong VID/PID %04x:%04x", vid, pid); return FALSE; } buf.buf = NULL; if (!fu_dell_query_dock(data->smi_obj, &buf)) { g_debug("no dock detected"); return TRUE; } if (buf.record->dock_info_header.dir_version != 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dock info header version unknown %d", buf.record->dock_info_header.dir_version); return FALSE; } dock_info = &buf.record->dock_info; g_debug("Dock description: %s", dock_info->dock_description); /* Note: fw package version is deprecated, look at components instead */ g_debug("Dock flash pkg ver: 0x%x", dock_info->flash_pkg_version); if (dock_info->flash_pkg_version == 0x00ffffff) g_debug("WARNING: dock flash package version invalid"); g_debug("Dock cable type: %" G_GUINT32_FORMAT, dock_info->cable_type); g_debug("Dock location: %d", dock_info->location); g_debug("Dock component count: %d", dock_info->component_count); for (guint i = 0; i < dock_info->component_count; i++) { g_autofree gchar *fw_str = NULL; if (i >= MAX_COMPONENTS) { g_debug("Too many components. Invalid: #%u", i); break; } g_debug("Dock component %u: %s (version 0x%x)", i, dock_info->components[i].description, dock_info->components[i].fw_version); query_str = g_strrstr(dock_info->components[i].description, "Query "); if (query_str == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dock component request"); return FALSE; } if (!fu_plugin_dell_match_dock_component(query_str + 6, &component_guid, &component_name)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dock component request %s", query_str); return FALSE; } if (component_guid == NULL || component_name == NULL) { g_debug("%s is supported by another plugin", query_str); return TRUE; } /* dock EC hasn't been updated for first time */ if (dock_info->flash_pkg_version == 0x00ffffff) { old_ec = TRUE; dock_info->flash_pkg_version = 0; continue; } /* if invalid version, don't mark device for updates */ else if (dock_info->components[i].fw_version == 0 || dock_info->components[i].fw_version == 0xffffffff) { old_ec = TRUE; continue; } fw_str = fu_common_version_from_uint32(dock_info->components[i].fw_version, version_format); if (!fu_plugin_dock_node(plugin, platform, buf.record->dock_info_header.dock_type, component_guid, component_name, fw_str, version_format)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create %s", component_name); return FALSE; } } /* if an old EC or invalid EC version found, create updatable parent */ if (old_ec) flash_ver_str = fu_common_version_from_uint32(dock_info->flash_pkg_version, version_format); if (!fu_plugin_dock_node(plugin, platform, buf.record->dock_info_header.dock_type, DOCK_FLASH_GUID, NULL, flash_ver_str, version_format)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to create top dock node"); return FALSE; } return TRUE; } static gboolean fu_plugin_dell_get_results(FuPlugin *plugin, FuDevice *device, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(GBytes) de_table = NULL; const gchar *tmp = NULL; const guint16 *completion_code; gsize len; de_table = fu_context_get_smbios_data(ctx, 0xDE); completion_code = g_bytes_get_data(de_table, &len); if (len < 8) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ERROR: Unable to read results of %s: %" G_GSIZE_FORMAT " < 8", fu_device_get_name(device), len); return FALSE; } /* look at byte offset 0x06 for identifier meaning completion code */ if (completion_code[3] == DELL_SUCCESS) { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); } else { FwupdUpdateState update_state = FWUPD_UPDATE_STATE_FAILED; switch (completion_code[3]) { case DELL_CONSISTENCY_FAIL: tmp = "The image failed one or more consistency checks."; break; case DELL_FLASH_MEMORY_FAIL: tmp = "The BIOS could not access the flash-memory device."; break; case DELL_FLASH_NOT_READY: tmp = "The flash-memory device was not ready when an erase was attempted."; break; case DELL_FLASH_DISABLED: tmp = "Flash programming is currently disabled on the system, or the " "voltage is low."; break; case DELL_BATTERY_MISSING: tmp = "A battery must be installed for the operation to complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_BATTERY_DEAD: tmp = "A fully-charged battery must be present for the operation to " "complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_AC_MISSING: tmp = "An external power adapter must be connected for the operation to " "complete."; update_state = FWUPD_UPDATE_STATE_FAILED_TRANSIENT; break; case DELL_CANT_SET_12V: tmp = "The 12V required to program the flash-memory could not be set."; break; case DELL_CANT_UNSET_12V: tmp = "The 12V required to program the flash-memory could not be removed."; break; case DELL_FAILURE_BLOCK_ERASE: tmp = "A flash-memory failure occurred during a block-erase operation."; break; case DELL_GENERAL_FAILURE: tmp = "A general failure occurred during the flash programming."; break; case DELL_DATA_MISCOMPARE: tmp = "A data miscompare error occurred during the flash programming."; break; case DELL_IMAGE_MISSING: tmp = "The image could not be found in memory, i.e. the header could not " "be located."; break; case DELL_DID_NOTHING: tmp = "No update operation has been performed on the system."; break; default: break; } fu_device_set_update_state(device, update_state); if (tmp != NULL) fu_device_set_update_error(device, tmp); } return TRUE; } #ifdef HAVE_TSS2 static void Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context) { Esys_Finalize(&esys_context); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup) static gchar * fu_plugin_dell_get_tpm_capability(ESYS_CONTEXT *ctx, guint32 query) { TSS2_RC rc; guint32 val; gchar result[5] = {'\0'}; g_autofree TPMS_CAPABILITY_DATA *capability = NULL; rc = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_TPM_PROPERTIES, query, 1, NULL, &capability); if (rc != TSS2_RC_SUCCESS) { g_debug("capability request failed for query %x", query); return NULL; } if (capability->data.tpmProperties.count == 0) { g_debug("no properties returned for query %x", query); return NULL; } if (capability->data.tpmProperties.tpmProperty[0].property != query) { g_debug("wrong query returned (got %x expected %x)", capability->data.tpmProperties.tpmProperty[0].property, query); return NULL; } val = GUINT32_FROM_BE(capability->data.tpmProperties.tpmProperty[0].value); 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] != '\0') result[i] = 0x20; } return fu_common_strstrip(result); } #endif static gboolean fu_plugin_dell_add_tpm_model(FuDevice *dev, GError **error) { #ifdef HAVE_TSS2 TSS2_RC rc; const gchar *base = "DELL-TPM"; g_autoptr(ESYS_CONTEXT) ctx = NULL; g_autofree gchar *family = NULL; g_autofree gchar *manufacturer = NULL; g_autofree gchar *vendor1 = NULL; g_autofree gchar *vendor2 = NULL; g_autofree gchar *vendor3 = NULL; g_autofree gchar *vendor4 = NULL; g_autofree gchar *v1 = NULL; g_autofree gchar *v1_v2 = NULL; g_autofree gchar *v1_v2_v3 = NULL; g_autofree gchar *v1_v2_v3_v4 = NULL; rc = Esys_Initialize(&ctx, 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; } rc = Esys_Startup(ctx, 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_plugin_dell_get_tpm_capability(ctx, TPM2_PT_FAMILY_INDICATOR); if (family == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM family"); return FALSE; } manufacturer = fu_plugin_dell_get_tpm_capability(ctx, TPM2_PT_MANUFACTURER); if (manufacturer == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM manufacturer"); return FALSE; } vendor1 = fu_plugin_dell_get_tpm_capability(ctx, TPM2_PT_VENDOR_STRING_1); if (vendor1 == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read TPM vendor string"); return FALSE; } fu_device_set_metadata(dev, "TpmFamily", family); /* these are not guaranteed by spec and may be NULL */ vendor2 = fu_plugin_dell_get_tpm_capability(ctx, TPM2_PT_VENDOR_STRING_2); vendor3 = fu_plugin_dell_get_tpm_capability(ctx, TPM2_PT_VENDOR_STRING_3); vendor4 = fu_plugin_dell_get_tpm_capability(ctx, TPM2_PT_VENDOR_STRING_4); /* add GUIDs to daemon */ v1 = g_strjoin("-", base, family, manufacturer, vendor1, NULL); v1_v2 = g_strconcat(v1, vendor2, NULL); v1_v2_v3 = g_strconcat(v1_v2, vendor3, NULL); v1_v2_v3_v4 = g_strconcat(v1_v2_v3, vendor4, NULL); fu_device_add_instance_id(dev, v1); fu_device_add_instance_id(dev, v1_v2); fu_device_add_instance_id(dev, v1_v2_v3); fu_device_add_instance_id(dev, v1_v2_v3_v4); #endif return TRUE; } gboolean fu_plugin_dell_detect_tpm(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_get_data(plugin); const gchar *tpm_mode; const gchar *tpm_mode_alt; guint16 system_id = 0; gboolean can_switch_modes = FALSE; g_autofree gchar *pretty_tpm_name_alt = NULL; g_autofree gchar *pretty_tpm_name = NULL; g_autofree gchar *tpm_guid_raw_alt = NULL; g_autofree gchar *tpm_guid_alt = NULL; g_autofree gchar *tpm_guid = NULL; g_autofree gchar *tpm_guid_raw = NULL; g_autofree gchar *tpm_id_alt = NULL; g_autofree gchar *version_str = NULL; struct tpm_status *out = NULL; g_autoptr(FuDevice) dev_alt = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_tss = NULL; fu_dell_clear_smi(data->smi_obj); out = (struct tpm_status *)data->smi_obj->output; /* execute TPM Status Query */ data->smi_obj->input[0] = DACI_FLASH_ARG_TPM; if (!fu_dell_execute_simple_smi(data->smi_obj, DACI_FLASH_INTERFACE_CLASS, DACI_FLASH_INTERFACE_SELECT)) return FALSE; if (out->ret != 0) { g_debug("Failed to query system for TPM information: " "(%" G_GUINT32_FORMAT ")", out->ret); return FALSE; } /* HW version is output in second /input/ arg * it may be relevant as next gen TPM is enabled */ g_debug("TPM HW version: 0x%x", data->smi_obj->input[1]); g_debug("TPM Status: 0x%x", out->status); /* test TPM enabled (Bit 0) */ if (!(out->status & TPM_EN_MASK)) { g_debug("TPM not enabled (%x)", out->status); return FALSE; } /* test TPM mode to determine current mode */ if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_1_2_MODE) { tpm_mode = "1.2"; tpm_mode_alt = "2.0"; } else if (((out->status & TPM_TYPE_MASK) >> 8) == TPM_2_0_MODE) { tpm_mode = "2.0"; tpm_mode_alt = "1.2"; } else { g_debug("Unable to determine TPM mode"); return FALSE; } system_id = fu_dell_get_system_id(plugin); if (data->smi_obj->fake_smbios) can_switch_modes = data->can_switch_modes; else if (system_id == 0) return FALSE; for (guint i = 0; i < G_N_ELEMENTS(tpm_switch_allowlist); i++) { if (tpm_switch_allowlist[i] == system_id) { can_switch_modes = TRUE; } } tpm_guid_raw = g_strdup_printf("%04x-%s", system_id, tpm_mode); tpm_guid = fwupd_guid_hash_string(tpm_guid_raw); tpm_guid_raw_alt = g_strdup_printf("%04x-%s", system_id, tpm_mode_alt); tpm_guid_alt = fwupd_guid_hash_string(tpm_guid_raw_alt); tpm_id_alt = g_strdup_printf("DELL-%s" G_GUINT64_FORMAT, tpm_guid_alt); g_debug("Creating primary TPM GUID %s and secondary TPM GUID %s", tpm_guid_raw, tpm_guid_raw_alt); version_str = fu_common_version_from_uint32(out->fw_version, FWUPD_VERSION_FORMAT_QUAD); /* make it clear that the TPM is a discrete device of the product */ pretty_tpm_name = g_strdup_printf("TPM %s", tpm_mode); pretty_tpm_name_alt = g_strdup_printf("TPM %s", tpm_mode_alt); /* build Standard device nodes */ dev = fu_device_new_with_context(ctx); fu_device_set_physical_id(dev, "DEVNAME=/dev/tpm0"); fu_device_set_logical_id(dev, "UEFI"); fu_device_add_instance_id(dev, tpm_guid_raw); fu_device_add_instance_id(dev, "system-tpm"); fu_device_set_vendor(dev, "Dell Inc."); fu_device_add_vendor_id(dev, "PCI:0x1028"); fu_device_set_name(dev, pretty_tpm_name); fu_device_set_summary(dev, "Platform TPM device"); fu_device_set_version_format(dev, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(dev, version_str); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_icon(dev, "computer"); fu_device_set_metadata(dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "dell-tpm-firmware"); if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { if (fu_plugin_dell_capsule_supported(plugin)) { fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } fu_device_set_flashes_left(dev, out->flashes_left); } else { fu_device_set_update_error(dev, "Updating disabled due to TPM ownership"); } /* build GUIDs from TSS strings */ if (!fu_plugin_dell_add_tpm_model(dev, &error_tss)) g_debug("could not build instances: %s", error_tss->message); if (!fu_device_setup(dev, error)) return FALSE; fu_plugin_device_register(plugin, dev); fu_plugin_add_report_metadata(plugin, "TpmFamily", fu_device_get_metadata(dev, "TpmFamily")); /* build alternate device node */ if (can_switch_modes) { dev_alt = fu_device_new_with_context(ctx); fu_device_set_id(dev_alt, tpm_id_alt); fu_device_add_instance_id(dev_alt, tpm_guid_raw_alt); fu_device_set_vendor(dev, "Dell Inc."); fu_device_add_vendor_id(dev, "PCI:0x1028"); fu_device_set_name(dev_alt, pretty_tpm_name_alt); fu_device_set_summary(dev_alt, "Alternate mode for platform TPM device"); fu_device_add_flag(dev_alt, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(dev_alt, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(dev_alt, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_icon(dev_alt, "computer"); fu_device_set_alternate_id(dev_alt, fu_device_get_id(dev)); fu_device_set_metadata(dev_alt, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "dell-tpm-firmware"); fu_device_add_parent_guid(dev_alt, tpm_guid); /* If TPM is not owned and at least 1 flash left allow mode switching * * Mode switching is turned on by setting flashes left on alternate * device. */ if ((out->status & TPM_OWN_MASK) == 0 && out->flashes_left > 0) { fu_device_set_flashes_left(dev_alt, out->flashes_left); } else { fu_device_set_update_error(dev_alt, "mode switch disabled due to TPM ownership"); } if (!fu_device_setup(dev_alt, error)) return FALSE; fu_plugin_device_register(plugin, dev_alt); } else g_debug("System %04x does not offer TPM modeswitching", system_id); return TRUE; } static void fu_plugin_dell_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); } } } static void fu_plugin_dell_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("%d.%d", smbios_get_library_version_major(), smbios_get_library_version_minor()); fu_context_add_runtime_version(ctx, "com.dell.libsmbios", tmp); g_debug("Using libsmbios %s", tmp); data->smi_obj = g_malloc0(sizeof(FuDellSmiObj)); if (g_getenv("FWUPD_DELL_VERBOSE") != NULL) g_setenv("LIBSMBIOS_C_DEBUG_OUTPUT_ALL", "1", TRUE); else g_setenv("TSS2_LOG", "esys+error,tcti+none", FALSE); if (fu_dell_supported(plugin)) data->smi_obj->smi = dell_smi_factory(DELL_SMI_DEFAULTS); data->smi_obj->fake_smbios = FALSE; if (g_getenv("FWUPD_DELL_FAKE_SMBIOS") != NULL) data->smi_obj->fake_smbios = TRUE; /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi_capsule"); /* our TPM device is upgradable! */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_BETTER_THAN, "tpm"); } static void fu_plugin_dell_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->smi_obj->smi) dell_smi_obj_free(data->smi_obj->smi); g_free(data->smi_obj); } static gboolean fu_plugin_dell_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; if (data->smi_obj->fake_smbios) { g_debug("Called with fake SMBIOS implementation. " "We're ignoring test for SBMIOS table and ESRT. " "Individual calls will need to be properly staged."); return TRUE; } if (!fu_dell_supported(plugin)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware updating not supported"); return FALSE; } if (data->smi_obj->smi == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to initialize libsmbios library"); 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_common_get_path(FU_PATH_KIND_SYSFSDIR_FW); esrtdir = g_build_filename(sysfsfwdir, "efi", "esrt", NULL); if (g_file_test(esrtdir, G_FILE_TEST_EXISTS)) data->capsule_supported = TRUE; /* capsules not supported */ if (!fu_plugin_dell_capsule_supported(plugin)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED); } return TRUE; } static gboolean fu_plugin_dell_coldplug(FuPlugin *plugin, GError **error) { /* look for switchable TPM */ if (!fu_plugin_dell_detect_tpm(plugin, error)) g_debug("No switchable TPM detected"); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_dell_init; vfuncs->destroy = fu_plugin_dell_destroy; vfuncs->startup = fu_plugin_dell_startup; vfuncs->coldplug = fu_plugin_dell_coldplug; vfuncs->backend_device_added = fu_plugin_dell_backend_device_added; vfuncs->device_registered = fu_plugin_dell_device_registered; vfuncs->get_results = fu_plugin_dell_get_results; } fwupd-1.7.5/plugins/dell/fu-plugin-dell.h000066400000000000000000000017231420024370600202500ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-dell-smi.h" struct FuPluginData { FuDellSmiObj *smi_obj; guint16 fake_vid; guint16 fake_pid; gboolean can_switch_modes; gboolean capsule_supported; }; void fu_plugin_dell_inject_fake_data(FuPlugin *plugin, guint32 *output, guint16 vid, guint16 pid, guint8 *buf, gboolean can_switch_modes); gboolean fu_plugin_dell_detect_tpm(FuPlugin *plugin, GError **error); gboolean fu_plugin_dell_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error); /* These are nodes that will indicate information about * the TPM status */ struct tpm_status { guint32 ret; guint32 fw_version; guint32 status; guint32 flashes_left; }; #define TPM_EN_MASK 0x0001 #define TPM_OWN_MASK 0x0004 #define TPM_TYPE_MASK 0x0F00 #define TPM_1_2_MODE 0x0001 #define TPM_2_0_MODE 0x0002 fwupd-1.7.5/plugins/dell/fu-self-test.c000066400000000000000000000453231420024370600177410ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-plugin-dell.h" #include "fu-plugin-private.h" typedef struct { FuPlugin *plugin_uefi_capsule; FuPlugin *plugin_dell; } FuTest; static FuDevice * _find_device_by_id(GPtrArray *devices, const gchar *device_id) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (g_strcmp0(fu_device_get_id(device), device_id) == 0) return device; } return NULL; } static FuDevice * _find_device_by_name(GPtrArray *devices, const gchar *device_id) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (g_strcmp0(fu_device_get_name(device), device_id) == 0) return device; } return NULL; } static void _plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray *devices = (GPtrArray *)user_data; if (fu_device_get_alternate_id(device) != NULL) { FuDevice *device_alt = _find_device_by_id(devices, fu_device_get_alternate_id(device)); if (device_alt != NULL) fu_device_set_alternate(device, device_alt); } g_ptr_array_add(devices, g_object_ref(device)); } static void fu_engine_plugin_device_register_cb(FuPlugin *plugin_dell, FuDevice *device, gpointer user_data) { FuPlugin *plugin_uefi_capsule = FU_PLUGIN(user_data); g_autofree gchar *dbg = fu_device_to_string(device); g_debug("registering device: %s", dbg); fu_plugin_runner_device_register(plugin_uefi_capsule, device); } static void fu_plugin_dell_tpm_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FuDevice *device_v12; FuDevice *device_v20; const guint8 fw[30] = {'F', 'W', 0x00}; gboolean ret; gulong added_id; gulong register_id; struct tpm_status tpm_out; const gchar *tpm_server_running = g_getenv("TPM_SERVER_RUNNING"); g_autoptr(GBytes) blob_fw = g_bytes_new_static(fw, sizeof(fw)); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); #ifdef HAVE_GETUID if (tpm_server_running == NULL && (getuid() != 0 || geteuid() != 0)) { g_test_skip("TPM tests require simulated TPM2.0 running or need root access with " "physical TPM"); return; } #endif memset(&tpm_out, 0x0, sizeof(tpm_out)); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); added_id = g_signal_connect(FU_PLUGIN(self->plugin_uefi_capsule), "device-added", G_CALLBACK(_plugin_device_added_cb), devices); register_id = g_signal_connect(FU_PLUGIN(self->plugin_dell), "device-register", G_CALLBACK(fu_engine_plugin_device_register_cb), self->plugin_uefi_capsule); ret = fu_plugin_runner_coldplug(self->plugin_dell, &error); g_assert_no_error(error); g_assert_true(ret); /* inject fake data (no TPM) */ tpm_out.ret = -2; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&tpm_out, 0, 0, NULL, FALSE); ret = fu_plugin_dell_detect_tpm(self->plugin_dell, &error); g_assert_no_error(error); g_assert_false(ret); g_assert_cmpint(devices->len, ==, 0); /* inject fake data: * - that is out of flashes * - no ownership * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.ret = 0; tpm_out.fw_version = 0; tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 0; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm(self->plugin_dell, &error); g_assert_true(ret); g_assert_cmpint(devices->len, ==, 2); /* make sure 2.0 is locked */ device_v20 = _find_device_by_name(devices, "TPM 2.0"); g_assert_nonnull(device_v20); g_assert_true(fu_device_has_flag(device_v20, FWUPD_DEVICE_FLAG_LOCKED)); /* make sure not allowed to flash 1.2 */ device_v12 = _find_device_by_name(devices, "TPM 1.2"); g_assert_nonnull(device_v12); g_assert_false(fu_device_has_flag(device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ ret = fu_plugin_runner_unlock(self->plugin_uefi_capsule, device_v20, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); g_clear_error(&error); /* cleanup */ g_ptr_array_set_size(devices, 0); /* inject fake data: * - that has flashes * - owned * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.status = TPM_EN_MASK | TPM_OWN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 125; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm(self->plugin_dell, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure not allowed to flash 1.2 */ device_v12 = _find_device_by_name(devices, "TPM 1.2"); g_assert_nonnull(device_v12); g_assert_false(fu_device_has_flag(device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ device_v20 = _find_device_by_name(devices, "TPM 2.0"); g_assert_nonnull(device_v20); ret = fu_plugin_runner_unlock(self->plugin_uefi_capsule, device_v20, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); g_clear_error(&error); /* cleanup */ g_ptr_array_set_size(devices, 0); /* inject fake data: * - that has flashes * - not owned * - TPM 1.2 * dev will be the locked 2.0, alt will be the orig 1.2 */ tpm_out.status = TPM_EN_MASK | (TPM_1_2_MODE << 8); tpm_out.flashes_left = 125; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm(self->plugin_dell, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure allowed to flash 1.2 but not 2.0 */ device_v12 = _find_device_by_name(devices, "TPM 1.2"); g_assert_nonnull(device_v12); g_assert_true(fu_device_has_flag(device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); device_v20 = _find_device_by_name(devices, "TPM 2.0"); g_assert_nonnull(device_v20); g_assert_false(fu_device_has_flag(device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); /* try to unlock 2.0 */ ret = fu_plugin_runner_unlock(self->plugin_uefi_capsule, device_v20, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure no longer allowed to flash 1.2 but can flash 2.0 */ g_assert_false(fu_device_has_flag(device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_true(fu_device_has_flag(device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); /* cleanup */ g_ptr_array_set_size(devices, 0); /* inject fake data: * - that has 1 flash left * - not owned * - TPM 2.0 * dev will be the locked 1.2, alt will be the orig 2.0 */ tpm_out.status = TPM_EN_MASK | (TPM_2_0_MODE << 8); tpm_out.flashes_left = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&tpm_out, 0, 0, NULL, TRUE); ret = fu_plugin_dell_detect_tpm(self->plugin_dell, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure allowed to flash 2.0 but not 1.2 */ device_v20 = _find_device_by_name(devices, "TPM 2.0"); g_assert_nonnull(device_v20); g_assert_true(fu_device_has_flag(device_v20, FWUPD_DEVICE_FLAG_UPDATABLE)); device_v12 = _find_device_by_name(devices, "TPM 1.2"); g_assert_nonnull(device_v12); g_assert_false(fu_device_has_flag(device_v12, FWUPD_DEVICE_FLAG_UPDATABLE)); /* With one flash left we need an override */ ret = fu_plugin_runner_write_firmware(self->plugin_uefi_capsule, device_v20, blob_fw, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); g_clear_error(&error); /* test override */ ret = fu_plugin_runner_write_firmware(self->plugin_uefi_capsule, device_v20, blob_fw, progress, FWUPD_INSTALL_FLAG_FORCE, &error); g_assert_no_error(error); g_assert_true(ret); /* all */ g_signal_handler_disconnect(self->plugin_uefi_capsule, added_id); g_signal_handler_disconnect(self->plugin_dell, register_id); } static void fu_plugin_dell_dock_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; guint32 out[4] = {0x0, 0x0, 0x0, 0x0}; DOCK_UNION buf; DOCK_INFO *dock_info; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(FuUsbDevice) fake_usb_device = NULL; gulong added_id; gulong register_id; fake_usb_device = fu_usb_device_new_with_context(fu_plugin_get_context(self->plugin_dell), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); added_id = g_signal_connect(FU_PLUGIN(self->plugin_uefi_capsule), "device-added", G_CALLBACK(_plugin_device_added_cb), devices); register_id = g_signal_connect(FU_PLUGIN(self->plugin_dell), "device-register", G_CALLBACK(fu_engine_plugin_device_register_cb), self->plugin_uefi_capsule); /* make sure bad device doesn't trigger this */ fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, 0x1234, 0x4321, NULL, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), &error); g_assert_false(ret); g_clear_error(&error); g_assert_cmpint(devices->len, ==, 0); /* inject a USB dongle matching correct VID/PID */ out[0] = 0; out[1] = 0; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, NULL, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), &error); g_assert_true(ret); g_clear_error(&error); g_assert_cmpint(devices->len, ==, 0); /* inject valid TB16 dock w/ invalid flash pkg version */ buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_TB16; memcpy(dock_info->dock_description, "BME_Dock", 8); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_TBT; dock_info->location = 2; dock_info->component_count = 4; dock_info->components[0].fw_version = 0x00ffffff; memcpy(dock_info->components[0].description, "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); dock_info->components[1].fw_version = 0x10201; memcpy(dock_info->components[1].description, "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); dock_info->components[2].fw_version = 0x10201; memcpy(dock_info->components[2].description, "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); dock_info->components[3].fw_version = 0x00ffffff; memcpy(dock_info->components[3].description, "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), NULL); g_assert_true(ret); g_assert_cmpint(devices->len, ==, 4); g_ptr_array_set_size(devices, 0); g_free(buf.record); /* inject valid TB16 dock w/ older system EC */ buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_TB16; memcpy(dock_info->dock_description, "BME_Dock", 8); dock_info->flash_pkg_version = 0x43; dock_info->cable_type = CABLE_TYPE_TBT; dock_info->location = 2; dock_info->component_count = 4; dock_info->components[0].fw_version = 0xffffffff; memcpy(dock_info->components[0].description, "Dock1,EC,MIPS32,BME_Dock,0 :Query 2 0 2 1 0", 43); dock_info->components[1].fw_version = 0x10211; memcpy(dock_info->components[1].description, "Dock1,PC,TI,BME_Dock,0 :Query 2 1 0 1 0", 39); dock_info->components[2].fw_version = 0x10212; memcpy(dock_info->components[2].description, "Dock1,PC,TI,BME_Dock,1 :Query 2 1 0 1 1", 39); dock_info->components[3].fw_version = 0xffffffff; memcpy(dock_info->components[3].description, "Dock1,Cable,Cyp,TBT_Cable,0 :Query 2 2 2 3 0", 44); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), NULL); g_assert_true(ret); g_assert_cmpint(devices->len, ==, 3); g_ptr_array_set_size(devices, 0); g_free(buf.record); /* inject valid WD15 dock w/ invalid flash pkg version */ buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; memcpy(dock_info->dock_description, "IE_Dock", 7); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_LEGACY; dock_info->location = 2; dock_info->component_count = 3; dock_info->components[0].fw_version = 0x00ffffff; memcpy(dock_info->components[0].description, "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); dock_info->components[1].fw_version = 0x00ffffff; memcpy(dock_info->components[1].description, "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); dock_info->components[2].fw_version = 0x00ffffff; memcpy(dock_info->components[2].description, "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), &error); g_assert_true(ret); g_assert_no_error(error); g_assert_cmpint(devices->len, ==, 3); g_ptr_array_set_size(devices, 0); g_free(buf.record); /* inject valid WD15 dock w/ older system EC */ buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = DOCK_TYPE_WD15; memcpy(dock_info->dock_description, "IE_Dock", 7); dock_info->flash_pkg_version = 0x43; dock_info->cable_type = CABLE_TYPE_LEGACY; dock_info->location = 2; dock_info->component_count = 3; dock_info->components[0].fw_version = 0xffffffff; memcpy(dock_info->components[0].description, "Dock1,EC,MIPS32,IE_Dock,0 :Query 2 0 2 2 0", 42); dock_info->components[1].fw_version = 0x10108; memcpy(dock_info->components[1].description, "Dock1,PC,TI,IE_Dock,0 :Query 2 1 0 2 0", 38); dock_info->components[2].fw_version = 0xffffffff; memcpy(dock_info->components[2].description, "Dock1,Cable,Cyp,IE_Cable,0 :Query 2 2 2 1 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), &error); g_assert_true(ret); g_assert_no_error(error); g_assert_cmpint(devices->len, ==, 2); g_ptr_array_set_size(devices, 0); g_free(buf.record); /* inject an invalid future dock */ buf.record = g_malloc0(sizeof(DOCK_INFO_RECORD)); dock_info = &buf.record->dock_info; buf.record->dock_info_header.dir_version = 1; buf.record->dock_info_header.dock_type = 50; memcpy(dock_info->dock_description, "Future!", 8); dock_info->flash_pkg_version = 0x00ffffff; dock_info->cable_type = CABLE_TYPE_UNIV; dock_info->location = 2; dock_info->component_count = 1; dock_info->components[0].fw_version = 0x00ffffff; memcpy(dock_info->components[0].description, "Dock1,EC,MIPS32,FUT_Dock,0 :Query 2 0 2 2 0", 43); out[0] = 0; out[1] = 1; fu_plugin_dell_inject_fake_data(self->plugin_dell, (guint32 *)&out, DOCK_NIC_VID, DOCK_NIC_PID, buf.buf, FALSE); ret = fu_plugin_dell_backend_device_added(self->plugin_dell, FU_DEVICE(fake_usb_device), &error); g_assert_false(ret); g_assert_cmpint(devices->len, ==, 0); g_free(buf.record); /* all */ g_signal_handler_disconnect(self->plugin_uefi_capsule, added_id); g_signal_handler_disconnect(self->plugin_dell, register_id); } static void fu_test_self_init(FuTest *self) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; g_autofree gchar *pluginfn_uefi = NULL; g_autofree gchar *pluginfn_dell = 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); self->plugin_uefi_capsule = fu_plugin_new(ctx); pluginfn_uefi = g_test_build_filename(G_TEST_BUILT, "..", "uefi-capsule", "libfu_plugin_uefi_capsule." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(self->plugin_uefi_capsule, pluginfn_uefi, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin_uefi_capsule, &error); g_assert_no_error(error); g_assert_true(ret); self->plugin_dell = fu_plugin_new(ctx); pluginfn_dell = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_dell." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(self->plugin_dell, pluginfn_dell, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin_dell, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_test_self_free(FuTest *self) { if (self->plugin_uefi_capsule != NULL) g_object_unref(self->plugin_uefi_capsule); if (self->plugin_dell != NULL) g_object_unref(self->plugin_dell); 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_autoptr(FuTest) self = g_new0(FuTest, 1); g_test_init(&argc, &argv, NULL); /* change path */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); /* change behavior */ sysfsdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW); g_setenv("FWUPD_UEFI_ESP_PATH", sysfsdir, TRUE); g_setenv("FWUPD_UEFI_TEST", "1", TRUE); g_setenv("FWUPD_DELL_FAKE_SMBIOS", "1", FALSE); /* 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 */ fu_test_self_init(self); g_test_add_data_func("/fwupd/plugin{dell:tpm}", self, fu_plugin_dell_tpm_func); g_test_add_data_func("/fwupd/plugin{dell:dock}", self, fu_plugin_dell_dock_func); return g_test_run(); } fwupd-1.7.5/plugins/dell/meson.build000066400000000000000000000023601420024370600174130ustar00rootroot00000000000000if get_option('plugin_dell') cargs = ['-DG_LOG_DOMAIN="FuPluginDell"'] install_data(['dell.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_dell', fu_hash, sources : [ 'fu-plugin-dell.c', 'fu-dell-smi.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], dependencies : [ plugin_deps, libsmbios_c, tpm2tss, ], ) 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( 'dell-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-dell-smi.c', 'fu-plugin-dell.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, libsmbios_c, valgrind, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], ) test('dell-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/dell/tests000077700000000000000000000000001420024370600222712../uefi-capsule/tests/ustar00rootroot00000000000000fwupd-1.7.5/plugins/dfu-csr/000077500000000000000000000000001420024370600156735ustar00rootroot00000000000000fwupd-1.7.5/plugins/dfu-csr/README.md000066400000000000000000000032071420024370600171540ustar00rootroot00000000000000# DFU CSR ## 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` * `USB\VID_0A12` ## 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`. fwupd-1.7.5/plugins/dfu-csr/data/000077500000000000000000000000001420024370600166045ustar00rootroot00000000000000fwupd-1.7.5/plugins/dfu-csr/data/lsusb.txt000066400000000000000000000036411420024370600205010ustar00rootroot00000000000000Bus 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.7.5/plugins/dfu-csr/data/upgrade.tdc.gz000066400000000000000000000163611420024370600213550ustar00rootroot00000000000000*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.7.5/plugins/dfu-csr/dfu-csr.quirk000066400000000000000000000004461420024370600203170ustar00rootroot00000000000000[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.7.5/plugins/dfu-csr/fu-dfu-csr-device.c000066400000000000000000000303621420024370600212530ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-dfu-common.h" #include "fu-dfu-csr-device.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_REPORT_ID_COMMAND 0x01 #define FU_DFU_CSR_REPORT_ID_STATUS 0x02 #define FU_DFU_CSR_REPORT_ID_CONTROL 0x03 #define FU_DFU_CSR_COMMAND_HEADER_SIZE 6 /* bytes */ #define FU_DFU_CSR_COMMAND_UPGRADE 0x01 #define FU_DFU_CSR_STATUS_HEADER_SIZE 7 #define FU_DFU_CSR_CONTROL_HEADER_SIZE 2 /* bytes */ #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_common_string_append_kv(str, idt, "State", fu_dfu_state_to_string(self->dfu_state)); fu_common_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 = buf[2] + (((guint32)buf[3]) << 8) + (((guint32)buf[4]) << 16); 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_common_read_uint16(&buf[1], G_LITTLE_ENDIAN); if (data_sz + FU_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_DFU_CSR_COMMAND_HEADER_SIZE, sizeof(buf) - FU_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 DFU_CSR header */ if (i == 0 && chunk_sz >= 10) { const guint8 *buf = g_bytes_get_data(chunk, NULL); if (memcmp(buf, "DFU_CSR-dfu", 7) == 0) { guint16 hdr_ver; guint16 hdr_len; if (!fu_common_read_uint16_safe(buf, chunk_sz, 8, &hdr_ver, G_LITTLE_ENDIAN, error)) return NULL; if (hdr_ver != 0x03) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DFU_CSR header version is " "invalid %" G_GUINT16_FORMAT, hdr_ver); return NULL; } if (!fu_common_read_uint32_safe(buf, chunk_sz, 10, &total_sz, G_LITTLE_ENDIAN, error)) return NULL; if (total_sz == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DFU_CSR header data length " "invalid %" G_GUINT32_FORMAT, total_sz); return NULL; } if (!fu_common_read_uint16_safe(buf, chunk_sz, 14, &hdr_len, G_LITTLE_ENDIAN, error)) return NULL; g_debug("DFU_CSR header length: %" G_GUINT16_FORMAT, hdr_len); } } /* 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_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) { const guint8 *chunk_data; gsize chunk_sz = 0; guint8 buf[FU_DFU_CSR_PACKET_DATA_SIZE] = {0}; /* too large? */ chunk_data = g_bytes_get_data(chunk, &chunk_sz); if (chunk_sz + FU_DFU_CSR_COMMAND_HEADER_SIZE > FU_DFU_CSR_PACKET_DATA_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet was too large: %" G_GSIZE_FORMAT, chunk_sz); return FALSE; } g_debug("writing %" G_GSIZE_FORMAT " bytes of data", chunk_sz); /* create packet */ buf[0] = FU_DFU_CSR_REPORT_ID_COMMAND; buf[1] = FU_DFU_CSR_COMMAND_UPGRADE; fu_common_write_uint16(&buf[2], idx, G_LITTLE_ENDIAN); fu_common_write_uint16(&buf[4], chunk_sz, G_LITTLE_ENDIAN); if (!fu_memcpy_safe(buf, sizeof(buf), FU_DFU_CSR_COMMAND_HEADER_SIZE, /* dst */ chunk_data, chunk_sz, 0x0, /* src */ chunk_sz, error)) return FALSE; /* hit hardware */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_DFU_CSR_REPORT_ID_COMMAND, buf, sizeof(buf), 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); g_usleep(self->dnload_timeout * 1000); } /* 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"); g_usleep(G_USEC_PER_SEC); 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); guint16 idx; g_autoptr(GBytes) blob_empty = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) 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, 0x0, FU_DFU_CSR_PACKET_DATA_SIZE - FU_DFU_CSR_COMMAND_HEADER_SIZE); /* send to hardware */ for (idx = 0; idx < chunks->len; idx++) { FuChunk *chk = g_ptr_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_set_percentage_full(progress, (gsize)idx + 1, (gsize)chunks->len); } /* 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_probe(FuDevice *device, GError **error) { /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_dfu_csr_device_parent_class)->probe(device, error)) return FALSE; /* hardcoded */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } 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_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); 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->probe = fu_dfu_csr_device_probe; klass_device->set_progress = fu_dfu_csr_device_set_progress; } fwupd-1.7.5/plugins/dfu-csr/fu-dfu-csr-device.h000066400000000000000000000004531420024370600212560ustar00rootroot00000000000000/* * 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.7.5/plugins/dfu-csr/fu-plugin-dfu-csr.c000066400000000000000000000006661420024370600213160ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-csr-device.h" static void fu_plugin_dfu_csr_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_DFU_CSR_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_dfu_csr_init; } fwupd-1.7.5/plugins/dfu-csr/meson.build000066400000000000000000000010441420024370600200340ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginDfuCsr"'] install_data(['dfu-csr.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_dfu_csr', fu_hash, sources : [ 'fu-dfu-csr-device.c', 'fu-plugin-dfu-csr.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, plugindfu_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupdplugin, dfu, ], ) endif fwupd-1.7.5/plugins/dfu/000077500000000000000000000000001420024370600151065ustar00rootroot00000000000000fwupd-1.7.5/plugins/dfu/README.md000066400000000000000000000031151420024370600163650ustar00rootroot00000000000000# 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` * `USB\VID_273F` ## 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. ## 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. 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`. fwupd-1.7.5/plugins/dfu/contrib/000077500000000000000000000000001420024370600165465ustar00rootroot00000000000000fwupd-1.7.5/plugins/dfu/contrib/parse-avrdude-conf.py000077500000000000000000000113561420024370600226160ustar00rootroot00000000000000#!/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 not "size" 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"] + " [USER] USER=0x%x" % result["size"] + "\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("USAGE: %s avrdude.conf tmp.quirk" % sys.argv[0]) sys.exit(1) all_parts = _parse_parts(sys.argv[1]) _write_quirks(all_parts, sys.argv[2]) fwupd-1.7.5/plugins/dfu/dfu-tool.1000066400000000000000000000022741420024370600167260ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "dfu-tool man page" .SH NAME dfu-tool \- write firmware to DFU devices .SH SYNOPSIS dfu-tool [CMD] .SH DESCRIPTION .PP This manual page documents briefly the \fBdfu-tool\fR command. .PP \fBdfu-tool\fR 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. .PP 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\&. .SH OPTIONS The dfu-tool command takes various options depending on the action. Run \fBdfu-tool --help\fR for the full list. .SH SEE ALSO fwupdtool(1), fwupdmgr(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/plugins/dfu/dfu.quirk000066400000000000000000000241121420024370600167410ustar00rootroot00000000000000# 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] CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_5855] CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_58FE] CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_5800] Flags = detach-for-attach # 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 # 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 [USB\VID_03EB&PID_2FF9] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FFA] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FFB] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode # 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 # Leaflabs Maple3 [USB\VID_1EAF&PID_0003&REV_0200] Plugin = dfu DfuForceVersion = 0110 # Atmel FLIP Bootloader [USB\VID_03EB] Plugin = dfu DfuForceVersion = ff01 # 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 = 011a DfuForceTimeout = 5000 # Poly Studio [USB\VID_095D&PID_9217] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout RemoveDelay = 60000 [USB\VID_095D&PID_9218] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout RemoveDelay = 60000 # Poly Eagle Eye Cube [USB\VID_095D&PID_9212] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 30000 [USB\VID_095D&PID_9213] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 30000 # Poly Studio P15 [USB\VID_095D&PID_9290] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 60000 [USB\VID_095D&PID_9291] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 60000 # Poly ULCC [USB\VID_095D&PID_9160] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout RemoveDelay = 60000 [USB\VID_095D&PID_927B] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout RemoveDelay = 60000 # Poly Eagle Eye Mini [USB\VID_095D&PID_3001] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 [USB\VID_095D&PID_3002] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 # Poly Studio R30 [USB\VID_095D&PID_92B2] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 60000 [USB\VID_095D&PID_92B3] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 60000 # Poly Studio P5 [USB\VID_095D&PID_9296] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 [USB\VID_095D&PID_9297] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 # Poly Studio E70 [USB\VID_095D&PID_92A1] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 90000 [USB\VID_095D&PID_92A2] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 90000 # Poly Studio P21 [USB\VID_095D&PID_9298] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 [USB\VID_095D&PID_9299] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout RemoveDelay = 9000 # AVer ATLAS CAM [USB\VID_2574&PID_0A70] Plugin = dfu Flags = detach-for-attach RemoveDelay = 60000 # AVer CAM520 Pro2 [USB\VID_2574&PID_0A30] Plugin = dfu Flags = detach-for-attach RemoveDelay = 180000 fwupd-1.7.5/plugins/dfu/fu-dfu-common.c000066400000000000000000000064001420024370600177260ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-common.h" /** * fu_dfu_state_to_string: * @state: a #FuDfuState, e.g. %FU_DFU_STATE_DFU_MANIFEST * * Converts an enumerated value to a string. * * Returns: a string **/ const gchar * fu_dfu_state_to_string(FuDfuState state) { if (state == FU_DFU_STATE_APP_IDLE) return "appIDLE"; if (state == FU_DFU_STATE_APP_DETACH) return "appDETACH"; if (state == FU_DFU_STATE_DFU_IDLE) return "dfuIDLE"; if (state == FU_DFU_STATE_DFU_DNLOAD_SYNC) return "dfuDNLOAD-SYNC"; if (state == FU_DFU_STATE_DFU_DNBUSY) return "dfuDNBUSY"; if (state == FU_DFU_STATE_DFU_DNLOAD_IDLE) return "dfuDNLOAD-IDLE"; if (state == FU_DFU_STATE_DFU_MANIFEST_SYNC) return "dfuMANIFEST-SYNC"; if (state == FU_DFU_STATE_DFU_MANIFEST) return "dfuMANIFEST"; if (state == FU_DFU_STATE_DFU_MANIFEST_WAIT_RESET) return "dfuMANIFEST-WAIT-RESET"; if (state == FU_DFU_STATE_DFU_UPLOAD_IDLE) return "dfuUPLOAD-IDLE"; if (state == FU_DFU_STATE_DFU_ERROR) return "dfuERROR"; return NULL; } /** * fu_dfu_status_to_string: * @status: a #FuDfuStatus, e.g. %FU_DFU_STATUS_ERR_ERASE * * Converts an enumerated value to a string. * * Returns: a string **/ const gchar * fu_dfu_status_to_string(FuDfuStatus status) { if (status == FU_DFU_STATUS_OK) return "OK"; if (status == FU_DFU_STATUS_ERR_TARGET) return "errTARGET"; if (status == FU_DFU_STATUS_ERR_FILE) return "errFILE"; if (status == FU_DFU_STATUS_ERR_WRITE) return "errwrite"; if (status == FU_DFU_STATUS_ERR_ERASE) return "errERASE"; if (status == FU_DFU_STATUS_ERR_CHECK_ERASED) return "errCHECK_ERASED"; if (status == FU_DFU_STATUS_ERR_PROG) return "errPROG"; if (status == FU_DFU_STATUS_ERR_VERIFY) return "errVERIFY"; if (status == FU_DFU_STATUS_ERR_ADDRESS) return "errADDRESS"; if (status == FU_DFU_STATUS_ERR_NOTDONE) return "errNOTDONE"; if (status == FU_DFU_STATUS_ERR_FIRMWARE) return "errFIRMWARE"; if (status == FU_DFU_STATUS_ERR_VENDOR) return "errVENDOR"; if (status == FU_DFU_STATUS_ERR_USBR) return "errUSBR"; if (status == FU_DFU_STATUS_ERR_POR) return "errPOR"; if (status == FU_DFU_STATUS_ERR_UNKNOWN) return "errUNKNOWN"; if (status == FU_DFU_STATUS_ERR_STALLDPKT) return "errSTALLDPKT"; return NULL; } /** * 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.7.5/plugins/dfu/fu-dfu-common.h000066400000000000000000000147331420024370600177430ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FuDfuRequest: * @FU_DFU_REQUEST_DETACH: Detach * @FU_DFU_REQUEST_DNLOAD: Download host-to-device * @FU_DFU_REQUEST_UPLOAD: Upload device-to-host * @FU_DFU_REQUEST_GETSTATUS: Get the device status * @FU_DFU_REQUEST_CLRSTATUS: Clear the device status * @FU_DFU_REQUEST_GETSTATE: Get the last set state * @FU_DFU_REQUEST_ABORT: Abort the current transfer * * The DFU request kinds. **/ typedef enum { FU_DFU_REQUEST_DETACH = 0x00, FU_DFU_REQUEST_DNLOAD = 0x01, FU_DFU_REQUEST_UPLOAD = 0x02, FU_DFU_REQUEST_GETSTATUS = 0x03, FU_DFU_REQUEST_CLRSTATUS = 0x04, FU_DFU_REQUEST_GETSTATE = 0x05, FU_DFU_REQUEST_ABORT = 0x06, /*< private >*/ FU_DFU_REQUEST_LAST } FuDfuRequest; /** * FuDfuStatus: * @FU_DFU_STATUS_OK: No error condition is present * @FU_DFU_STATUS_ERR_TARGET: File is not targeted for use by this device * @FU_DFU_STATUS_ERR_FILE: File is for this device but fails a verification *test * @FU_DFU_STATUS_ERR_WRITE: Device is unable to write memory * @FU_DFU_STATUS_ERR_ERASE: Memory erase function failed * @FU_DFU_STATUS_ERR_CHECK_ERASED: Memory erase check failed * @FU_DFU_STATUS_ERR_PROG: Program memory function failed * @FU_DFU_STATUS_ERR_VERIFY: Programmed memory failed verification * @FU_DFU_STATUS_ERR_ADDRESS: Cannot program memory due to received address that *isout of range * @FU_DFU_STATUS_ERR_NOTDONE: Received DFU_DNLOAD with wLength = 0 but data is *incomplete * @FU_DFU_STATUS_ERR_FIRMWARE: Device firmware is corrupt * @FU_DFU_STATUS_ERR_VENDOR: iString indicates a vendor-specific error * @FU_DFU_STATUS_ERR_USBR: Device detected unexpected USB reset signaling * @FU_DFU_STATUS_ERR_POR: Device detected unexpected power on reset * @FU_DFU_STATUS_ERR_UNKNOWN: Something unexpected went wrong * @FU_DFU_STATUS_ERR_STALLDPKT: Device stalled an unexpected request * * The status enumerated kind. **/ typedef enum { FU_DFU_STATUS_OK = 0x00, FU_DFU_STATUS_ERR_TARGET = 0x01, FU_DFU_STATUS_ERR_FILE = 0x02, FU_DFU_STATUS_ERR_WRITE = 0x03, FU_DFU_STATUS_ERR_ERASE = 0x04, FU_DFU_STATUS_ERR_CHECK_ERASED = 0x05, FU_DFU_STATUS_ERR_PROG = 0x06, FU_DFU_STATUS_ERR_VERIFY = 0x07, FU_DFU_STATUS_ERR_ADDRESS = 0x08, FU_DFU_STATUS_ERR_NOTDONE = 0x09, FU_DFU_STATUS_ERR_FIRMWARE = 0x0a, FU_DFU_STATUS_ERR_VENDOR = 0x0b, FU_DFU_STATUS_ERR_USBR = 0x0c, FU_DFU_STATUS_ERR_POR = 0x0d, FU_DFU_STATUS_ERR_UNKNOWN = 0x0e, FU_DFU_STATUS_ERR_STALLDPKT = 0x0f, /*< private >*/ FU_DFU_STATUS_LAST } FuDfuStatus; /** * FuDfuState: * @FU_DFU_STATE_APP_IDLE: State 0 * @FU_DFU_STATE_APP_DETACH: State 1 * @FU_DFU_STATE_DFU_IDLE: State 2 * @FU_DFU_STATE_DFU_DNLOAD_SYNC: State 3 * @FU_DFU_STATE_DFU_DNBUSY: State 4 * @FU_DFU_STATE_DFU_DNLOAD_IDLE: State 5 * @FU_DFU_STATE_DFU_MANIFEST_SYNC: State 6 * @FU_DFU_STATE_DFU_MANIFEST: State 7 * @FU_DFU_STATE_DFU_MANIFEST_WAIT_RESET: State 8 * @FU_DFU_STATE_DFU_UPLOAD_IDLE: State 9 * @FU_DFU_STATE_DFU_ERROR: State 10 * * The state enumerated kind. **/ typedef enum { FU_DFU_STATE_APP_IDLE = 0x00, FU_DFU_STATE_APP_DETACH = 0x01, FU_DFU_STATE_DFU_IDLE = 0x02, FU_DFU_STATE_DFU_DNLOAD_SYNC = 0x03, FU_DFU_STATE_DFU_DNBUSY = 0x04, FU_DFU_STATE_DFU_DNLOAD_IDLE = 0x05, FU_DFU_STATE_DFU_MANIFEST_SYNC = 0x06, FU_DFU_STATE_DFU_MANIFEST = 0x07, FU_DFU_STATE_DFU_MANIFEST_WAIT_RESET = 0x08, FU_DFU_STATE_DFU_UPLOAD_IDLE = 0x09, FU_DFU_STATE_DFU_ERROR = 0x0a, /*< private >*/ FU_DFU_STATE_LAST } FuDfuState; /** * FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET: * * Device needs resetting twice for attach. */ #define FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET (1 << 0) /** * FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD: * * An upload or download is required for attach. */ #define FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD (1 << 1) /** * FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE: * * Force DFU mode. */ #define FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE (1 << 2) /** * FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT: * * Ignore the device download timeout. */ #define FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT (1 << 3) /** * FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME: * * Device has broken DFU runtime support. */ #define FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME (1 << 4) /** * FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD: * * Uploading from the device is broken. */ #define FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD (1 << 5) /** * FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME: * * No DFU runtime interface is provided. */ #define FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME (1 << 6) /** * FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD: * * Do not do GetStatus when uploading. */ #define FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD (1 << 7) /** * FU_DFU_DEVICE_FLAG_NO_PID_CHANGE: * * Accept the same VID:PID when changing modes. */ #define FU_DFU_DEVICE_FLAG_NO_PID_CHANGE (1 << 8) /** * FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE: * * Use any interface for DFU. */ #define FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE (1 << 9) /** * FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR: * * Device uses the ATMEL bootloader. */ #define FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR (1 << 10) /** * FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO: * * Fix up the protocol number. */ #define FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO (1 << 11) /** * FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL: * * Use a legacy protocol version. */ #define FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL (1 << 12) /** * FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH: * * Requires a FU_DFU_REQUEST_DETACH to attach. */ #define FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH (1 << 13) /** * FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE: * * In absence of sector size, assume byte. */ #define FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE (1 << 14) /** * FU_DFU_DEVICE_FLAG_MANIFEST_POLL: * * Requires polling via GetStatus in dfuManifest state. */ #define FU_DFU_DEVICE_FLAG_MANIFEST_POLL (1 << 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 (1 << 16) /** * FU_DFU_DEVICE_FLAG_GD32: * * Uses the slightly weird GD32 variant of DFU. */ #define FU_DFU_DEVICE_FLAG_GD32 (1 << 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 (1 << 18) const gchar * fu_dfu_state_to_string(FuDfuState state); const gchar * fu_dfu_status_to_string(FuDfuStatus status); /* helpers */ GBytes * fu_dfu_utils_bytes_join_array(GPtrArray *chunks); fwupd-1.7.5/plugins/dfu/fu-dfu-device.c000066400000000000000000001603311420024370600177010ustar00rootroot00000000000000/* * 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. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_FORCE_VERSION "DfuForceVersion" #define DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT 5 /* ms */ #include "config.h" #include #include #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 void fu_dfu_device_finalize(GObject *object); typedef struct { FuDfuDeviceAttrs attributes; 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_common_string_append_kv(str, idt, "State", fu_dfu_state_to_string(priv->state)); fu_common_string_append_kv(str, idt, "Status", fu_dfu_status_to_string(priv->status)); fu_common_string_append_kb(str, idt, "DoneUploadOrDownload", priv->done_upload_or_download); fu_common_string_append_kb(str, idt, "ClaimedInterface", priv->claimed_interface); if (priv->chip_id != NULL) fu_common_string_append_kv(str, idt, "ChipId", priv->chip_id); fu_common_string_append_kx(str, idt, "Version", priv->version); fu_common_string_append_kx(str, idt, "ForceVersion", priv->force_version); if (priv->force_transfer_size != 0x0) { fu_common_string_append_kx(str, idt, "ForceTransferSize", priv->force_transfer_size); } fu_common_string_append_kx(str, idt, "RuntimePid", priv->runtime_pid); fu_common_string_append_kx(str, idt, "RuntimeVid", priv->runtime_vid); fu_common_string_append_kx(str, idt, "RuntimeRelease", priv->runtime_release); fu_common_string_append_kx(str, idt, "TransferSize", priv->transfer_size); fu_common_string_append_kx(str, idt, "IfaceNumber", priv->iface_number); fu_common_string_append_kx(str, idt, "DnloadTimeout", priv->dnload_timeout); fu_common_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_dfu_target_to_string(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; } /** * 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_ATTR_CAN_ACCELERATE) priv->transfer_size = 0x1000; /* get attributes about the DFU operation */ priv->attributes = desc.bmAttributes; 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; /* 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 (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 { priv->attributes |= FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD | FU_DFU_DEVICE_ATTR_CAN_UPLOAD; } /* fix up the version */ if (priv->force_version > 0) 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_debug("DFU v1.1"); } else if (priv->version == FU_DFU_FIRMARE_VERSION_ATMEL_AVR) { g_debug("AVR-DFU support"); priv->version = FU_DFU_FIRMARE_VERSION_ATMEL_AVR; } else if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE) { g_debug("STM-DFU support"); } else if (priv->version == 0x0101) { g_debug("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_dfu_target_set_device(target, 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); priv->attributes = FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD | FU_DFU_DEVICE_ATTR_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)) priv->attributes &= ~FU_DFU_DEVICE_ATTR_CAN_UPLOAD; return TRUE; } /** * fu_dfu_device_can_upload: * @self: a #FuDfuDevice * * Gets if the device can upload. * * Returns: %TRUE if the device can upload from device to host **/ gboolean fu_dfu_device_can_upload(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); return (priv->attributes & FU_DFU_DEVICE_ATTR_CAN_UPLOAD) > 0; } /** * fu_dfu_device_can_download: * @self: a #FuDfuDevice * * Gets if the device can download. * * Returns: %TRUE if the device can download from host to device **/ gboolean fu_dfu_device_can_download(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); return (priv->attributes & FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD) > 0; } /** * fu_dfu_device_set_timeout: * @self: a #FuDfuDevice * @timeout_ms: the timeout in ms * * Sets the USB timeout to use when contacting the USB device. **/ void fu_dfu_device_set_timeout(FuDfuDevice *self, guint timeout_ms) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_DEVICE(self)); priv->timeout_ms = timeout_ms; } /** * 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_has_attribute: (skip) * @self: a #FuDfuDevice * @attribute: a device attribute, e.g. %FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD * * Returns if an attribute set for the device. * * Returns: %TRUE if the attribute is set **/ gboolean fu_dfu_device_has_attribute(FuDfuDevice *self, FuDfuDeviceAttrs attribute) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); return (priv->attributes & attribute) > 0; } /** * fu_dfu_device_remove_attribute: (skip) * @self: a #FuDfuDevice * @attribute: a device attribute, e.g. %FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD * * Removes an attribute from the device. **/ void fu_dfu_device_remove_attribute(FuDfuDevice *self, FuDfuDeviceAttrs attribute) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_DEVICE(self)); priv->attributes &= ~attribute; } /** * fu_dfu_device_new: * * Creates a new DFU device object. * * Returns: a new #FuDfuDevice **/ FuDfuDevice * fu_dfu_device_new(GUsbDevice *usb_device) { FuDfuDevice *self; self = g_object_new(FU_TYPE_DFU_DEVICE, "usb-device", usb_device, NULL); return self; } /** * fu_dfu_device_get_targets: * @self: a #FuDfuDevice * * Gets all the targets for this device. * * Returns: (transfer none) (element-type FuDfuTarget): #FuDfuTarget, or %NULL **/ GPtrArray * fu_dfu_device_get_targets(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL); return priv->targets; } /** * 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_dfu_target_get_alt_name(target, NULL), 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_platform_id: * @self: a #FuDfuDevice * * Gets the platform ID which normally corresponds to the port in some way. * * Returns: string or %NULL **/ const gchar * fu_dfu_device_get_platform_id(FuDfuDevice *self) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL); return g_usb_device_get_platform_id(usb_device); } /** * 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; } /** * fu_dfu_device_get_runtime_release: * @self: a #FuDfuDevice * * Gets the runtime release number in BCD format. * * Returns: release number, or 0xffff for unknown **/ guint16 fu_dfu_device_get_runtime_release(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff); return priv->runtime_release; } 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; } 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 transers * 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, 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 * @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, 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]; 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); /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to refresh: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); 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)) 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 && !(priv->attributes & FU_DFU_DEVICE_ATTR_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, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { 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_debug("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]); if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT)) { priv->dnload_timeout = DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT; } else { priv->dnload_timeout = buf[1] + (((guint32)buf[2]) << 8) + (((guint32)buf[3]) << 16); if (priv->dnload_timeout == 0 && !fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT)) { priv->dnload_timeout = DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT; g_debug("no dnload-timeout, using default of %ums", priv->dnload_timeout); } } 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; g_autoptr(GError) error_local = NULL; 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, priv->iface_number, 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); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); 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; /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to detach: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); 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)) 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 ((priv->attributes & FU_DFU_DEVICE_ATTR_WILL_DETACH) == 0) { g_debug("doing device reset as host will not self-reset"); if (!fu_dfu_device_reset(self, progress, error)) return FALSE; } /* success */ priv->force_version = 0x0; 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(error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to abort: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); 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)) { 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; } /** * fu_dfu_device_clear_status: * @self: a #FuDfuDevice * @error: (nullable): optional return location for an error * * Clears any error status on the DFU device. * * Returns: %TRUE for success **/ gboolean fu_dfu_device_clear_status(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(error == NULL || *error == NULL, FALSE); /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to clear status: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); 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)) { 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_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); GPtrArray *targets = fu_dfu_device_get_targets(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)) { #if G_USB_CHECK_VERSION(0, 3, 6) 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; if (g_getenv("FWUPD_DFU_VERBOSE") != NULL) fu_common_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); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "GUsb version too old to support GD32, " "fwupd needs to be rebuilt against 0.3.6 or later"); return FALSE; #endif } /* set up target ready for use */ for (guint j = 0; j < targets->len; j++) { FuDfuTarget *target = g_ptr_array_index(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)); /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_dfu_device_parent_class)->probe(device, error)) return FALSE; /* 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_dfu_device_can_download(self)) { g_warning("%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_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET)) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); fu_progress_sleep(progress, 10000); } /* success */ return TRUE; } gboolean fu_dfu_device_reset(FuDfuDevice *self, FuProgress *progress, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); 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); /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to reset: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); return FALSE; } 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; } 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_dfu_device_has_attribute(self, FU_DFU_DEVICE_ATTR_WILL_DETACH)) { g_debug("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); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gboolean use_dfuse = FALSE; g_autoptr(FuFirmware) firmware = NULL; /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to upload: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); return 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_dfu_target_get_alt_name(target, NULL) != 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; const gchar *alt_name; /* upload to target and proxy signals */ target = g_ptr_array_index(priv->targets, i); /* ignore some target types */ alt_name = fu_dfu_target_get_alt_name_for_display(target, NULL); if (g_strcmp0(alt_name, "Option Bytes") == 0) { g_debug("ignoring target %s", alt_name); 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); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gboolean ret; g_autoptr(GPtrArray) images = NULL; guint16 firmware_pid = 0xffff; guint16 firmware_vid = 0xffff; /* no backing USB device */ if (usb_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to download: no GUsbDevice for %s", fu_dfu_device_get_platform_id(self)); return FALSE; } /* 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)); } for (guint i = 0; i < images->len; i++) { FuFirmware *image = g_ptr_array_index(images, i); FuDfuTargetTransferFlags flags_local = DFU_TARGET_TRANSFER_FLAG_NONE; const gchar *alt_name; guint8 alt; g_autoptr(FuDfuTarget) target_tmp = NULL; g_autoptr(GError) error_local = 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; /* we don't actually need to print this */ alt_name = fu_dfu_target_get_alt_name(target_tmp, &error_local); if (alt_name == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { alt_name = "unknown"; } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } g_debug("downloading to target: %s", alt_name); /* 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, 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, 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 (value != NULL) { gsize valuesz = strlen(value); return fu_firmware_strparse_uint16_safe(value, valuesz, 0, &priv->force_version, error); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DFU version"); return FALSE; } if (g_strcmp0(key, "DfuForceTimeout") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT, error)) return FALSE; priv->timeout_ms = tmp; return TRUE; } if (g_strcmp0(key, "DfuForceTransferSize") == 0) { if (!fu_common_strtoull_full(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; } /** * fu_dfu_device_get_attributes_as_string: (skip) * @self: a #FuDfuDevice * * Gets a string describing the attributes for a device. * * Returns: a string, possibly empty **/ gchar * fu_dfu_device_get_attributes_as_string(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GString *str; /* just append to a string */ str = g_string_new(""); if (priv->attributes & FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD) g_string_append_printf(str, "can-download|"); if (priv->attributes & FU_DFU_DEVICE_ATTR_CAN_UPLOAD) g_string_append_printf(str, "can-upload|"); if (priv->attributes & FU_DFU_DEVICE_ATTR_MANIFEST_TOL) g_string_append_printf(str, "manifest-tol|"); if (priv->attributes & FU_DFU_DEVICE_ATTR_WILL_DETACH) g_string_append_printf(str, "will-detach|"); if (priv->attributes & FU_DFU_DEVICE_ATTR_CAN_ACCELERATE) g_string_append_printf(str, "can-accelerate|"); /* remove trailing pipe */ g_string_truncate(str, str->len - 1); return g_string_free(str, 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; fu_device_add_icon(FU_DEVICE(self), "drive-harddisk-usb"); 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), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ATTACH_EXTRA_RESET, "attach-extra-reset"); 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"); } fwupd-1.7.5/plugins/dfu/fu-dfu-device.h000066400000000000000000000066621420024370600177140ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include #include "fu-dfu-common.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) /** * FuDfuDeviceAttrs: * @FU_DFU_DEVICE_ATTR_NONE: No attributes set * @FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD: Can download from host->device * @FU_DFU_DEVICE_ATTR_CAN_UPLOAD: Can upload from device->host * @FU_DFU_DEVICE_ATTR_MANIFEST_TOL: Can answer GetStatus in manifest * @FU_DFU_DEVICE_ATTR_WILL_DETACH: Will self-detach * @FU_DFU_DEVICE_ATTR_CAN_ACCELERATE: Use a larger transfer size for speed * * The device DFU attributes. **/ typedef enum { FU_DFU_DEVICE_ATTR_NONE = 0, FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD = (1 << 0), FU_DFU_DEVICE_ATTR_CAN_UPLOAD = (1 << 1), FU_DFU_DEVICE_ATTR_MANIFEST_TOL = (1 << 2), FU_DFU_DEVICE_ATTR_WILL_DETACH = (1 << 3), FU_DFU_DEVICE_ATTR_CAN_ACCELERATE = (1 << 7), /*< private >*/ FU_DFU_DEVICE_ATTR_LAST } FuDfuDeviceAttrs; struct _FuDfuDeviceClass { FuUsbDeviceClass parent_class; }; FuDfuDevice * fu_dfu_device_new(GUsbDevice *usb_device); const gchar * fu_dfu_device_get_platform_id(FuDfuDevice *self); GPtrArray * fu_dfu_device_get_targets(FuDfuDevice *self); 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); void fu_dfu_device_set_chip_id(FuDfuDevice *self, const gchar *chip_id); guint16 fu_dfu_device_get_runtime_vid(FuDfuDevice *self); guint16 fu_dfu_device_get_runtime_pid(FuDfuDevice *self); guint16 fu_dfu_device_get_runtime_release(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, GError **error); gboolean fu_dfu_device_refresh_and_clear(FuDfuDevice *self, GError **error); gboolean fu_dfu_device_abort(FuDfuDevice *self, GError **error); gboolean fu_dfu_device_clear_status(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); gboolean fu_dfu_device_can_upload(FuDfuDevice *self); gboolean fu_dfu_device_can_download(FuDfuDevice *self); gboolean fu_dfu_device_has_attribute(FuDfuDevice *self, FuDfuDeviceAttrs attribute); void fu_dfu_device_remove_attribute(FuDfuDevice *self, FuDfuDeviceAttrs attribute); void fu_dfu_device_set_transfer_size(FuDfuDevice *self, guint16 transfer_size); void fu_dfu_device_set_timeout(FuDfuDevice *self, guint timeout_ms); void fu_dfu_device_error_fixup(FuDfuDevice *self, GError **error); guint fu_dfu_device_get_download_timeout(FuDfuDevice *self); gchar * fu_dfu_device_get_attributes_as_string(FuDfuDevice *self); gboolean fu_dfu_device_ensure_interface(FuDfuDevice *self, GError **error); fwupd-1.7.5/plugins/dfu/fu-dfu-sector.c000066400000000000000000000124671420024370600177470ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * FuDfuSector: * * 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. * * See also: [class@FuChunk] */ #include "config.h" #include #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) { } /** * fu_dfu_sector_new: (skip) * address: the address for the sector * size: the size of this sector * size_left: the size of the rest of the sector * zone: the zone of memory the setor belongs * number: the sector number in the zone * cap: the #FuDfuSectorCap * * Creates a new DFU sector object. * * Returns: a new #FuDfuSector **/ 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; } /** * fu_dfu_sector_get_address: * @self: a #FuDfuSector * * Gets the alternate setting. * * Returns: integer, or 0x00 for unset **/ 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; } /** * fu_dfu_sector_get_size: * @self: a #FuDfuSector * * Gets the sector size. * * Returns: integer, or 0x00 for unset **/ 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; } /** * fu_dfu_sector_get_size_left: * @self: a #FuDfuSector * * Gets the size of the rest of the sector. * * Returns: integer, or 0x00 for unset **/ guint32 fu_dfu_sector_get_size_left(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return priv->size_left; } /** * fu_dfu_sector_get_zone: * @self: a #FuDfuSector * * Gets the sector zone number. * * Returns: integer, or 0x00 for unset **/ 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; } /** * fu_dfu_sector_get_number: * @self: a #FuDfuSector * * Gets the sector index number. * * Returns: integer, or 0x00 for unset **/ guint16 fu_dfu_sector_get_number(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return priv->number; } /** * fu_dfu_sector_get_id: * @self: a #FuDfuSector * * Gets the sector ID which is a combination of the zone and sector number. * You can use this number to check if the segment is the 'same' as the last * written or read sector. * * Returns: integer ID, or 0x00 for unset **/ 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; } /** * fu_dfu_sector_has_cap: * @self: a #FuDfuSector * @cap: a #FuDfuSectorCap, e.g. %DFU_SECTOR_CAP_ERASEABLE * * Finds out if the sector has the required capability. * * Returns: %TRUE if the sector has the capabilily **/ 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; } static gchar * fu_dfu_sector_cap_to_string(FuDfuSectorCap cap) { GString *str = g_string_new(NULL); if (cap & DFU_SECTOR_CAP_READABLE) g_string_append(str, "R"); if (cap & DFU_SECTOR_CAP_ERASEABLE) g_string_append(str, "E"); if (cap & DFU_SECTOR_CAP_WRITEABLE) g_string_append(str, "W"); return g_string_free(str, FALSE); } /** * fu_dfu_sector_to_string: * @self: a #FuDfuSector * * Returns a string representation of the object. * * Returns: NULL terminated string, or %NULL for invalid **/ 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.7.5/plugins/dfu/fu-dfu-sector.h000066400000000000000000000026361420024370600177510ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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; }; /** * FuDfuSectorCap: * @DFU_SECTOR_CAP_NONE: No operations possible * @DFU_SECTOR_CAP_READABLE: Sector can be read * @DFU_SECTOR_CAP_WRITEABLE: Sector can be written * @DFU_SECTOR_CAP_ERASEABLE: Sector can be erased * * The flags indicating what the sector can do. **/ typedef enum { DFU_SECTOR_CAP_NONE = 0, DFU_SECTOR_CAP_READABLE = 1 << 0, DFU_SECTOR_CAP_WRITEABLE = 1 << 1, DFU_SECTOR_CAP_ERASEABLE = 1 << 2, /*< private >*/ DFU_SECTOR_CAP_LAST } FuDfuSectorCap; 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); guint16 fu_dfu_sector_get_number(FuDfuSector *self); gboolean fu_dfu_sector_has_cap(FuDfuSector *self, FuDfuSectorCap cap); gchar * fu_dfu_sector_to_string(FuDfuSector *self); fwupd-1.7.5/plugins/dfu/fu-dfu-self-test.c000066400000000000000000000121411420024370600203430ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-target-private.h" static void fu_dfu_enums_func(void) { for (guint i = 0; i < FU_DFU_STATE_LAST; i++) g_assert_cmpstr(fu_dfu_state_to_string(i), !=, NULL); for (guint i = 0; i < FU_DFU_STATUS_LAST; i++) g_assert_cmpstr(fu_dfu_status_to_string(i), !=, NULL); } 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 (fu_common_fnmatch(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(FuDfuDevice) device = fu_dfu_device_new(NULL); g_autoptr(FuDfuTarget) target = NULL; g_autoptr(GError) error = NULL; /* NULL */ target = g_object_new(FU_TYPE_DFU_TARGET, NULL); fu_dfu_target_set_device(target, 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 [R]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [R]", &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 [R]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [R]\n" "Zone:0, Sec#:1, Addr:0x08000800, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08000c00, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08001000, Size:0x0400, Caps:0x7 [REW]\n" "Zone:0, Sec#:1, Addr:0x08001400, Size:0x0400, Caps:0x7 [REW]", &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 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f064, Size:0x0064, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f0c8, Size:0x0064, Caps:0x1 [R]\n" "Zone:0, Sec#:0, Addr:0x0000f12c, Size:0x0064, Caps:0x1 [R]\n" "Zone:1, Sec#:0, Addr:0x0000e000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:1, Sec#:0, Addr:0x00010000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:1, Sec#:0, Addr:0x00012000, Size:0x2000, Caps:0x7 [REW]\n" "Zone:2, Sec#:0, Addr:0x00080000, Size:0x6000, Caps:0x7 [REW]\n" "Zone:2, Sec#:0, Addr:0x00086000, Size:0x6000, Caps:0x7 [REW]", &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_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); /* log everything */ g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* tests go here */ g_test_add_func("/dfu/enums", fu_dfu_enums_func); g_test_add_func("/dfu/target(DfuSe}", fu_dfu_target_dfuse_func); return g_test_run(); } fwupd-1.7.5/plugins/dfu/fu-dfu-target-avr.c000066400000000000000000000671041420024370600205220ustar00rootroot00000000000000/* * 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-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(GBytes) data_in = NULL; guint8 buf[3]; /* this takes a long time on some devices */ fu_dfu_device_set_timeout(fu_dfu_target_get_device(target), 5000); /* format buffer */ buf[0] = DFU_AVR32_GROUP_EXEC; buf[1] = DFU_AVR32_CMD_ERASE; buf[2] = 0xff; data_in = g_bytes_new_static(buf, sizeof(buf)); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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) { guint8 buf[3]; g_autoptr(GBytes) data_empty = NULL; g_autoptr(GBytes) data_in = NULL; g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50); /* format buffer */ buf[0] = DFU_AVR32_GROUP_EXEC; buf[1] = DFU_AVR32_CMD_START_APPLI; buf[2] = DFU_AVR32_START_APPLI_RESET; data_in = g_bytes_new_static(buf, sizeof(buf)); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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 */ data_empty = g_bytes_new(NULL, 0); if (!fu_dfu_target_download_chunk(target, 0, data_empty, 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(GBytes) data_in = NULL; guint8 buf[4]; /* check legacy protocol quirk */ if (fu_device_has_private_flag(FU_DEVICE(fu_dfu_target_get_device(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { g_debug("ignoring select memory unit as legacy protocol"); return TRUE; } /* format buffer */ buf[0] = DFU_AVR32_GROUP_SELECT; buf[1] = DFU_AVR32_CMD_SELECT_MEMORY; buf[2] = DFU_AVR32_MEMORY_UNIT; buf[3] = memory_unit; data_in = g_bytes_new_static(buf, sizeof(buf)); g_debug("selecting memory unit 0x%02x", (guint)memory_unit); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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(GBytes) data_in = NULL; guint8 buf[4]; /* 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 */ buf[0] = DFU_AVR_CMD_CHANGE_BASE_ADDR; buf[1] = 0x03; buf[2] = 0x00; buf[3] = memory_page & 0xff; data_in = g_bytes_new_static(buf, sizeof(buf)); g_debug("selecting memory page 0x%01x", (guint)memory_page); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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(GBytes) data_in = NULL; guint8 buf[5]; /* format buffer */ buf[0] = DFU_AVR32_GROUP_SELECT; buf[1] = DFU_AVR32_CMD_SELECT_MEMORY; buf[2] = DFU_AVR32_MEMORY_PAGE; fu_common_write_uint16(&buf[3], memory_page, G_BIG_ENDIAN); data_in = g_bytes_new_static(buf, sizeof(buf)); g_debug("selecting memory page 0x%02x", (guint)memory_page); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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(GBytes) data_in = NULL; guint8 buf[6]; /* format buffer */ buf[0] = DFU_AVR32_GROUP_UPLOAD; buf[1] = DFU_AVR32_CMD_READ_MEMORY; fu_common_write_uint16(&buf[2], addr_start, G_BIG_ENDIAN); fu_common_write_uint16(&buf[4], addr_end, G_BIG_ENDIAN); data_in = g_bytes_new_static(buf, sizeof(buf)); g_debug("reading memory from 0x%04x to 0x%04x", (guint)addr_start, (guint)addr_end); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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(GBytes) data_in = NULL; guint8 buf[3]; /* format buffer */ buf[0] = DFU_AVR_CMD_READ_COMMAND; buf[1] = page; buf[2] = addr; data_in = g_bytes_new_static(buf, sizeof(buf)); g_debug("read command page:0x%02x addr:0x%02x", (guint)page, (guint)addr); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25); /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 90); /* 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(fu_dfu_target_get_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); if (g_getenv("FWUPD_DFU_VERBOSE") != NULL) fu_common_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("DFU_AVR\\CID_0x%08x", (guint)priv->device_id); } else if (buf[0] == ATMEL_MANUFACTURER_CODE2) { chip_id_guid = g_strdup_printf("DFU_AVR\\CID_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_target_get_device(target); fu_device_add_instance_id(FU_DEVICE(device), chip_id_guid); chip_id = fu_dfu_device_get_chip_id(device); if (chip_id == NULL) { fu_dfu_device_remove_attribute(fu_dfu_target_get_device(target), FU_DFU_DEVICE_ATTR_CAN_DOWNLOAD); fu_dfu_device_remove_attribute(fu_dfu_target_get_device(target), FU_DFU_DEVICE_ATTR_CAN_UPLOAD); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ChipID %s [%s] is not supported", chip_id, chip_id_guid); return FALSE; } fu_dfu_target_set_alt_name(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_autofree guint8 *buf = NULL; g_autoptr(GBytes) chunk_tmp = NULL; /* 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(fu_dfu_target_get_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 */ buf = g_malloc0(fu_chunk_get_data_sz(chk) + header_sz + sizeof(footer)); buf[0] = DFU_AVR32_GROUP_DOWNLOAD; buf[1] = DFU_AVR32_CMD_PROGRAM_START; fu_common_write_uint16(&buf[2], fu_chunk_get_address(chk), G_BIG_ENDIAN); fu_common_write_uint16(&buf[4], fu_chunk_get_address(chk) + fu_chunk_get_data_sz(chk) - 1, G_BIG_ENDIAN); memcpy(&buf[header_sz], fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); memcpy(&buf[header_sz + fu_chunk_get_data_sz(chk)], footer, sizeof(footer)); /* download data */ chunk_tmp = g_bytes_new_static(buf, fu_chunk_get_data_sz(chk) + header_sz + sizeof(footer)); g_debug("sending %" G_GSIZE_FORMAT " bytes to the hardware", g_bytes_get_size(chunk_tmp)); if (!fu_dfu_target_download_chunk(target, i, chunk_tmp, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* 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(fu_dfu_target_get_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 30); /* 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(fu_dfu_target_get_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_common_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 95); /* 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.7.5/plugins/dfu/fu-dfu-target-avr.h000066400000000000000000000007161420024370600205230ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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.7.5/plugins/dfu/fu-dfu-target-private.h000066400000000000000000000024651420024370600214100ustar00rootroot00000000000000/* * 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, GBytes *bytes, 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); void fu_dfu_target_set_device(FuDfuTarget *self, FuDfuDevice *device); FuDfuDevice * fu_dfu_target_get_device(FuDfuTarget *self); 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.7.5/plugins/dfu/fu-dfu-target-stm.c000066400000000000000000000332111420024370600205250ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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(GBytes) bytes_tmp = g_bytes_new(NULL, 0); g_autoptr(GError) error_local = NULL; if (!fu_dfu_target_download_chunk(target, 2, bytes_tmp, 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) { GBytes *data_in; guint8 buf[1]; /* format buffer */ buf[0] = DFU_STM_CMD_ERASE; data_in = g_bytes_new_static(buf, sizeof(buf)); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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) { GBytes *data_in; guint8 buf[5]; /* format buffer */ buf[0] = DFU_STM_CMD_SET_ADDRESS_POINTER; memcpy(buf + 1, &address, 4); data_in = g_bytes_new_static(buf, sizeof(buf)); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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_target_get_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 58); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* 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, 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_common_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) { GBytes *data_in; guint8 buf[5]; /* format buffer */ buf[0] = DFU_STM_CMD_ERASE; memcpy(buf + 1, &address, 4); data_in = g_bytes_new_static(buf, sizeof(buf)); if (!fu_dfu_target_download_chunk(target, 0, data_in, 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, GPtrArray *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 (chunks->len > 0) { FuChunk *chk = g_ptr_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 < chunks->len; i++) { guint32 offset_dev = i * transfer_size; /* for DfuSe devices we need to handle the erase and setting * the sectory 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, 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, DFU_SECTOR_CAP_ERASEABLE) && 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, GPtrArray *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, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk_tmp = g_ptr_array_index(chunks, i); FuDfuSector *sector; guint32 offset_dev = fu_chunk_get_address(chk_tmp); 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 */ if (!fu_dfu_target_download_chunk(target, (i + 2), bytes_tmp, 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_target_get_device(target); g_autoptr(GBytes) bytes = NULL; g_autoptr(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 49); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50); /* 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), 0x0, 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.7.5/plugins/dfu/fu-dfu-target-stm.h000066400000000000000000000007161420024370600205360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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.7.5/plugins/dfu/fu-dfu-target.c000066400000000000000000001124631420024370600177330ustar00rootroot00000000000000/* * 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 #include "fu-dfu-common.h" #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-target-private.h" /* waive-pre-commit */ #define DFU_TARGET_MANIFEST_MAX_POLLING_TRIES 200 static void fu_dfu_target_finalize(GObject *object); typedef struct { FuDfuDevice *device; /* not refcounted */ gboolean done_setup; guint8 alt_setting; guint8 alt_idx; gchar *alt_name; gchar *alt_name_for_display; GPtrArray *sectors; /* of FuDfuSector */ } FuDfuTargetPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuTarget, fu_dfu_target, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_dfu_target_get_instance_private(o)) static void fu_dfu_target_class_init(FuDfuTargetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_dfu_target_finalize; } 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_free(priv->alt_name); g_free(priv->alt_name_for_display); g_ptr_array_unref(priv->sectors); /* we no longer care */ if (priv->device != NULL) { g_object_remove_weak_pointer(G_OBJECT(priv->device), (gpointer *)&priv->device); } G_OBJECT_CLASS(fu_dfu_target_parent_class)->finalize(object); } void fu_dfu_target_to_string(FuDfuTarget *self, guint idt, GString *str) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); fu_common_string_append_kx(str, idt, "AltSetting", priv->alt_setting); fu_common_string_append_kx(str, idt, "AltIdx", priv->alt_idx); if (priv->alt_name != NULL) fu_common_string_append_kv(str, idt, "AltName", priv->alt_name); if (priv->alt_name_for_display != NULL) { fu_common_string_append_kv(str, idt, "AltNameForDisplay", priv->alt_name_for_display); } 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_common_string_append_kv(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 = 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(priv->device), 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 = DFU_SECTOR_CAP_READABLE; break; case 'b': cap = DFU_SECTOR_CAP_ERASEABLE; break; case 'c': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_ERASEABLE; break; case 'd': cap = DFU_SECTOR_CAP_WRITEABLE; break; case 'e': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_WRITEABLE; break; case 'f': cap = DFU_SECTOR_CAP_ERASEABLE | DFU_SECTOR_CAP_WRITEABLE; break; case 'g': cap = DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_ERASEABLE | 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 */ DFU_SECTOR_CAP_ERASEABLE | DFU_SECTOR_CAP_READABLE | 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); g_free(priv->alt_name_for_display); priv->alt_name_for_display = g_strdup(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) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); guint polling_count = 0; /* get the status */ if (!fu_dfu_device_refresh(priv->device, error)) return FALSE; /* wait for FU_DFU_STATE_DFU_MANIFEST to not be set */ while (fu_dfu_device_get_state(priv->device) == FU_DFU_STATE_DFU_MANIFEST_SYNC || fu_dfu_device_get_state(priv->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; } g_usleep((fu_dfu_device_get_download_timeout(priv->device) + 1000) * 1000); if (!fu_dfu_device_refresh(priv->device, error)) return FALSE; } /* in an error state */ if (fu_dfu_device_get_state(priv->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(priv->device))); return FALSE; } return TRUE; } gboolean fu_dfu_target_check_status(FuDfuTarget *self, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); FuDfuStatus status; g_autoptr(GTimer) timer = g_timer_new(); /* get the status */ if (!fu_dfu_device_refresh(priv->device, error)) return FALSE; /* wait for dfuDNBUSY to not be set */ while (fu_dfu_device_get_state(priv->device) == FU_DFU_STATE_DFU_DNBUSY) { g_debug("waiting for FU_DFU_STATE_DFU_DNBUSY to clear"); g_usleep(fu_dfu_device_get_download_timeout(priv->device) * 1000); if (!fu_dfu_device_refresh(priv->device, 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(priv->device) != FU_DFU_STATE_DFU_ERROR) return TRUE; /* STM32-specific long errors */ status = fu_dfu_device_get_status(priv->device); if (fu_dfu_device_get_version(priv->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) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(priv->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(priv->device, error)) return FALSE; /* use the correct setting */ if (fu_device_has_flag(FU_DEVICE(priv->device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!g_usb_device_set_interface_alt(usb_device, (gint)fu_dfu_device_get_interface(priv->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(priv->device), error_local->message); return FALSE; } } return TRUE; } void fu_dfu_target_set_alt_name(FuDfuTarget *self, const gchar *alt_name) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->alt_name, alt_name) == 0) return; g_free(priv->alt_name); priv->alt_name = g_strdup(alt_name); } void fu_dfu_target_set_device(FuDfuTarget *self, FuDfuDevice *device) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_set_object(&priv->device, device); /* if we try to ref the target and destroy the device */ g_object_add_weak_pointer(G_OBJECT(priv->device), (gpointer *)&priv->device); } /** * 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(priv->device); 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(FU_DEVICE(priv->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_dfu_target_set_alt_name(self, "@Internal Flash /0x8000000/8*1Kg"); } else if (serial[2] == '4') { fu_dfu_target_set_alt_name(self, "@Internal Flash /0x8000000/16*1Kg"); } else if (serial[2] == '6') { fu_dfu_target_set_alt_name(self, "@Internal Flash /0x8000000/32*1Kg"); } else if (serial[2] == '8') { fu_dfu_target_set_alt_name(self, "@Internal Flash /0x8000000/64*1Kg"); } else if (serial[2] == 'B') { fu_dfu_target_set_alt_name(self, "@Internal Flash /0x8000000/128*1Kg"); } else if (serial[2] == 'D') { fu_dfu_target_set_alt_name(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 && priv->alt_name == NULL) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(priv->device)); priv->alt_name = g_usb_device_get_string_descriptor(usb_device, priv->alt_idx, NULL); } /* parse the DfuSe format according to UM0424 */ if (priv->sectors->len == 0) { if (!fu_dfu_target_parse_sectors(self, priv->alt_name, 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 */ DFU_SECTOR_CAP_READABLE | DFU_SECTOR_CAP_WRITEABLE); g_debug("no UM0424 sector description in %s", priv->alt_name); g_ptr_array_add(priv->sectors, sector); } priv->done_setup = TRUE; return TRUE; } /** * fu_dfu_target_mass_erase: * @self: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Mass erases the device clearing all SRAM and EEPROM memory. * * IMPORTANT: This only works on STM32 devices from ST and AVR32 devices from Atmel. * * Returns: %TRUE for success **/ gboolean fu_dfu_target_mass_erase(FuDfuTarget *self, FuProgress *progress, GError **error) { FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); if (!fu_dfu_target_setup(self, error)) return FALSE; if (klass->mass_erase == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "mass erase not supported"); return FALSE; } return klass->mass_erase(self, progress, error); } gboolean fu_dfu_target_download_chunk(FuDfuTarget *self, guint16 index, GBytes *bytes, FuProgress *progress, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(priv->device)); g_autoptr(GError) error_local = NULL; gsize actual_length; /* low level packet debugging */ if (g_getenv("FWUPD_DFU_VERBOSE") != NULL) fu_common_dump_bytes(G_LOG_DOMAIN, "Message", bytes); 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(priv->device), (guint8 *)g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), &actual_length, fu_dfu_device_get_timeout(priv->device), NULL, &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(priv->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 */ if (fu_dfu_device_get_version(priv->device) == FU_DFU_FIRMARE_VERSION_DFUSE) { if (!fu_dfu_device_refresh(priv->device, error)) return FALSE; } /* wait for the device to write contents to the EEPROM */ if (g_bytes_get_size(bytes) == 0 && fu_dfu_device_get_download_timeout(priv->device) > 0) fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); if (fu_dfu_device_get_download_timeout(priv->device) > 0) { g_debug("sleeping for %ums…", fu_dfu_device_get_download_timeout(priv->device)); g_usleep(fu_dfu_device_get_download_timeout(priv->device) * 1000); } /* 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, ==, g_bytes_get_size(bytes)); return TRUE; } GBytes * fu_dfu_target_upload_chunk(FuDfuTarget *self, guint16 index, gsize buf_sz, FuProgress *progress, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(priv->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(priv->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(priv->device), buf, buf_sz, &actual_length, fu_dfu_device_get_timeout(priv->device), NULL, &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(priv->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 */ if (g_getenv("FWUPD_DFU_VERBOSE") != NULL) fu_common_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; } FuDfuDevice * fu_dfu_target_get_device(FuDfuTarget *self) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); return priv->device; } gboolean fu_dfu_target_attach(FuDfuTarget *self, FuProgress *progress, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(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(priv->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) { FuDfuTargetPrivate *priv = GET_PRIVATE(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(priv->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_dfu_device_can_upload(priv->device)) { 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, priv->alt_name); 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) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); guint32 nr_chunks; guint16 transfer_size = fu_dfu_device_get_transfer_size(priv->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(GBytes) bytes_tmp = NULL; /* caclulate 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) { length = g_bytes_get_size(bytes) - offset; if (length > transfer_size) length = transfer_size; bytes_tmp = fu_common_bytes_new_offset(bytes, offset, length, error); if (bytes_tmp == NULL) return FALSE; } else { bytes_tmp = g_bytes_new(NULL, 0); } g_debug("writing #%04x chunk of size %" G_GSIZE_FORMAT, i, g_bytes_get_size(bytes_tmp)); if (!fu_dfu_target_download_chunk(self, i, bytes_tmp, 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) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); /* progress */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY && fu_dfu_device_has_attribute(priv->device, FU_DFU_DEVICE_ATTR_CAN_UPLOAD)) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 4); } 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_dfu_device_has_attribute(priv->device, FU_DFU_DEVICE_ATTR_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) { 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_dfu_device_can_download(priv->device)) { 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(FU_DEVICE(priv->device), FU_DFU_DEVICE_FLAG_MANIFEST_POLL) && fu_dfu_device_has_attribute(priv->device, FU_DFU_DEVICE_ATTR_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; } /** * fu_dfu_target_get_alt_name: * @self: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Gets the alternate setting name to use for this interface. * * Returns: the alternative setting name, typically %NULL **/ const gchar * fu_dfu_target_get_alt_name(FuDfuTarget *self, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_TARGET(self), NULL); /* ensure populated */ if (!fu_dfu_target_setup(self, error)) return NULL; /* nothing */ if (priv->alt_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no alt-name"); return NULL; } return priv->alt_name; } /** * fu_dfu_target_get_alt_name_for_display: * @self: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Gets the alternate setting name to use for this interface that can be * shown on the display. * * Returns: the alternative setting name **/ const gchar * fu_dfu_target_get_alt_name_for_display(FuDfuTarget *self, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_TARGET(self), NULL); /* ensure populated */ if (!fu_dfu_target_setup(self, error)) return NULL; /* nothing */ if (priv->alt_name_for_display == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no alt-name for display"); return NULL; } return priv->alt_name_for_display; } fwupd-1.7.5/plugins/dfu/fu-dfu-target.h000066400000000000000000000054251420024370600177370ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #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, GUsbDevice) /** * 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 { GUsbDeviceClass 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); const gchar * fu_dfu_target_get_alt_name(FuDfuTarget *self, GError **error); const gchar * fu_dfu_target_get_alt_name_for_display(FuDfuTarget *self, GError **error); 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); gboolean fu_dfu_target_mass_erase(FuDfuTarget *self, FuProgress *progress, GError **error); void fu_dfu_target_to_string(FuDfuTarget *self, guint idt, GString *str); fwupd-1.7.5/plugins/dfu/fu-dfu-tool.c000066400000000000000000000564551420024370600174320ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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(FuDfuDevice) device = NULL; 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; } device = fu_dfu_device_new(usb_device); fu_device_set_context(FU_DEVICE(device), self->ctx); return g_steal_pointer(&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(usb_device); fu_device_set_context(FU_DEVICE(device), self->ctx); 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, 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, 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, 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_NONE; 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_common_get_contents_bytes(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, 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_dfu_device_has_attribute(device, FU_DFU_DEVICE_ATTR_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, _("Print verbose debug statements"), NULL}, {"device", 'd', 0, G_OPTION_ARG_STRING, &self->device_vid_pid, _("Specify Vendor/Product ID(s) of DFU device"), _("VID:PID")}, {"transfer-size", 't', 0, G_OPTION_ARG_STRING, &self->transfer_size, _("Specify the number of bytes per USB transfer"), _("BYTES")}, {"force", '\0', 0, G_OPTION_ARG_NONE, &self->force, _("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); /* 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) 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.7.5/plugins/dfu/fu-plugin-dfu.c000066400000000000000000000011531420024370600177340ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-device.h" static void fu_plugin_dfu_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_DFU_DEVICE); fu_context_add_quirk_key(ctx, "DfuAltName"); fu_context_add_quirk_key(ctx, "DfuForceTimeout"); fu_context_add_quirk_key(ctx, "DfuForceVersion"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_dfu_init; } fwupd-1.7.5/plugins/dfu/meson.build000066400000000000000000000042231420024370600172510ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginDfu"'] install_data(['dfu.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) dfu = static_library( 'dfu', fu_hash, sources : [ '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', ], dependencies : [ plugin_deps, libm, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], ) shared_module('fu_plugin_dfu', fu_hash, sources : [ 'fu-plugin-dfu.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, libm, ], link_with : [ fwupd, fwupdplugin, dfu, ], ) if get_option('compat_cli') fu_dfu_tool = executable( 'dfu-tool', fu_hash, sources : [ 'fu-dfu-tool.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ dfu, fwupd, fwupdplugin, ], c_args : cargs, install : true, install_dir : bindir ) endif if get_option('compat_cli') and get_option('man') configure_file( input : 'dfu-tool.1', output : 'dfu-tool.1', configuration : conf, 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', fu_hash, sources : [ 'fu-dfu-self-test.c' ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, libm, ], link_with : [ dfu, fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('fu-dfu-self-test', e, env : env) # added to installed-tests endif plugindfu_incdir = include_directories('.') endif fwupd-1.7.5/plugins/ebitdo/000077500000000000000000000000001420024370600155765ustar00rootroot00000000000000fwupd-1.7.5/plugins/ebitdo/README.md000066400000000000000000000030141420024370600170530ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_2DC8&PID_AB11` * `USB\VID_2DC8` ## 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`. fwupd-1.7.5/plugins/ebitdo/data/000077500000000000000000000000001420024370600165075ustar00rootroot00000000000000fwupd-1.7.5/plugins/ebitdo/data/m30.txt000066400000000000000000000175211420024370600176550ustar00rootroot00000000000000Bus 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.7.5/plugins/ebitdo/data/nes30pro.txt000066400000000000000000000107671420024370600207340ustar00rootroot00000000000000Bus 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.7.5/plugins/ebitdo/data/scf30.txt000066400000000000000000000106001420024370600201630ustar00rootroot00000000000000 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.7.5/plugins/ebitdo/data/sf30pro.txt000066400000000000000000000303401420024370600205440ustar00rootroot00000000000000Start + 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.7.5/plugins/ebitdo/data/update.csv000066400000000000000000032101711420024370600205130ustar00rootroot00000000000000# Total Phase Data Center(tm) v6.63 # (c) 2005-2015 Total Phase, Inc. # www.totalphase.com # # Fri Jun 03 12:02:30 2016 # # Level,Sp,Index,m:s.ms.us,Dur,Len,Err,Dev,Ep,Record,Summary 0,,0,0:00.000.000,,,,,,Capture started (Aggregate),[Fri 03 Jun 2016 12:00:01 BST] 0,,1,0:00.000.024,,,,,, / , 0,,2,0:09.124.398,,,,,, / , 0,,3,0:09.565.223,,,,,, / , 0,,4,0:09.643.335,,,,,, / , 0,,5,0:10.095.673,,,,,, / , 0,,6,0:10.126.773,,,,,, / , 0,,7,0:10.127.444,31.007.125 ms,,,,,[32 SOF],[Frames: 1881 - 1912] 0,,8,0:10.158.452,13.000 us,8 B,,00,00,SETUP txn,80 06 00 01 00 00 40 00 0,,12,0:10.159.448,2.812 us,,,,,[1 SOF],[Frame: 1913] 0,,13,0:10.159.451,20.229 us,18 B,,00,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,17,0:10.160.448,1.002.958 ms,,,,,[2 SOF],[Frames: 1914 - 1915] 0,,18,0:10.161.452,7.666 us,0 B,,00,00,OUT txn, 0,,22,0:10.162.449,2.833 us,,,,,[1 SOF],[Frame: 1916] 0,,23,0:10.162.542,,,,,, / , 0,,24,0:10.189.147,,,,,, / , 0,,25,0:10.189.452,31.007.125 ms,,,,,[32 SOF],[Frames: 1943 - 1974] 0,,26,0:10.220.460,13.000 us,8 B,,00,00,SETUP txn,00 05 01 00 00 00 00 00 0,,30,0:10.221.457,2.812 us,,,,,[1 SOF],[Frame: 1975] 0,,31,0:10.221.460,8.229 us,0 B,,00,00,IN txn, 0,,35,0:10.222.457,30.007.000 ms,,,,,[31 SOF],[Frames: 1976 - 2006] 0,,36,0:10.252.465,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,40,0:10.253.461,2.895 us,,,,,[1 SOF],[Frame: 2007] 0,,41,0:10.253.465,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,45,0:10.254.461,2.812 us,,,,,[1 SOF],[Frame: 2008] 0,,46,0:10.254.465,7.645 us,0 B,,01,00,OUT txn, 0,,50,0:10.255.462,1.003.041 ms,,,,,[2 SOF],[Frames: 2009 - 2010] 0,,51,0:10.256.465,13.083 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 FF 00 0,,55,0:10.257.462,2.812 us,,,,,[1 SOF],[Frame: 2011] 0,,56,0:10.257.465,35.729 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,60,0:10.258.462,1.002.958 ms,,,,,[2 SOF],[Frames: 2012 - 2013] 0,,61,0:10.259.465,7.666 us,0 B,,01,00,OUT txn, 0,,65,0:10.260.462,1.003.125 ms,,,,,[2 SOF],[Frames: 2014 - 2015] 0,,66,0:10.261.466,13.083 us,8 B,,01,00,SETUP txn,80 06 03 03 09 04 FF 00 0,,70,0:10.262.463,2.895 us,,,,,[1 SOF],[Frame: 2016] 0,,71,0:10.262.466,25.562 us,26 B,,01,00,IN txn,1A 03 38 00 42 00 69 00 74 00 64 00 6F 00 20 00 00 00 00 00 00 00 00 00… 0,,75,0:10.263.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2017 - 2018] 0,,76,0:10.264.466,7.666 us,0 B,,01,00,OUT txn, 0,,80,0:10.265.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2019 - 2020] 0,,81,0:10.266.466,13.062 us,8 B,,01,00,SETUP txn,80 06 00 03 00 00 FF 00 0,,85,0:10.267.463,2.916 us,,,,,[1 SOF],[Frame: 2021] 0,,86,0:10.267.467,10.916 us,4 B,,01,00,IN txn,04 03 09 04 0,,90,0:10.268.463,1.003.041 ms,,,,,[2 SOF],[Frames: 2022 - 2023] 0,,91,0:10.269.467,7.645 us,0 B,,01,00,OUT txn, 0,,95,0:10.270.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2024 - 2025] 0,,96,0:10.271.467,13.062 us,8 B,,01,00,SETUP txn,80 06 02 03 09 04 FF 00 0,,100,0:10.272.464,2.895 us,,,,,[1 SOF],[Frame: 2026] 0,,101,0:10.272.467,20.229 us,18 B,,01,00,IN txn,12 03 38 00 42 00 69 00 74 00 64 00 6F 00 20 00 20 00 0,,105,0:10.273.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2027 - 2028] 0,,106,0:10.274.468,7.645 us,0 B,,01,00,OUT txn, 0,,110,0:10.275.464,1.003.041 ms,,,,,[2 SOF],[Frames: 2029 - 2030] 0,,111,0:10.276.468,13.000 us,8 B,,01,00,SETUP txn,80 06 00 06 00 00 0A 00 0,,115,0:10.277.465,2.895 us,,,,,[1 SOF],[Frame: 2031] 0,,116,0:10.277.468,4.583 us,,,01,00,IN txn (STALL), 0,,119,0:10.278.465,4.003.458 ms,,,,,[5 SOF],[Frames: 2032 - 2036] 0,,120,0:10.282.469,13.020 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,124,0:10.283.465,2.895 us,,,,,[1 SOF],[Frame: 2037] 0,,125,0:10.283.469,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 83 04 50 57 00 02 01 02 03 01 0,,129,0:10.284.466,2.895 us,,,,,[1 SOF],[Frame: 2038] 0,,130,0:10.284.469,7.666 us,0 B,,01,00,OUT txn, 0,,134,0:10.285.466,1.003.041 ms,,,,,[2 SOF],[Frames: 2039 - 2040] 0,,135,0:10.286.470,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 09 00 0,,139,0:10.287.466,2.895 us,,,,,[1 SOF],[Frame: 2041] 0,,140,0:10.287.469,14.229 us,9 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 0,,144,0:10.288.466,2.916 us,,,,,[1 SOF],[Frame: 2042] 0,,145,0:10.288.469,7.666 us,0 B,,01,00,OUT txn, 0,,149,0:10.289.466,1.003.041 ms,,,,,[2 SOF],[Frames: 2043 - 2044] 0,,150,0:10.290.470,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 29 00 0,,154,0:10.291.467,3.000 us,,,,,[1 SOF],[Frame: 2045] 0,,155,0:10.291.470,35.750 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,159,0:10.292.467,2.979 us,,,,,[1 SOF],[Frame: 2046] 0,,160,0:10.292.470,7.666 us,0 B,,01,00,OUT txn, 0,,164,0:10.293.467,1.002.958 ms,,,,,[2 SOF],[Frames: 2047 - 0] 0,,165,0:10.294.472,13.000 us,8 B,,01,00,SETUP txn,00 09 01 00 00 00 00 00 0,,169,0:10.295.467,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,170,0:10.295.470,8.229 us,0 B,,01,00,IN txn, 0,,174,0:10.296.467,2.812 us,,,,,[1 SOF],[Frame: 2] 0,,175,0:10.296.471,13.000 us,8 B,,01,00,SETUP txn,21 0A 00 00 00 00 00 00 0,,179,0:10.297.467,2.833 us,,,,,[1 SOF],[Frame: 3] 0,,180,0:10.297.471,4.604 us,,,01,00,IN txn (STALL), 0,,183,0:10.298.468,1.002.958 ms,,,,,[2 SOF],[Frames: 4 - 5] 0,,184,0:10.299.473,13.083 us,8 B,,01,00,SETUP txn,81 06 00 22 00 00 61 00 0,,188,0:10.300.468,2.833 us,,,,,[1 SOF],[Frame: 6] 0,,189,0:10.300.471,30.416 us,33 B,,01,00,IN txn,05 8C 09 01 A1 01 09 03 15 00 26 00 FF 75 08 95 40 81 02 09 04 15 00 26… 0,,193,0:10.301.468,1.002.958 ms,,,,,[2 SOF],[Frames: 7 - 8] 0,,194,0:10.302.471,7.666 us,0 B,,01,00,OUT txn, 0,,198,0:10.303.468,88.015.041 ms,,,,,[89 SOF],[Frames: 9 - 97] 0,,199,0:10.391.484,50.312 us,64 B,,01,01,OUT txn,05 00 16 01 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,203,0:10.392.481,207.031.562 ms,,,,,[208 SOF],[Frames: 98 - 305] 0,,204,0:10.599.513,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,208,0:10.600.509,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,209,0:10.326.475,288.090.895 ms,64 B,,01,02,IN txn [9 POLL],0C 00 16 07 00 04 04 00 0B 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,214,0:10.615.512,192.029.479 ms,,,,,[193 SOF],[Frames: 321 - 513] 0,,215,0:10.807.541,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,219,0:10.808.538,30.006.979 ms,,,,,[31 SOF],[Frames: 514 - 544] 0,,220,0:10.646.519,192.077.562 ms,64 B,,01,02,IN txn [6 POLL],0C 00 16 07 00 04 04 00 0B 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,225,0:10.839.543,1.999.280.375 s,,,,,[2000 SOF],[Frames: 545 - 496] [Periodic Timeout] 0,,226,0:10.870.550,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,227,0:12.839.820,1.999.280.375 s,,,,,[2000 SOF],[Frames: 497 - 448] [Periodic Timeout] 0,,228,0:12.886.830,1.984.280.062 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,229,0:14.840.098,1.999.280.375 s,,,,,[2000 SOF],[Frames: 449 - 400] [Periodic Timeout] 0,,230,0:14.903.110,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,231,0:16.840.376,1.999.280.375 s,,,,,[2000 SOF],[Frames: 401 - 352] [Periodic Timeout] 0,,232,0:16.919.390,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,233,0:18.840.653,288.042.812 ms,,,,,[289 SOF],[Frames: 353 - 641] 0,,234,0:19.128.697,50.333 us,64 B,,01,01,OUT txn,05 00 1A 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,238,0:19.129.694,30.006.979 ms,,,,,[31 SOF],[Frames: 642 - 672] 0,,239,0:18.935.670,224.082.354 ms,64 B,,01,02,IN txn [7 POLL],2D 00 19 24 00 7F 1E 50 33 39 31 32 02 4E 39 31 38 2C BD CB 0F 9A 89 12… 0,,244,0:19.160.698,1.999.280.354 s,,,,,[2000 SOF],[Frames: 673 - 624] [Periodic Timeout] 0,,245,0:19.191.705,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,246,0:21.160.975,1.999.280.375 s,,,,,[2000 SOF],[Frames: 625 - 576] [Periodic Timeout] 0,,247,0:21.207.985,1.984.280.020 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,248,0:23.161.253,1.999.280.354 s,,,,,[2000 SOF],[Frames: 577 - 528] [Periodic Timeout] 0,,249,0:23.224.265,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,250,0:25.161.531,1.999.280.375 s,,,,,[2000 SOF],[Frames: 529 - 480] [Periodic Timeout] 0,,251,0:25.240.545,1.984.280.041 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,252,0:27.161.809,1.552.218.375 s,,,,,[1553 SOF],[Frames: 481 - 2033] 0,,253,0:28.714.027,50.333 us,64 B,,01,01,OUT txn,23 00 16 1F 00 01 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,257,0:28.715.024,15.004.916 ms,,,,,[16 SOF],[Frames: 2034 - 1] 0,,258,0:28.730.029,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,262,0:27.256.825,1.984.280.020 s,,,01,02,[63 IN-NAK],[Periodic Timeout] 0,,263,0:28.731.026,1.839.258.145 s,,,,,[1840 SOF],[Frames: 2 - 1841] 0,,264,0:28.746.032,1.824.303.562 s,64 B,,01,01,OUT txn [114 POLL],05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,725,0:30.571.282,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,726,0:29.273.105,1.312.233.062 s,64 B,,01,02,IN txn [41 POLL],06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,731,0:30.586.284,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,732,0:30.586.287,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 1C 00 0B 01 00 00 00 A0 00 08 00 B8 00 00 80 68 CF 01… 0,,736,0:30.587.284,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,737,0:30.602.289,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 7D 28 D4 3A 00 4A F1 30 AC 25 4C FD 08 67 17… 0,,741,0:30.603.286,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,742,0:30.617.291,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,746,0:30.618.288,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,747,0:30.618.292,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 7D 28 D4 3A 00 4A F1 30 AC 25 4C FD 08 67 17… 0,,751,0:30.619.288,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,752,0:30.634.294,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 E3 13 0F 27 61 F6 24 5D 4B B2 25 D4 AF 56 6E… 0,,756,0:30.635.291,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,757,0:30.649.296,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,761,0:30.650.293,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,762,0:30.650.296,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 E3 13 0F 27 61 F6 24 5D 4B B2 25 D4 AF 56 6E… 0,,766,0:30.651.293,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,767,0:30.666.298,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C DD 4E 02 98 67 3D 24 80 29 06 7C CB A8 FD 44… 0,,771,0:30.667.295,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,772,0:30.681.300,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,776,0:30.682.297,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,777,0:30.682.300,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C DD 4E 02 98 67 3D 24 80 29 06 7C CB A8 FD 44… 0,,781,0:30.683.297,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,782,0:30.698.303,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 6A 81 1E F9 A0 8D 24 E8 A3 35 4C E8 67 F3 F9… 0,,786,0:30.699.300,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,787,0:30.713.305,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,791,0:30.714.302,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,792,0:30.714.305,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 6A 81 1E F9 A0 8D 24 E8 A3 35 4C E8 67 F3 F9… 0,,796,0:30.715.302,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,797,0:30.730.307,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 32 52 7B 9D C1 93 B1 88 79 49 FB 02 32 9F BA… 0,,801,0:30.731.304,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,802,0:30.745.309,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,806,0:30.746.306,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,807,0:30.746.309,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 32 52 7B 9D C1 93 B1 88 79 49 FB 02 32 9F BA… 0,,811,0:30.747.306,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,812,0:30.762.312,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 08 B8 FC B9 85 51 F6 32 2E E3 BD EF 38 77 72… 0,,816,0:30.763.308,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,817,0:30.777.314,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,821,0:30.778.311,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,822,0:30.778.314,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 08 B8 FC B9 85 51 F6 32 2E E3 BD EF 38 77 72… 0,,826,0:30.779.311,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,827,0:30.794.316,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 EC AF 7B 87 DB 1A 24 35 70 6F 77 0B 07 AE 30… 0,,831,0:30.795.313,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,832,0:30.809.318,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,836,0:30.810.315,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,837,0:30.810.318,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 EC AF 7B 87 DB 1A 24 35 70 6F 77 0B 07 AE 30… 0,,841,0:30.811.315,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,842,0:30.826.320,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 91 59 84 CB 48 C0 82 4C D9 EB C5 7E 78 F1 0B… 0,,846,0:30.827.317,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,847,0:30.841.323,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,851,0:30.842.319,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,852,0:30.842.323,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 91 59 84 CB 48 C0 82 4C D9 EB C5 7E 78 F1 0B… 0,,856,0:30.843.320,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,857,0:30.858.325,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE BA 5B 62 0B 67 2F A6 8F 71 D4 C3 30 08 C9 B7… 0,,861,0:30.859.322,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,862,0:30.873.327,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,866,0:30.874.324,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,867,0:30.874.327,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE BA 5B 62 0B 67 2F A6 8F 71 D4 C3 30 08 C9 B7… 0,,871,0:30.875.324,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,872,0:30.890.329,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 BF D6 CF DE B4 36 32 1D 5C 56 AE 14 03 70 42… 0,,876,0:30.891.326,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,877,0:30.905.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,881,0:30.906.328,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,882,0:30.906.332,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 BF D6 CF DE B4 36 32 1D 5C 56 AE 14 03 70 42… 0,,886,0:30.907.328,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,887,0:30.922.334,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 24 EF 66 05 78 4C 13 3E AA C1 D2 E9 C7 6C 41… 0,,891,0:30.923.331,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,892,0:30.937.336,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,896,0:30.938.333,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,897,0:30.938.336,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 24 EF 66 05 78 4C 13 3E AA C1 D2 E9 C7 6C 41… 0,,901,0:30.939.333,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,902,0:30.954.338,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B B7 26 DC 03 B1 E2 C0 F6 AB 95 C8 C6 8A B8 77… 0,,906,0:30.955.335,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,907,0:30.969.340,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,911,0:30.970.337,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,912,0:30.970.340,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B B7 26 DC 03 B1 E2 C0 F6 AB 95 C8 C6 8A B8 77… 0,,916,0:30.971.337,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,917,0:30.986.343,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 68 58 E9 BE C1 35 C5 D8 04 E9 3B E4 CB 8C 0E… 0,,921,0:30.987.340,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,922,0:31.001.345,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,926,0:31.002.342,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,927,0:31.002.345,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 68 58 E9 BE C1 35 C5 D8 04 E9 3B E4 CB 8C 0E… 0,,931,0:31.003.342,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,932,0:31.018.347,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 21 BB 73 CE 2E 26 27 1C DF E7 01 8A 78 1C BC… 0,,936,0:31.019.344,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,937,0:31.033.349,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,941,0:31.034.346,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,942,0:31.034.349,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 21 BB 73 CE 2E 26 27 1C DF E7 01 8A 78 1C BC… 0,,946,0:31.035.346,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,947,0:31.050.352,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 98 9B 98 3F D8 A7 7B FF FD A5 2B 32 CF 42 E3… 0,,951,0:31.051.348,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,952,0:31.065.354,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,956,0:31.066.351,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,957,0:31.066.354,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 98 9B 98 3F D8 A7 7B FF FD A5 2B 32 CF 42 E3… 0,,961,0:31.067.351,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,962,0:31.082.356,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC C5 DE 5D 0C A9 27 3D 37 88 70 55 0A AF 1B 33… 0,,966,0:31.083.353,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,967,0:31.097.358,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,971,0:31.098.355,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,972,0:31.098.358,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC C5 DE 5D 0C A9 27 3D 37 88 70 55 0A AF 1B 33… 0,,976,0:31.099.355,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,977,0:31.114.360,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 77 F3 6D 38 6A 16 2E F4 76 80 DD 73 3A D4 88… 0,,981,0:31.115.357,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,982,0:31.129.363,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,986,0:31.130.359,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,987,0:31.130.363,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 77 F3 6D 38 6A 16 2E F4 76 80 DD 73 3A D4 88… 0,,991,0:31.131.360,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,992,0:31.146.365,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 B9 74 C4 E1 68 3B 17 91 E7 22 F0 F4 91 77 E2… 0,,996,0:31.147.362,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,997,0:31.161.367,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1001,0:31.162.364,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,1002,0:31.178.369,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1006,0:31.179.366,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,1007,0:31.193.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1011,0:31.194.368,16.005.041 ms,,,,,[17 SOF],[Frames: 417 - 433] 0,,1012,0:31.210.374,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1016,0:31.211.371,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,1017,0:31.225.376,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1021,0:31.226.373,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,1022,0:31.226.376,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 A6 A0 56 CA 7E 26 FF AB EE 6A 14 00 AB 77 D6… 0,,1026,0:31.227.373,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,1027,0:31.242.378,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 90 84 4C FF 0A E7 3B 9A AC 33 6A A2 52 E2 A4… 0,,1031,0:31.243.375,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,1032,0:31.257.380,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1036,0:31.258.377,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,1037,0:31.258.380,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 90 84 4C FF 0A E7 3B 9A AC 33 6A A2 52 E2 A4… 0,,1041,0:31.259.377,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,1042,0:31.274.383,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 97 FC 22 EC 53 D1 BB 1F C2 4E D3 05 56 1E A9… 0,,1046,0:31.275.380,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,1047,0:31.289.385,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1051,0:31.290.382,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,1052,0:31.290.385,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 97 FC 22 EC 53 D1 BB 1F C2 4E D3 05 56 1E A9… 0,,1056,0:31.291.382,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,1057,0:31.306.387,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 19 3C 8C 2F C8 7B 3A 5E 28 74 86 64 6E 97 28… 0,,1061,0:31.307.384,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,1062,0:31.321.389,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1066,0:31.322.386,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,1067,0:31.322.389,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 19 3C 8C 2F C8 7B 3A 5E 28 74 86 64 6E 97 28… 0,,1071,0:31.323.386,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,1072,0:31.338.392,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 68 E4 2B F6 74 64 05 5E C3 F7 B4 46 E6 8C BC… 0,,1076,0:31.339.388,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,1077,0:31.353.394,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1081,0:31.354.391,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,1082,0:31.354.394,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 68 E4 2B F6 74 64 05 5E C3 F7 B4 46 E6 8C BC… 0,,1086,0:31.355.391,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,1087,0:31.370.396,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 F8 20 A8 A5 6A 61 16 FD 82 60 00 4B 05 DB 2D… 0,,1091,0:31.371.393,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,1092,0:31.385.398,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1096,0:31.386.395,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,1097,0:31.386.398,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 97 F8 20 A8 A5 6A 61 16 FD 82 60 00 4B 05 DB 2D… 0,,1101,0:31.387.395,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,1102,0:31.402.400,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 7E 0A FF 96 2C E8 4C A1 D0 BE E3 3A 36 18 28… 0,,1106,0:31.403.397,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,1107,0:31.417.403,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1111,0:31.418.399,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,1112,0:31.418.403,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 7E 0A FF 96 2C E8 4C A1 D0 BE E3 3A 36 18 28… 0,,1116,0:31.419.400,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,1117,0:31.434.405,50.854 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF D8 5F 12 48 C6 CD 87 70 93 E0 CE C2 C8 92 FF… 0,,1121,0:31.435.402,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,1122,0:31.449.407,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1126,0:31.450.404,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,1127,0:31.450.407,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF D8 5F 12 48 C6 CD 87 70 93 E0 CE C2 C8 92 FF… 0,,1131,0:31.451.404,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,1132,0:31.466.409,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 27 3E 1F C9 6F 01 DD A9 D7 33 14 B9 DE 56 08… 0,,1136,0:31.467.406,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,1137,0:31.481.411,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1141,0:31.482.408,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,1142,0:31.482.412,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 27 3E 1F C9 6F 01 DD A9 D7 33 14 B9 DE 56 08… 0,,1146,0:31.483.408,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,1147,0:31.498.414,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 26 6B 5E 85 30 FB 6A 78 D9 C4 D1 71 0D 01 AA… 0,,1151,0:31.499.411,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,1152,0:31.513.416,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1156,0:31.514.413,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,1157,0:31.514.416,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 26 6B 5E 85 30 FB 6A 78 D9 C4 D1 71 0D 01 AA… 0,,1161,0:31.515.413,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,1162,0:31.530.418,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 49 B1 42 9A AD 07 51 4A 3B 9F 83 F5 3A 52 98… 0,,1166,0:31.531.415,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,1167,0:31.545.420,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1171,0:31.546.417,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,1172,0:31.546.420,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 49 B1 42 9A AD 07 51 4A 3B 9F 83 F5 3A 52 98… 0,,1176,0:31.547.417,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,1177,0:31.562.423,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 91 B9 91 C2 AD A6 00 99 92 1B 63 B8 60 D0 26… 0,,1181,0:31.563.420,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,1182,0:31.577.425,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1186,0:31.578.422,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,1187,0:31.578.425,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 91 B9 91 C2 AD A6 00 99 92 1B 63 B8 60 D0 26… 0,,1191,0:31.579.422,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,1192,0:31.594.427,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 3A 70 87 32 41 29 84 34 5E 03 54 85 C0 C4 9D… 0,,1196,0:31.595.424,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,1197,0:31.609.429,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1201,0:31.610.426,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,1202,0:31.610.429,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 3A 70 87 32 41 29 84 34 5E 03 54 85 C0 C4 9D… 0,,1206,0:31.611.426,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,1207,0:31.626.432,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 9F 96 DB 76 CC A0 2D 87 D2 F0 74 2E C9 B8 F9… 0,,1211,0:31.627.428,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,1212,0:31.641.434,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1216,0:31.642.431,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,1217,0:31.642.434,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 9F 96 DB 76 CC A0 2D 87 D2 F0 74 2E C9 B8 F9… 0,,1221,0:31.643.431,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,1222,0:31.658.436,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 D2 EA CB FE 7B 7B E3 FE 98 D9 6D F3 D4 9F 4C… 0,,1226,0:31.659.433,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,1227,0:31.673.438,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1231,0:31.674.435,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,1232,0:31.674.438,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 D2 EA CB FE 7B 7B E3 FE 98 D9 6D F3 D4 9F 4C… 0,,1236,0:31.675.435,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,1237,0:31.690.440,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 2C B9 B8 67 65 0E 62 6A 12 00 B9 51 93 AB 76… 0,,1241,0:31.691.437,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,1242,0:31.705.443,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1246,0:31.706.439,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,1247,0:31.706.443,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 2C B9 B8 67 65 0E 62 6A 12 00 B9 51 93 AB 76… 0,,1251,0:31.707.440,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,1252,0:31.722.445,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 54 91 CD 15 CF 37 70 1E 40 83 70 52 33 C6 A1… 0,,1256,0:31.723.442,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,1257,0:31.737.447,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1261,0:31.738.444,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,1262,0:31.738.447,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 54 91 CD 15 CF 37 70 1E 40 83 70 52 33 C6 A1… 0,,1266,0:31.739.444,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,1267,0:31.754.449,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 0A F6 77 15 90 B5 B0 6A AC EC B9 56 F8 51 C8… 0,,1271,0:31.755.446,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,1272,0:31.769.451,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1276,0:31.770.448,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,1277,0:31.786.454,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 20 64 E9 0E 99 B4 72 5F DC 48 F6 3A F2 B3 C6… 0,,1281,0:31.787.451,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,1282,0:31.801.456,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1286,0:31.802.453,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,1287,0:31.802.456,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 20 64 E9 0E 99 B4 72 5F DC 48 F6 3A F2 B3 C6… 0,,1291,0:31.803.453,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,1292,0:31.818.458,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B B1 5A 26 3A B6 1A BC CC 28 BA 62 86 70 89 1E… 0,,1296,0:31.819.455,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,1297,0:31.833.460,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1301,0:31.834.457,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,1302,0:31.834.460,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B B1 5A 26 3A B6 1A BC CC 28 BA 62 86 70 89 1E… 0,,1306,0:31.835.457,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,1307,0:31.850.463,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C B0 C5 8E D3 6B F4 2A E7 D1 E0 EB 2B 22 B9 2C… 0,,1311,0:31.851.460,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,1312,0:31.865.465,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1316,0:31.866.462,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,1317,0:31.866.465,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C B0 C5 8E D3 6B F4 2A E7 D1 E0 EB 2B 22 B9 2C… 0,,1321,0:31.867.462,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,1322,0:31.882.467,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 F1 7E 3F BA 67 BA 7C DE DD 8B 8C 98 9F C6 F5… 0,,1326,0:31.883.464,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,1327,0:31.897.469,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1331,0:31.898.466,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,1332,0:31.898.469,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 F1 7E 3F BA 67 BA 7C DE DD 8B 8C 98 9F C6 F5… 0,,1336,0:31.899.466,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,1337,0:31.914.472,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 C8 8B 4D CE A3 EC FC EE 20 B9 53 00 2E 2F 67… 0,,1341,0:31.915.468,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,1342,0:31.929.474,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1346,0:31.930.471,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,1347,0:31.930.474,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 C8 8B 4D CE A3 EC FC EE 20 B9 53 00 2E 2F 67… 0,,1351,0:31.931.471,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,1352,0:31.946.476,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 0C 95 20 4C 59 B9 B1 E0 43 20 E8 00 29 C1 FA… 0,,1356,0:31.947.473,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,1357,0:31.961.478,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1361,0:31.962.475,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,1362,0:31.962.478,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 0C 95 20 4C 59 B9 B1 E0 43 20 E8 00 29 C1 FA… 0,,1366,0:31.963.475,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,1367,0:31.978.480,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A B6 DC FD A0 C5 1B 37 C6 6F 8C C3 63 23 2C E8… 0,,1371,0:31.979.477,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,1372,0:31.993.483,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1376,0:31.994.479,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,1377,0:31.994.483,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A B6 DC FD A0 C5 1B 37 C6 6F 8C C3 63 23 2C E8… 0,,1381,0:31.995.480,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,1382,0:32.010.485,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 EB 47 C8 52 F3 28 09 82 7E F6 91 B3 24 8A 6B… 0,,1386,0:32.011.482,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,1387,0:32.025.487,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1391,0:32.026.484,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,1392,0:32.026.487,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 EB 47 C8 52 F3 28 09 82 7E F6 91 B3 24 8A 6B… 0,,1396,0:32.027.484,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,1397,0:32.042.489,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB B1 35 6D B3 AD E0 D5 08 64 94 45 F1 00 14 7A… 0,,1401,0:32.043.486,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,1402,0:32.057.491,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1406,0:32.058.488,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,1407,0:32.058.492,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB B1 35 6D B3 AD E0 D5 08 64 94 45 F1 00 14 7A… 0,,1411,0:32.059.488,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,1412,0:32.074.494,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 F7 F9 DC 55 01 23 D1 90 80 05 69 01 80 90 5E… 0,,1416,0:32.075.491,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,1417,0:32.089.496,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1421,0:32.090.493,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,1422,0:32.090.496,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 F7 F9 DC 55 01 23 D1 90 80 05 69 01 80 90 5E… 0,,1426,0:32.091.493,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,1427,0:32.106.498,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 0B 1E 13 8D B2 6E EF 95 57 69 90 3E CB 86 D9… 0,,1431,0:32.107.495,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,1432,0:32.121.500,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1436,0:32.122.497,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,1437,0:32.122.500,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 0B 1E 13 8D B2 6E EF 95 57 69 90 3E CB 86 D9… 0,,1441,0:32.123.497,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,1442,0:32.138.503,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 45 AD E8 A2 46 6F E0 2F 78 FB AD B6 1E 13 9B… 0,,1446,0:32.139.500,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,1447,0:32.153.505,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1451,0:32.154.502,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,1452,0:32.154.505,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 45 AD E8 A2 46 6F E0 2F 78 FB AD B6 1E 13 9B… 0,,1456,0:32.155.502,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,1457,0:32.170.507,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 F7 0A F2 B5 7E 4D 07 06 B9 A5 AA 13 56 98 A0… 0,,1461,0:32.171.504,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,1462,0:32.185.509,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1466,0:32.186.506,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,1467,0:32.186.509,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 F7 0A F2 B5 7E 4D 07 06 B9 A5 AA 13 56 98 A0… 0,,1471,0:32.187.506,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,1472,0:32.202.512,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 07 28 27 AD 79 8B EA FF 90 30 CD ED 1E 7E 65… 0,,1476,0:32.203.508,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,1477,0:32.217.514,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1481,0:32.218.510,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,1482,0:32.218.514,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 07 28 27 AD 79 8B EA FF 90 30 CD ED 1E 7E 65… 0,,1486,0:32.219.511,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,1487,0:32.234.516,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D EF A8 CB C9 99 CE D2 69 EB 90 72 17 08 01 14… 0,,1491,0:32.235.513,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,1492,0:32.249.518,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1496,0:32.250.515,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,1497,0:32.250.518,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D EF A8 CB C9 99 CE D2 69 EB 90 72 17 08 01 14… 0,,1501,0:32.251.515,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,1502,0:32.266.520,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A D5 7B F6 67 10 92 64 46 45 A4 48 D0 AC 08 87… 0,,1506,0:32.267.517,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,1507,0:32.281.523,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1511,0:32.282.519,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,1512,0:32.282.523,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A D5 7B F6 67 10 92 64 46 45 A4 48 D0 AC 08 87… 0,,1516,0:32.283.520,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,1517,0:32.298.525,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 D8 84 39 8E 3B 94 24 A1 4B 89 B9 3A 9D EB 33… 0,,1521,0:32.299.522,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,1522,0:32.313.527,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1526,0:32.314.524,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,1527,0:32.314.527,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 D8 84 39 8E 3B 94 24 A1 4B 89 B9 3A 9D EB 33… 0,,1531,0:32.315.524,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,1532,0:32.330.529,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 1A FF 43 FA 58 94 D0 35 DC 9D 55 BB 7A ED 41… 0,,1536,0:32.331.526,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,1537,0:32.345.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1541,0:32.346.528,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,1542,0:32.346.532,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 1A FF 43 FA 58 94 D0 35 DC 9D 55 BB 7A ED 41… 0,,1546,0:32.347.528,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,1547,0:32.362.534,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 7A 47 E5 00 E0 0D 6D 1D 9A DC A1 0B 2F 24 D1… 0,,1551,0:32.363.531,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,1552,0:32.377.536,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1556,0:32.378.533,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,1557,0:32.378.536,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 7A 47 E5 00 E0 0D 6D 1D 9A DC A1 0B 2F 24 D1… 0,,1561,0:32.379.533,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,1562,0:32.394.538,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 F9 EC 91 41 76 FC 09 42 B6 ED C2 71 3B AE 63… 0,,1566,0:32.395.535,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,1567,0:32.409.540,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1571,0:32.410.537,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,1572,0:32.426.543,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 3A 51 81 A7 26 D5 0E 6C ED DB 99 02 B0 6A 9C… 0,,1576,0:32.427.540,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,1577,0:32.441.545,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1581,0:32.442.542,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,1582,0:32.442.545,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 3A 51 81 A7 26 D5 0E 6C ED DB 99 02 B0 6A 9C… 0,,1586,0:32.443.542,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,1587,0:32.458.547,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 6A 03 2E BD 3D FD B8 DA 07 A2 22 86 BB 9D FC… 0,,1591,0:32.459.544,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,1592,0:32.473.549,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1596,0:32.474.546,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,1597,0:32.474.549,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 6A 03 2E BD 3D FD B8 DA 07 A2 22 86 BB 9D FC… 0,,1601,0:32.475.546,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,1602,0:32.490.552,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 A3 FA 54 E3 F3 CD 3F 20 0B 43 41 FE 0D 52 3C… 0,,1606,0:32.491.548,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,1607,0:32.505.554,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1611,0:32.506.550,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,1612,0:32.506.554,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 A3 FA 54 E3 F3 CD 3F 20 0B 43 41 FE 0D 52 3C… 0,,1616,0:32.507.551,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,1617,0:32.522.556,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD BE 6D 16 2E BF 61 E2 2F 8E E4 88 5C 9D 8E 27… 0,,1621,0:32.523.553,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,1622,0:32.537.558,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1626,0:32.538.555,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,1627,0:32.538.558,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD BE 6D 16 2E BF 61 E2 2F 8E E4 88 5C 9D 8E 27… 0,,1631,0:32.539.555,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,1632,0:32.554.560,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 5D F2 53 23 65 43 57 D4 C2 C7 F9 A1 F0 B6 20… 0,,1636,0:32.555.557,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,1637,0:32.569.562,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1641,0:32.570.559,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,1642,0:32.570.563,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 5D F2 53 23 65 43 57 D4 C2 C7 F9 A1 F0 B6 20… 0,,1646,0:32.571.559,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,1647,0:32.586.565,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F 92 B1 0A 6D 3B DF A0 C7 CE B8 DD DD 6B 8E 20… 0,,1651,0:32.587.562,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,1652,0:32.601.567,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1656,0:32.602.564,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,1657,0:32.602.567,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F 92 B1 0A 6D 3B DF A0 C7 CE B8 DD DD 6B 8E 20… 0,,1661,0:32.603.564,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,1662,0:32.618.569,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC EE A7 CC 67 01 8F 3D 78 9C 22 C2 ED 73 54 31… 0,,1666,0:32.619.566,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,1667,0:32.633.571,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1671,0:32.634.568,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,1672,0:32.634.571,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC EE A7 CC 67 01 8F 3D 78 9C 22 C2 ED 73 54 31… 0,,1676,0:32.635.568,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,1677,0:32.650.574,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 2A 96 8B 8B 07 9E E6 FE F9 3B 43 22 45 06 F4… 0,,1681,0:32.651.571,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,1682,0:32.665.576,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1686,0:32.666.573,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,1687,0:32.666.576,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 2A 96 8B 8B 07 9E E6 FE F9 3B 43 22 45 06 F4… 0,,1691,0:32.667.573,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,1692,0:32.682.578,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 69 75 CB 1F 8C 63 DF 7F A7 EB A7 EA C2 3F 54… 0,,1696,0:32.683.575,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,1697,0:32.697.580,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1701,0:32.698.577,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,1702,0:32.698.580,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 69 75 CB 1F 8C 63 DF 7F A7 EB A7 EA C2 3F 54… 0,,1706,0:32.699.577,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,1707,0:32.714.583,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 BA 64 DD 01 08 6E 7D 57 CB F1 6D A7 ED 9D FE… 0,,1711,0:32.715.579,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,1712,0:32.729.585,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1716,0:32.730.582,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,1717,0:32.730.585,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 BA 64 DD 01 08 6E 7D 57 CB F1 6D A7 ED 9D FE… 0,,1721,0:32.731.582,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,1722,0:32.746.587,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 5A 01 0F 9C 98 A8 9E 74 7E 5E 74 26 20 D5 72… 0,,1726,0:32.747.584,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,1727,0:32.761.589,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1731,0:32.762.586,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,1732,0:32.762.589,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 5A 01 0F 9C 98 A8 9E 74 7E 5E 74 26 20 D5 72… 0,,1736,0:32.763.586,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,1737,0:32.778.592,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 E6 7A 96 C0 4E BA 80 CA C4 3C DC AD B7 B0 3C… 0,,1741,0:32.779.588,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,1742,0:32.793.594,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1746,0:32.794.590,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,1747,0:32.794.594,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 E6 7A 96 C0 4E BA 80 CA C4 3C DC AD B7 B0 3C… 0,,1751,0:32.795.591,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,1752,0:32.810.596,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 A0 BC CF 14 D7 7A BB 25 F7 1E 45 8C F1 76 BA… 0,,1756,0:32.811.593,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,1757,0:32.825.598,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1761,0:32.826.595,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,1762,0:32.826.598,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 A0 BC CF 14 D7 7A BB 25 F7 1E 45 8C F1 76 BA… 0,,1766,0:32.827.595,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,1767,0:32.842.600,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 A2 66 12 39 B0 83 CE E5 84 74 C6 AC 50 C3 BC… 0,,1771,0:32.843.597,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,1772,0:32.857.602,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1776,0:32.858.599,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,1777,0:32.858.603,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 A2 66 12 39 B0 83 CE E5 84 74 C6 AC 50 C3 BC… 0,,1781,0:32.859.599,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,1782,0:32.874.605,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 FD 25 FF 65 86 B5 1E 20 E0 31 7E E2 8C A5 FE… 0,,1786,0:32.875.602,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,1787,0:32.889.607,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1791,0:32.890.604,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,1792,0:32.890.607,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 FD 25 FF 65 86 B5 1E 20 E0 31 7E E2 8C A5 FE… 0,,1796,0:32.891.604,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,1797,0:32.906.609,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 DA B6 D7 2D B7 2C 8D 07 D3 92 80 F1 2B 91 4A… 0,,1801,0:32.907.606,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,1802,0:32.921.611,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1806,0:32.922.608,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,1807,0:32.922.611,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 DA B6 D7 2D B7 2C 8D 07 D3 92 80 F1 2B 91 4A… 0,,1811,0:32.923.608,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,1812,0:32.938.614,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 1F A4 8C 58 9D 4F E9 05 B8 B7 6D 62 3F 48 D2… 0,,1816,0:32.939.611,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,1817,0:32.953.616,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1821,0:32.954.613,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,1822,0:32.954.616,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 1F A4 8C 58 9D 4F E9 05 B8 B7 6D 62 3F 48 D2… 0,,1826,0:32.955.613,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,1827,0:32.970.618,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 54 23 7D 48 1E 3C 0D 0B E2 2C 2C FD E5 93 E4… 0,,1831,0:32.971.615,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,1832,0:32.985.620,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1836,0:32.986.617,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,1837,0:32.986.620,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED 54 23 7D 48 1E 3C 0D 0B E2 2C 2C FD E5 93 E4… 0,,1841,0:32.987.617,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,1842,0:33.002.623,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 3D 45 74 34 A6 63 3F 89 6E 3F D6 8A 04 FD 74… 0,,1846,0:33.003.619,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,1847,0:33.017.625,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1851,0:33.018.622,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,1852,0:33.034.627,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 FC 9B 03 2D 29 7D FD 47 6F A9 C9 6D C9 5C 86… 0,,1856,0:33.035.624,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,1857,0:33.049.629,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1861,0:33.050.626,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,1862,0:33.050.629,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 FC 9B 03 2D 29 7D FD 47 6F A9 C9 6D C9 5C 86… 0,,1866,0:33.051.626,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,1867,0:33.066.631,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 0E 95 5F BF 6F D0 99 D9 10 FC CE BC 31 2F FE… 0,,1871,0:33.067.628,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,1872,0:33.081.634,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1876,0:33.082.630,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,1877,0:33.082.634,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 0E 95 5F BF 6F D0 99 D9 10 FC CE BC 31 2F FE… 0,,1881,0:33.083.631,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,1882,0:33.098.636,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 50 A2 7D 4F 26 27 68 4A 9D D9 8D 19 40 29 0B… 0,,1886,0:33.099.633,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,1887,0:33.113.638,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1891,0:33.114.635,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,1892,0:33.114.638,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 50 A2 7D 4F 26 27 68 4A 9D D9 8D 19 40 29 0B… 0,,1896,0:33.115.635,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,1897,0:33.130.640,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA FF 5B 7A B8 4A C3 7C 58 C0 52 71 05 3D BA BB… 0,,1901,0:33.131.637,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,1902,0:33.145.642,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1906,0:33.146.639,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,1907,0:33.146.643,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA FF 5B 7A B8 4A C3 7C 58 C0 52 71 05 3D BA BB… 0,,1911,0:33.147.639,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,1912,0:33.162.645,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 1A 17 C8 5C 7A 47 6E 59 E1 A3 B6 17 C6 53 51… 0,,1916,0:33.163.642,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,1917,0:33.177.647,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1921,0:33.178.644,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,1922,0:33.178.647,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 1A 17 C8 5C 7A 47 6E 59 E1 A3 B6 17 C6 53 51… 0,,1926,0:33.179.644,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,1927,0:33.194.649,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D 97 DF 34 83 7B 9C 9C 0E CF A0 A4 26 87 EC 9A… 0,,1931,0:33.195.646,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,1932,0:33.209.651,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1936,0:33.210.648,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,1937,0:33.210.651,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D 97 DF 34 83 7B 9C 9C 0E CF A0 A4 26 87 EC 9A… 0,,1941,0:33.211.648,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,1942,0:33.226.654,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 81 94 4A 76 2C 4D AA 19 85 E9 B1 EC 35 94 4A… 0,,1946,0:33.227.651,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,1947,0:33.241.656,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1951,0:33.242.653,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,1952,0:33.242.656,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 81 94 4A 76 2C 4D AA 19 85 E9 B1 EC 35 94 4A… 0,,1956,0:33.243.653,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,1957,0:33.258.658,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 C5 74 96 6B B0 76 AC 97 DE F8 02 50 59 D6 5D… 0,,1961,0:33.259.655,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,1962,0:33.273.660,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1966,0:33.274.657,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,1967,0:33.274.660,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 C5 74 96 6B B0 76 AC 97 DE F8 02 50 59 D6 5D… 0,,1971,0:33.275.657,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,1972,0:33.290.663,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 8B 60 AF B1 AB C8 56 7D 5B FE B7 38 7F 6D 2D… 0,,1976,0:33.291.659,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,1977,0:33.305.665,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1981,0:33.306.662,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,1982,0:33.306.665,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 8B 60 AF B1 AB C8 56 7D 5B FE B7 38 7F 6D 2D… 0,,1986,0:33.307.662,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,1987,0:33.322.667,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F D2 4D 4D EB 1B AC 51 5B A3 E5 58 79 7D D7 D0… 0,,1991,0:33.323.664,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,1992,0:33.337.669,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,1996,0:33.338.666,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,1997,0:33.338.669,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F D2 4D 4D EB 1B AC 51 5B A3 E5 58 79 7D D7 D0… 0,,2001,0:33.339.666,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,2002,0:33.354.671,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 59 BE 1B C4 AA EE 1C EE BB EF 36 92 02 F1 88… 0,,2006,0:33.355.668,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,2007,0:33.369.674,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2011,0:33.370.670,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,2012,0:33.370.674,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 59 BE 1B C4 AA EE 1C EE BB EF 36 92 02 F1 88… 0,,2016,0:33.371.671,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,2017,0:33.386.676,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 8B C5 D3 A6 A7 9B E0 0E FF 60 E9 0B DC F0 C5… 0,,2021,0:33.387.673,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,2022,0:33.401.678,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2026,0:33.402.675,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,2027,0:33.402.678,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 8B C5 D3 A6 A7 9B E0 0E FF 60 E9 0B DC F0 C5… 0,,2031,0:33.403.675,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,2032,0:33.418.680,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F B6 0A BF D1 4F E4 7B 84 DE 01 00 0F 8A 96 E8… 0,,2036,0:33.419.677,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,2037,0:33.433.682,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2041,0:33.434.679,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,2042,0:33.434.683,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F B6 0A BF D1 4F E4 7B 84 DE 01 00 0F 8A 96 E8… 0,,2046,0:33.435.679,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,2047,0:33.450.685,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 BB 6B D9 28 2D F5 CF 18 89 00 66 D9 55 81 92… 0,,2051,0:33.451.682,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,2052,0:33.465.687,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2056,0:33.466.684,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,2057,0:33.466.687,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 BB 6B D9 28 2D F5 CF 18 89 00 66 D9 55 81 92… 0,,2061,0:33.467.684,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,2062,0:33.482.689,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 07 AC 24 58 34 5E D0 3D CB 55 E8 EA 37 3E AC… 0,,2066,0:33.483.686,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,2067,0:33.497.691,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2071,0:33.498.688,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,2072,0:33.498.691,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 07 AC 24 58 34 5E D0 3D CB 55 E8 EA 37 3E AC… 0,,2076,0:33.499.688,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,2077,0:33.514.694,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D CA 5F 42 85 E3 1F 9E 46 A7 84 D2 CE 41 99 A1… 0,,2081,0:33.515.691,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,2082,0:33.529.696,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2086,0:33.530.693,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,2087,0:33.530.696,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D CA 5F 42 85 E3 1F 9E 46 A7 84 D2 CE 41 99 A1… 0,,2091,0:33.531.693,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,2092,0:33.546.698,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 FC 6B 5C 0F 4F D6 BE 83 BC 22 FE 85 5B 8B EA… 0,,2096,0:33.547.695,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,2097,0:33.561.700,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2101,0:33.562.697,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,2102,0:33.562.700,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 FC 6B 5C 0F 4F D6 BE 83 BC 22 FE 85 5B 8B EA… 0,,2106,0:33.563.697,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,2107,0:33.578.703,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 E1 C6 AD C7 1F 46 95 51 69 F1 0F E8 90 C1 69… 0,,2111,0:33.579.699,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,2112,0:33.593.705,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2116,0:33.594.702,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,2117,0:33.594.705,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 E1 C6 AD C7 1F 46 95 51 69 F1 0F E8 90 C1 69… 0,,2121,0:33.595.702,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,2122,0:33.610.707,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 AC F1 35 B2 BD 1A 21 EB B5 76 4A E3 59 A9 FE… 0,,2126,0:33.611.704,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,2127,0:33.625.709,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2131,0:33.626.706,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,2132,0:33.626.709,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 AC F1 35 B2 BD 1A 21 EB B5 76 4A E3 59 A9 FE… 0,,2136,0:33.627.706,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,2137,0:33.642.711,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 21 4E FD AB 65 F4 65 CD 3F E0 BB 40 52 FD 69… 0,,2141,0:33.643.708,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,2142,0:33.657.714,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2146,0:33.658.710,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,2147,0:33.674.716,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 E3 1D 93 7E FE AA B1 7B F3 38 72 C2 50 9F 6D… 0,,2151,0:33.675.713,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,2152,0:33.689.718,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2156,0:33.690.715,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,2157,0:33.690.718,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 E3 1D 93 7E FE AA B1 7B F3 38 72 C2 50 9F 6D… 0,,2161,0:33.691.715,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,2162,0:33.706.720,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 7A 07 EA AC B7 AD 05 58 6F C9 51 60 C3 C3 02… 0,,2166,0:33.707.717,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,2167,0:33.721.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2171,0:33.722.719,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,2172,0:33.722.723,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 7A 07 EA AC B7 AD 05 58 6F C9 51 60 C3 C3 02… 0,,2176,0:33.723.719,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,2177,0:33.738.725,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 81 1E 1D A4 D7 2C A7 F5 15 81 F7 F9 A6 A2 24… 0,,2181,0:33.739.722,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,2182,0:33.753.727,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2186,0:33.754.724,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,2187,0:33.754.727,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 81 1E 1D A4 D7 2C A7 F5 15 81 F7 F9 A6 A2 24… 0,,2191,0:33.755.724,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,2192,0:33.770.729,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 24 2A 89 38 3F F1 66 68 E5 A6 54 7E 5C EB E8… 0,,2196,0:33.771.726,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,2197,0:33.785.731,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2201,0:33.786.728,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,2202,0:33.786.731,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 24 2A 89 38 3F F1 66 68 E5 A6 54 7E 5C EB E8… 0,,2206,0:33.787.728,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,2207,0:33.802.734,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F FD 56 69 9A 60 BB A0 CD 63 F3 D2 EA C4 97 68… 0,,2211,0:33.803.731,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,2212,0:33.817.736,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2216,0:33.818.733,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,2217,0:33.818.736,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F FD 56 69 9A 60 BB A0 CD 63 F3 D2 EA C4 97 68… 0,,2221,0:33.819.733,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,2222,0:33.834.738,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 53 57 1F 5D BB 31 C4 B1 0E 0C 5F 4B A8 85 FF… 0,,2226,0:33.835.735,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,2227,0:33.849.740,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2231,0:33.850.737,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,2232,0:33.850.740,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 53 57 1F 5D BB 31 C4 B1 0E 0C 5F 4B A8 85 FF… 0,,2236,0:33.851.737,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,2237,0:33.866.743,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 04 53 56 D8 2B C9 B0 8E 94 7C BF 12 B7 D0 2D… 0,,2241,0:33.867.739,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,2242,0:33.881.745,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2246,0:33.882.742,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,2247,0:33.882.745,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 04 53 56 D8 2B C9 B0 8E 94 7C BF 12 B7 D0 2D… 0,,2251,0:33.883.742,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,2252,0:33.898.747,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 F7 67 CC D4 AD E1 45 75 5F 8E 45 15 1B 2F 93… 0,,2256,0:33.899.744,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,2257,0:33.913.749,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2261,0:33.914.746,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,2262,0:33.914.749,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 F7 67 CC D4 AD E1 45 75 5F 8E 45 15 1B 2F 93… 0,,2266,0:33.915.746,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,2267,0:33.930.751,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 5B E7 63 93 87 E2 C0 EC D5 E6 E3 9E 6F CF 01… 0,,2271,0:33.931.748,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,2272,0:33.945.754,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2276,0:33.946.750,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,2277,0:33.946.754,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 5B E7 63 93 87 E2 C0 EC D5 E6 E3 9E 6F CF 01… 0,,2281,0:33.947.751,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,2282,0:33.962.756,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 B3 5E B8 2B 1C 2E C2 EF 69 11 DC 69 70 4D 32… 0,,2286,0:33.963.753,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,2287,0:33.977.758,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2291,0:33.978.755,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,2292,0:33.978.758,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 B3 5E B8 2B 1C 2E C2 EF 69 11 DC 69 70 4D 32… 0,,2296,0:33.979.755,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,2297,0:33.994.760,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 50 F6 5B 45 B2 86 CC B4 EB B2 80 A5 B4 7F C2… 0,,2301,0:33.995.757,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,2302,0:34.009.762,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2306,0:34.010.759,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,2307,0:34.010.763,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 50 F6 5B 45 B2 86 CC B4 EB B2 80 A5 B4 7F C2… 0,,2311,0:34.011.759,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,2312,0:34.026.765,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 54 0B BF 20 6C 1A 08 89 77 2A 9D 69 58 FC BF… 0,,2316,0:34.027.762,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,2317,0:34.041.767,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2321,0:34.042.764,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,2322,0:34.042.767,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 54 0B BF 20 6C 1A 08 89 77 2A 9D 69 58 FC BF… 0,,2326,0:34.043.764,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,2327,0:34.058.769,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 69 DC 56 86 00 38 21 4F 22 E6 58 6B 2C 0A 4F… 0,,2331,0:34.059.766,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,2332,0:34.073.771,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2336,0:34.074.768,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,2337,0:34.074.771,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 69 DC 56 86 00 38 21 4F 22 E6 58 6B 2C 0A 4F… 0,,2341,0:34.075.768,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,2342,0:34.090.774,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 4B 7D A1 64 EC 87 D4 22 09 2D 0B FF 7F 64 F4… 0,,2346,0:34.091.771,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,2347,0:34.105.776,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2351,0:34.106.773,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,2352,0:34.106.776,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 4B 7D A1 64 EC 87 D4 22 09 2D 0B FF 7F 64 F4… 0,,2356,0:34.107.773,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,2357,0:34.122.778,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 60 8F D0 CB 17 5B F1 C3 06 71 B3 56 0B F7 C8… 0,,2361,0:34.123.775,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,2362,0:34.137.780,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2366,0:34.138.777,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,2367,0:34.138.780,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 60 8F D0 CB 17 5B F1 C3 06 71 B3 56 0B F7 C8… 0,,2371,0:34.139.777,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,2372,0:34.154.783,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB DB A2 1B D6 7E 8A 16 95 2A 83 E0 6C 2A BF 85… 0,,2376,0:34.155.779,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,2377,0:34.169.785,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2381,0:34.170.781,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,2382,0:34.170.785,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB DB A2 1B D6 7E 8A 16 95 2A 83 E0 6C 2A BF 85… 0,,2386,0:34.171.782,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,2387,0:34.186.787,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 FF F7 9E 82 94 07 C4 0D 8B 6C 79 C2 92 A9 84… 0,,2391,0:34.187.784,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,2392,0:34.201.789,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2396,0:34.202.786,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,2397,0:34.202.789,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 FF F7 9E 82 94 07 C4 0D 8B 6C 79 C2 92 A9 84… 0,,2401,0:34.203.786,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,2402,0:34.218.791,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 1C EF 46 A1 E7 3B 90 F3 74 27 42 7C 4E 1E C2… 0,,2406,0:34.219.788,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,2407,0:34.233.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2411,0:34.234.790,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,2412,0:34.234.794,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 1C EF 46 A1 E7 3B 90 F3 74 27 42 7C 4E 1E C2… 0,,2416,0:34.235.791,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,2417,0:34.250.796,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 2A C7 6F C4 06 A0 BF 78 89 0E A5 2B DC CA 00… 0,,2421,0:34.251.793,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,2422,0:34.265.798,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2426,0:34.266.795,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,2427,0:34.282.800,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 70 03 63 0D C3 B5 A3 6B F6 B3 18 0E 97 F9 44… 0,,2431,0:34.283.797,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,2432,0:34.297.802,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2436,0:34.298.799,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,2437,0:34.298.803,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 70 03 63 0D C3 B5 A3 6B F6 B3 18 0E 97 F9 44… 0,,2441,0:34.299.799,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,2442,0:34.314.805,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 69 95 89 1B D6 D0 B7 C2 83 95 10 75 06 44 FE… 0,,2446,0:34.315.802,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,2447,0:34.329.807,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2451,0:34.330.804,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,2452,0:34.330.807,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 69 95 89 1B D6 D0 B7 C2 83 95 10 75 06 44 FE… 0,,2456,0:34.331.804,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,2457,0:34.346.809,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 82 BE 6A 45 AF 82 B0 CA 63 42 29 72 D3 6C 85… 0,,2461,0:34.347.806,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,2462,0:34.361.811,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2466,0:34.362.808,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,2467,0:34.362.811,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 82 BE 6A 45 AF 82 B0 CA 63 42 29 72 D3 6C 85… 0,,2471,0:34.363.808,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,2472,0:34.378.814,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 6C F1 3D 3B 97 CB 0F 86 AB 1B EC 7D 03 4E 4E… 0,,2476,0:34.379.811,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,2477,0:34.393.816,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2481,0:34.394.813,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,2482,0:34.394.816,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 6C F1 3D 3B 97 CB 0F 86 AB 1B EC 7D 03 4E 4E… 0,,2486,0:34.395.813,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,2487,0:34.410.818,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 2D ED CF 5B E8 BF 21 44 84 25 9D 0B 1E 51 48… 0,,2491,0:34.411.815,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,2492,0:34.425.820,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2496,0:34.426.817,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,2497,0:34.426.820,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 2D ED CF 5B E8 BF 21 44 84 25 9D 0B 1E 51 48… 0,,2501,0:34.427.817,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,2502,0:34.442.823,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 24 59 DF 7D D9 D5 51 C7 34 62 A7 94 65 9D 09… 0,,2506,0:34.443.819,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,2507,0:34.457.825,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2511,0:34.458.821,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,2512,0:34.458.825,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 24 59 DF 7D D9 D5 51 C7 34 62 A7 94 65 9D 09… 0,,2516,0:34.459.822,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,2517,0:34.474.827,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 CB 54 3E 5D 9B 5C 29 A4 9C 64 3F 24 2A CC 2E… 0,,2521,0:34.475.824,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,2522,0:34.489.829,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2526,0:34.490.826,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,2527,0:34.490.829,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 CB 54 3E 5D 9B 5C 29 A4 9C 64 3F 24 2A CC 2E… 0,,2531,0:34.491.826,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,2532,0:34.506.831,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 13 92 83 E9 70 31 E0 3D 04 E9 6C 6B C3 63 6B… 0,,2536,0:34.507.828,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,2537,0:34.521.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2541,0:34.522.830,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,2542,0:34.522.834,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 13 92 83 E9 70 31 E0 3D 04 E9 6C 6B C3 63 6B… 0,,2546,0:34.523.831,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,2547,0:34.538.836,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 6F 7D 86 79 6F EE 64 48 75 4B 36 EA 98 A7 4E… 0,,2551,0:34.539.833,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,2552,0:34.553.838,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2556,0:34.554.835,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,2557,0:34.554.838,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 6F 7D 86 79 6F EE 64 48 75 4B 36 EA 98 A7 4E… 0,,2561,0:34.555.835,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,2562,0:34.570.840,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 2B 3C 15 F4 4F 30 5B 03 4E 04 31 31 33 11 CE… 0,,2566,0:34.571.837,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,2567,0:34.585.842,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2571,0:34.586.839,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,2572,0:34.586.843,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 2B 3C 15 F4 4F 30 5B 03 4E 04 31 31 33 11 CE… 0,,2576,0:34.587.839,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,2577,0:34.602.845,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 45 2C 94 DB D3 A3 39 E9 5A 44 A6 BC 6C D9 4E… 0,,2581,0:34.603.842,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,2582,0:34.617.847,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2586,0:34.618.844,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,2587,0:34.618.847,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 45 2C 94 DB D3 A3 39 E9 5A 44 A6 BC 6C D9 4E… 0,,2591,0:34.619.844,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,2592,0:34.634.849,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 46 0B FF 18 F4 46 44 73 61 55 E3 02 CE 9B 46… 0,,2596,0:34.635.846,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,2597,0:34.649.851,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2601,0:34.650.848,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,2602,0:34.650.851,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 46 0B FF 18 F4 46 44 73 61 55 E3 02 CE 9B 46… 0,,2606,0:34.651.848,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,2607,0:34.666.854,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 2B DE 82 16 9E D3 C1 05 DB D7 C3 28 C4 E4 D8… 0,,2611,0:34.667.850,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,2612,0:34.681.856,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2616,0:34.682.853,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,2617,0:34.682.856,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 2B DE 82 16 9E D3 C1 05 DB D7 C3 28 C4 E4 D8… 0,,2621,0:34.683.853,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,2622,0:34.698.858,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 8C 22 16 FC 1D 79 E6 FF EC DC B5 03 F2 95 FC… 0,,2626,0:34.699.855,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,2627,0:34.713.860,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2631,0:34.714.857,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,2632,0:34.714.860,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 8C 22 16 FC 1D 79 E6 FF EC DC B5 03 F2 95 FC… 0,,2636,0:34.715.857,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,2637,0:34.730.862,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 BE CB C5 2A 35 AC 6F FA 8B 3E 5A 2E C2 EA 41… 0,,2641,0:34.731.859,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,2642,0:34.745.865,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2646,0:34.746.861,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,2647,0:34.746.865,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 BE CB C5 2A 35 AC 6F FA 8B 3E 5A 2E C2 EA 41… 0,,2651,0:34.747.862,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,2652,0:34.762.867,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 29 8C 92 43 A3 87 DB 17 D8 91 0B DC 31 29 F8… 0,,2656,0:34.763.864,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,2657,0:34.777.869,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2661,0:34.778.866,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,2662,0:34.778.869,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 29 8C 92 43 A3 87 DB 17 D8 91 0B DC 31 29 F8… 0,,2666,0:34.779.866,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,2667,0:34.794.871,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E E9 09 57 76 6C 4F D6 62 61 A7 E6 4C 67 E4 DB… 0,,2671,0:34.795.868,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,2672,0:34.809.873,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2676,0:34.810.870,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,2677,0:34.810.874,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E E9 09 57 76 6C 4F D6 62 61 A7 E6 4C 67 E4 DB… 0,,2681,0:34.811.870,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,2682,0:34.826.876,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 48 25 24 DE 7E 6F B8 76 B4 9F 06 AB 4C 9C D7… 0,,2686,0:34.827.873,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,2687,0:34.841.878,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2691,0:34.842.875,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,2692,0:34.842.878,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 48 25 24 DE 7E 6F B8 76 B4 9F 06 AB 4C 9C D7… 0,,2696,0:34.843.875,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,2697,0:34.858.880,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 29 63 B6 D9 AA EF 34 76 DF 79 98 3F 8C B5 D2… 0,,2701,0:34.859.877,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,2702,0:34.873.882,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2706,0:34.874.879,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,2707,0:34.874.882,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 29 63 B6 D9 AA EF 34 76 DF 79 98 3F 8C B5 D2… 0,,2711,0:34.875.879,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,2712,0:34.890.885,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A 08 E5 D1 BA 9F 4C 1A 5B A7 55 11 23 89 CB 80… 0,,2716,0:34.891.882,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,2717,0:34.905.887,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2721,0:34.906.884,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,2722,0:34.922.889,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 66 C2 6C 83 00 2F 89 96 CB 16 EB 52 43 68 CF… 0,,2726,0:34.923.886,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,2727,0:34.937.891,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2731,0:34.938.888,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,2732,0:34.938.891,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 66 C2 6C 83 00 2F 89 96 CB 16 EB 52 43 68 CF… 0,,2736,0:34.939.888,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,2737,0:34.954.894,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 AC AB 74 98 6E 61 27 BE F7 69 08 76 B4 73 6B… 0,,2741,0:34.955.890,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,2742,0:34.969.896,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2746,0:34.970.893,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,2747,0:34.970.896,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 AC AB 74 98 6E 61 27 BE F7 69 08 76 B4 73 6B… 0,,2751,0:34.971.893,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,2752,0:34.986.898,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 92 6F 5B 63 96 83 E3 45 98 DE 59 D9 11 EB 87… 0,,2756,0:34.987.895,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,2757,0:35.001.900,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2761,0:35.002.897,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,2762,0:35.002.900,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 92 6F 5B 63 96 83 E3 45 98 DE 59 D9 11 EB 87… 0,,2766,0:35.003.897,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,2767,0:35.018.902,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 1E 9E 7E 0B 7D A0 ED 5C F2 84 6C 52 28 7A 7A… 0,,2771,0:35.019.899,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,2772,0:35.033.905,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2776,0:35.034.901,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,2777,0:35.034.905,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 1E 9E 7E 0B 7D A0 ED 5C F2 84 6C 52 28 7A 7A… 0,,2781,0:35.035.902,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,2782,0:35.050.907,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 64 13 73 C3 A0 E8 1B AF 0A 4B 39 D4 54 32 26… 0,,2786,0:35.051.904,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,2787,0:35.065.909,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2791,0:35.066.906,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,2792,0:35.066.909,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 64 13 73 C3 A0 E8 1B AF 0A 4B 39 D4 54 32 26… 0,,2796,0:35.067.906,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,2797,0:35.082.911,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 F1 3B A7 73 C2 7D A6 12 13 70 57 52 BB 7C 9B… 0,,2801,0:35.083.908,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,2802,0:35.097.913,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2806,0:35.098.910,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,2807,0:35.098.914,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 F1 3B A7 73 C2 7D A6 12 13 70 57 52 BB 7C 9B… 0,,2811,0:35.099.910,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,2812,0:35.114.916,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 4F 46 B4 93 4E 56 DA 7C 82 E3 BC 6E 86 72 6C… 0,,2816,0:35.115.913,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,2817,0:35.129.918,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2821,0:35.130.915,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,2822,0:35.130.918,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 4F 46 B4 93 4E 56 DA 7C 82 E3 BC 6E 86 72 6C… 0,,2826,0:35.131.915,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,2827,0:35.146.920,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 D7 0D 2B F8 1E 70 05 F4 8C 86 C0 6F C4 4B 03… 0,,2831,0:35.147.917,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,2832,0:35.161.922,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2836,0:35.162.919,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,2837,0:35.162.922,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 D7 0D 2B F8 1E 70 05 F4 8C 86 C0 6F C4 4B 03… 0,,2841,0:35.163.919,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,2842,0:35.178.925,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 B9 90 89 DB 3C 2C FF D7 89 CB 42 AA 78 29 6C… 0,,2846,0:35.179.922,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,2847,0:35.193.927,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2851,0:35.194.924,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,2852,0:35.194.927,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 B9 90 89 DB 3C 2C FF D7 89 CB 42 AA 78 29 6C… 0,,2856,0:35.195.924,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,2857,0:35.210.929,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 91 DC FC B6 7E 01 62 41 D1 98 72 5B 65 D7 18… 0,,2861,0:35.211.926,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,2862,0:35.225.931,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2866,0:35.226.928,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,2867,0:35.226.931,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 91 DC FC B6 7E 01 62 41 D1 98 72 5B 65 D7 18… 0,,2871,0:35.227.928,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,2872,0:35.242.934,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 B2 7A 96 3B 6C 66 D7 1C 82 B0 CA 57 1A 69 48… 0,,2876,0:35.243.930,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,2877,0:35.257.936,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2881,0:35.258.933,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,2882,0:35.258.936,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 B2 7A 96 3B 6C 66 D7 1C 82 B0 CA 57 1A 69 48… 0,,2886,0:35.259.933,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,2887,0:35.274.938,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 26 D9 C6 7C B0 78 5B CE 0D 7C CC 51 3C EB 3C… 0,,2891,0:35.275.935,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,2892,0:35.289.940,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2896,0:35.290.937,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,2897,0:35.290.940,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 26 D9 C6 7C B0 78 5B CE 0D 7C CC 51 3C EB 3C… 0,,2901,0:35.291.937,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,2902,0:35.306.942,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C 7F 8A 6C 7C AA 59 EA 84 1A 60 F0 5A 3F 61 91… 0,,2906,0:35.307.939,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,2907,0:35.321.945,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2911,0:35.322.941,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,2912,0:35.322.945,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C 7F 8A 6C 7C AA 59 EA 84 1A 60 F0 5A 3F 61 91… 0,,2916,0:35.323.942,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,2917,0:35.338.947,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 51 85 99 58 4A 89 37 2B 0C 91 E1 3D 2C 2C E9… 0,,2921,0:35.339.944,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,2922,0:35.353.949,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2926,0:35.354.946,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,2927,0:35.354.949,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 51 85 99 58 4A 89 37 2B 0C 91 E1 3D 2C 2C E9… 0,,2931,0:35.355.946,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,2932,0:35.370.951,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AB B0 BD 54 D0 BE 6F 9B C4 02 42 5C 17 5A 74 6A… 0,,2936,0:35.371.948,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,2937,0:35.385.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2941,0:35.386.950,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,2942,0:35.386.954,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AB B0 BD 54 D0 BE 6F 9B C4 02 42 5C 17 5A 74 6A… 0,,2946,0:35.387.950,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,2947,0:35.402.956,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 06 30 30 A2 1C 73 A9 B8 3D 37 37 8D BC B5 F7… 0,,2951,0:35.403.953,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,2952,0:35.417.958,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2956,0:35.418.955,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,2957,0:35.418.958,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 06 30 30 A2 1C 73 A9 B8 3D 37 37 8D BC B5 F7… 0,,2961,0:35.419.955,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,2962,0:35.434.960,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 61 D7 99 B1 FD DC D1 C8 62 7A BC 83 F3 B3 7B… 0,,2966,0:35.435.957,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,2967,0:35.449.962,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2971,0:35.450.959,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,2972,0:35.450.962,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD 61 D7 99 B1 FD DC D1 C8 62 7A BC 83 F3 B3 7B… 0,,2976,0:35.451.959,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,2977,0:35.466.965,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 BE 91 BB 19 68 85 83 8F 30 FB 66 9D 19 4F C7… 0,,2981,0:35.467.962,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,2982,0:35.481.967,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,2986,0:35.482.964,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,2987,0:35.482.967,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 BE 91 BB 19 68 85 83 8F 30 FB 66 9D 19 4F C7… 0,,2991,0:35.483.964,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,2992,0:35.498.969,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 0E EF FA 4C 20 07 83 F3 B9 FE 72 01 57 6D 5C… 0,,2996,0:35.499.966,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,2997,0:35.513.971,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3001,0:35.514.968,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,3002,0:35.530.974,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 55 BA 2E 4E 75 8F 38 E8 DA BC C9 70 43 76 0B… 0,,3006,0:35.531.970,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,3007,0:35.545.976,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3011,0:35.546.973,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,3012,0:35.546.976,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 55 BA 2E 4E 75 8F 38 E8 DA BC C9 70 43 76 0B… 0,,3016,0:35.547.973,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,3017,0:35.562.978,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 CD 5B FF DA 80 1A 2D E0 BB 2A 99 46 6F E3 36… 0,,3021,0:35.563.975,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,3022,0:35.577.980,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3026,0:35.578.977,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,3027,0:35.578.980,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 CD 5B FF DA 80 1A 2D E0 BB 2A 99 46 6F E3 36… 0,,3031,0:35.579.977,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,3032,0:35.594.982,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA E1 AF FE FE FA FB 85 8F 9A 5B 8A 9C AC F8 3D… 0,,3036,0:35.595.979,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,3037,0:35.609.985,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3041,0:35.610.981,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,3042,0:35.610.985,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA E1 AF FE FE FA FB 85 8F 9A 5B 8A 9C AC F8 3D… 0,,3046,0:35.611.982,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,3047,0:35.626.987,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 34 58 B7 16 BE 8D 33 D1 1D 6F C7 00 1A 1E 26… 0,,3051,0:35.627.984,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,3052,0:35.641.989,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3056,0:35.642.986,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,3057,0:35.642.989,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 34 58 B7 16 BE 8D 33 D1 1D 6F C7 00 1A 1E 26… 0,,3061,0:35.643.986,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,3062,0:35.658.991,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 D4 B6 A8 6F 14 CD 3F 43 E3 6B 12 68 9B F9 3C… 0,,3066,0:35.659.988,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,3067,0:35.673.993,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3071,0:35.674.990,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,3072,0:35.674.994,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 D4 B6 A8 6F 14 CD 3F 43 E3 6B 12 68 9B F9 3C… 0,,3076,0:35.675.990,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,3077,0:35.690.996,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 11 24 19 1A BA 1C 72 BB CB B9 93 20 3E 7B C8… 0,,3081,0:35.691.993,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,3082,0:35.705.998,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3086,0:35.706.995,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,3087,0:35.706.998,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 11 24 19 1A BA 1C 72 BB CB B9 93 20 3E 7B C8… 0,,3091,0:35.707.995,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,3092,0:35.723.000,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 F8 34 A3 4E E9 33 11 08 18 03 4D 0C 38 B1 6A… 0,,3096,0:35.723.997,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,3097,0:35.738.002,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3101,0:35.738.999,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,3102,0:35.739.002,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 F8 34 A3 4E E9 33 11 08 18 03 4D 0C 38 B1 6A… 0,,3106,0:35.739.999,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,3107,0:35.755.005,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 8E D4 41 0D E0 23 44 05 AD 4E 5D 5B AB 37 63… 0,,3111,0:35.756.002,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,3112,0:35.770.007,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3116,0:35.771.004,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,3117,0:35.771.007,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 8E D4 41 0D E0 23 44 05 AD 4E 5D 5B AB 37 63… 0,,3121,0:35.772.004,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,3122,0:35.787.009,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 CA 20 38 AA 23 3B 38 91 D2 0E FA 33 8C 7B 5A… 0,,3126,0:35.788.006,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,3127,0:35.802.011,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3131,0:35.803.008,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,3132,0:35.803.011,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 CA 20 38 AA 23 3B 38 91 D2 0E FA 33 8C 7B 5A… 0,,3136,0:35.804.008,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,3137,0:35.819.014,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 ED 62 CE D8 AC EE 00 B2 58 E8 35 89 71 36 41… 0,,3141,0:35.820.010,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,3142,0:35.834.016,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3146,0:35.835.013,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,3147,0:35.835.016,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 ED 62 CE D8 AC EE 00 B2 58 E8 35 89 71 36 41… 0,,3151,0:35.836.013,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,3152,0:35.851.018,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 E4 C5 74 8F 0F 96 56 70 27 A5 11 6A CA 6B 7B… 0,,3156,0:35.852.015,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,3157,0:35.866.020,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3161,0:35.867.017,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,3162,0:35.867.020,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 E4 C5 74 8F 0F 96 56 70 27 A5 11 6A CA 6B 7B… 0,,3166,0:35.868.017,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,3167,0:35.883.023,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 C9 F0 81 2C 79 0B A9 E6 AF 0A 88 78 AB 01 38… 0,,3171,0:35.884.019,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,3172,0:35.898.025,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3176,0:35.899.021,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,3177,0:35.899.025,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 C9 F0 81 2C 79 0B A9 E6 AF 0A 88 78 AB 01 38… 0,,3181,0:35.900.022,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,3182,0:35.915.027,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E C6 0C 60 B2 EB 94 DA 94 3B E9 4B 6C BC 63 0F… 0,,3186,0:35.916.024,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,3187,0:35.930.029,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3191,0:35.931.026,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,3192,0:35.931.029,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E C6 0C 60 B2 EB 94 DA 94 3B E9 4B 6C BC 63 0F… 0,,3196,0:35.932.026,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,3197,0:35.947.031,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 F8 D7 DF B8 C9 30 80 FF 1D B4 99 66 5F 0D 23… 0,,3201,0:35.948.028,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,3202,0:35.962.033,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3206,0:35.963.030,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,3207,0:35.963.034,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 F8 D7 DF B8 C9 30 80 FF 1D B4 99 66 5F 0D 23… 0,,3211,0:35.964.030,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,3212,0:35.979.036,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C B4 58 4B 1B 7D F8 DC 80 B9 F8 E8 5C 80 6A 97… 0,,3216,0:35.980.033,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,3217,0:35.994.038,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3221,0:35.995.035,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,3222,0:35.995.038,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C B4 58 4B 1B 7D F8 DC 80 B9 F8 E8 5C 80 6A 97… 0,,3226,0:35.996.035,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,3227,0:36.011.040,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 26 F2 51 9D 76 72 1D 9E 1F 9A BE 8B 9F E1 EA… 0,,3231,0:36.012.037,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,3232,0:36.026.042,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3236,0:36.027.039,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,3237,0:36.027.043,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 26 F2 51 9D 76 72 1D 9E 1F 9A BE 8B 9F E1 EA… 0,,3241,0:36.028.039,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,3242,0:36.043.045,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 19 98 6A D7 85 35 EE 8D 03 16 A0 DF 47 90 0F… 0,,3246,0:36.044.042,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,3247,0:36.058.047,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3251,0:36.059.044,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,3252,0:36.059.047,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 19 98 6A D7 85 35 EE 8D 03 16 A0 DF 47 90 0F… 0,,3256,0:36.060.044,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,3257,0:36.075.049,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 81 3C 6B 32 27 E5 94 39 98 10 5A 6F 28 EB 09… 0,,3261,0:36.076.046,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,3262,0:36.090.051,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3266,0:36.091.048,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,3267,0:36.091.051,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 81 3C 6B 32 27 E5 94 39 98 10 5A 6F 28 EB 09… 0,,3271,0:36.092.048,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,3272,0:36.107.054,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 6A 22 A7 6F 0B 68 F7 8A 3E B3 4F DA C6 80 24… 0,,3276,0:36.108.050,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,3277,0:36.122.056,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3281,0:36.123.052,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,3282,0:36.123.056,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 6A 22 A7 6F 0B 68 F7 8A 3E B3 4F DA C6 80 24… 0,,3286,0:36.124.053,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,3287,0:36.139.058,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 81 A1 77 E9 C3 A0 41 32 44 2E DC 3A 32 DA 9A… 0,,3291,0:36.140.055,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,3292,0:36.154.060,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3296,0:36.155.057,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,3297,0:36.171.062,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 81 2D 84 83 A1 75 B6 B6 8F 0A D8 E8 3E F4 B4… 0,,3301,0:36.172.059,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,3302,0:36.186.064,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3306,0:36.187.061,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,3307,0:36.187.065,50.895 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 81 2D 84 83 A1 75 B6 B6 8F 0A D8 E8 3E F4 B4… 0,,3311,0:36.188.062,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,3312,0:36.203.067,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 C4 34 6F DB 27 0B C0 61 48 90 8E 27 2A 1A E7… 0,,3316,0:36.204.064,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,3317,0:36.218.069,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3321,0:36.219.066,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,3322,0:36.219.069,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 C4 34 6F DB 27 0B C0 61 48 90 8E 27 2A 1A E7… 0,,3326,0:36.220.066,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,3327,0:36.235.071,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 5E 13 85 CD 7A E7 51 2D A0 0E 17 EB 9C 83 00… 0,,3331,0:36.236.068,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,3332,0:36.250.073,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3336,0:36.251.070,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,3337,0:36.251.074,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 5E 13 85 CD 7A E7 51 2D A0 0E 17 EB 9C 83 00… 0,,3341,0:36.252.070,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,3342,0:36.267.076,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 79 4B E2 7F 7B F6 64 4F D4 8C 78 33 1B 63 E5… 0,,3346,0:36.268.073,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,3347,0:36.282.078,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3351,0:36.283.075,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,3352,0:36.283.078,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 79 4B E2 7F 7B F6 64 4F D4 8C 78 33 1B 63 E5… 0,,3356,0:36.284.075,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,3357,0:36.299.080,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE FB AB 46 B9 12 68 FD B6 7E 69 F2 58 7F 7D 5F… 0,,3361,0:36.300.077,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,3362,0:36.314.082,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3366,0:36.315.079,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,3367,0:36.315.082,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE FB AB 46 B9 12 68 FD B6 7E 69 F2 58 7F 7D 5F… 0,,3371,0:36.316.079,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,3372,0:36.331.085,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 63 8B 7A 94 B9 55 B8 B2 6D 92 69 2C 8E DD 12… 0,,3376,0:36.332.082,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,3377,0:36.346.087,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3381,0:36.347.084,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,3382,0:36.347.087,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 63 8B 7A 94 B9 55 B8 B2 6D 92 69 2C 8E DD 12… 0,,3386,0:36.348.084,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,3387,0:36.363.089,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 1E 56 F6 A7 EE C2 1E 95 E1 6B 70 9C 6E A8 15… 0,,3391,0:36.364.086,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,3392,0:36.378.091,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3396,0:36.379.088,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,3397,0:36.379.091,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 1E 56 F6 A7 EE C2 1E 95 E1 6B 70 9C 6E A8 15… 0,,3401,0:36.380.088,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,3402,0:36.395.094,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 77 F1 F7 FD BB 77 D3 1F 6D 0B 71 99 6F 44 5F… 0,,3406,0:36.396.090,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,3407,0:36.410.096,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3411,0:36.411.092,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,3412,0:36.411.096,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 77 F1 F7 FD BB 77 D3 1F 6D 0B 71 99 6F 44 5F… 0,,3416,0:36.412.093,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,3417,0:36.427.098,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 0A 27 0F 6A 8D 3D F8 6E 3A 44 A2 C2 73 2E 0D… 0,,3421,0:36.428.095,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,3422,0:36.442.100,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3426,0:36.443.097,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,3427,0:36.443.100,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 0A 27 0F 6A 8D 3D F8 6E 3A 44 A2 C2 73 2E 0D… 0,,3431,0:36.444.097,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,3432,0:36.459.102,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 08 57 77 E9 17 D4 0C 5E BB 02 11 F6 40 D4 E5… 0,,3436,0:36.460.099,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,3437,0:36.474.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3441,0:36.475.101,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,3442,0:36.475.105,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 08 57 77 E9 17 D4 0C 5E BB 02 11 F6 40 D4 E5… 0,,3446,0:36.476.102,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,3447,0:36.491.107,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 40 E5 53 C2 00 5E 17 B3 41 D8 CE 58 07 30 47 86… 0,,3451,0:36.492.104,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,3452,0:36.506.109,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3456,0:36.507.106,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,3457,0:36.507.109,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 40 E5 53 C2 00 5E 17 B3 41 D8 CE 58 07 30 47 86… 0,,3461,0:36.508.106,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,3462,0:36.523.111,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 40 9D 5A 99 57 14 6F 32 BA F6 7E 37 2D D5 5A… 0,,3466,0:36.524.108,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,3467,0:36.538.113,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3471,0:36.539.110,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,3472,0:36.539.113,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 40 9D 5A 99 57 14 6F 32 BA F6 7E 37 2D D5 5A… 0,,3476,0:36.540.110,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,3477,0:36.555.116,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 A4 3D 01 D2 DF 2C 78 F8 84 A6 D9 90 C0 34 F1… 0,,3481,0:36.556.113,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,3482,0:36.570.118,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3486,0:36.571.115,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,3487,0:36.571.118,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 A4 3D 01 D2 DF 2C 78 F8 84 A6 D9 90 C0 34 F1… 0,,3491,0:36.572.115,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,3492,0:36.587.120,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 49 D2 9A 26 2D B2 FD 86 E0 38 E2 44 2E 06 78… 0,,3496,0:36.588.117,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,3497,0:36.602.122,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3501,0:36.603.119,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,3502,0:36.603.122,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 49 D2 9A 26 2D B2 FD 86 E0 38 E2 44 2E 06 78… 0,,3506,0:36.604.119,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,3507,0:36.619.125,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 DB 35 DA 85 44 34 2F 71 62 3C D9 01 E4 5C 2B… 0,,3511,0:36.620.121,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,3512,0:36.634.127,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3516,0:36.635.124,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,3517,0:36.635.127,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 DB 35 DA 85 44 34 2F 71 62 3C D9 01 E4 5C 2B… 0,,3521,0:36.636.124,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,3522,0:36.651.129,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 46 CE 1D 4D 73 C4 AA D8 95 44 95 E0 18 28 E4… 0,,3526,0:36.652.126,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,3527,0:36.666.131,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3531,0:36.667.128,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,3532,0:36.667.131,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 46 CE 1D 4D 73 C4 AA D8 95 44 95 E0 18 28 E4… 0,,3536,0:36.668.128,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,3537,0:36.683.133,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 32 FC 0F D6 6C 46 CE 19 6F 78 B5 ED 21 A9 83… 0,,3541,0:36.684.130,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,3542,0:36.698.136,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3546,0:36.699.132,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,3547,0:36.699.136,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 32 FC 0F D6 6C 46 CE 19 6F 78 B5 ED 21 A9 83… 0,,3551,0:36.700.133,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,3552,0:36.715.138,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 5F DF F8 FD 99 DC C4 78 66 0F 33 89 4F AA F6… 0,,3556,0:36.716.135,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,3557,0:36.730.140,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3561,0:36.731.137,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,3562,0:36.731.140,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 5F DF F8 FD 99 DC C4 78 66 0F 33 89 4F AA F6… 0,,3566,0:36.732.137,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,3567,0:36.747.142,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 CB 14 EF 50 12 A8 0C 6A BB 2F 30 8B 9C ED 90… 0,,3571,0:36.748.139,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,3572,0:36.762.144,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3576,0:36.763.141,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,3577,0:36.779.147,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 E6 F6 76 CF 6E D3 80 8C 43 6F 85 20 26 AF B7… 0,,3581,0:36.780.144,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,3582,0:36.794.149,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3586,0:36.795.146,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,3587,0:36.795.149,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 E6 F6 76 CF 6E D3 80 8C 43 6F 85 20 26 AF B7… 0,,3591,0:36.796.146,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,3592,0:36.811.151,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 91 58 F8 5A F2 F0 8A 30 34 85 00 CD 59 01 E2… 0,,3596,0:36.812.148,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,3597,0:36.826.153,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3601,0:36.827.150,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,3602,0:36.827.153,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 91 58 F8 5A F2 F0 8A 30 34 85 00 CD 59 01 E2… 0,,3606,0:36.828.150,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,3607,0:36.843.156,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 8E D6 A5 95 63 EC 64 86 44 DF 7B 11 11 C5 DD… 0,,3611,0:36.844.153,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,3612,0:36.858.158,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3616,0:36.859.155,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,3617,0:36.859.158,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 8E D6 A5 95 63 EC 64 86 44 DF 7B 11 11 C5 DD… 0,,3621,0:36.860.155,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,3622,0:36.875.160,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 2A 3B 2F 93 C8 04 09 58 C2 C3 B2 EF 7E 93 3B… 0,,3626,0:36.876.157,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,3627,0:36.890.162,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3631,0:36.891.159,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,3632,0:36.891.162,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 2A 3B 2F 93 C8 04 09 58 C2 C3 B2 EF 7E 93 3B… 0,,3636,0:36.892.159,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,3637,0:36.907.165,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 E7 61 D7 09 D8 11 93 E3 53 FC F4 52 4B AB C0… 0,,3641,0:36.908.161,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,3642,0:36.922.167,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3646,0:36.923.164,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,3647,0:36.923.167,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 E7 61 D7 09 D8 11 93 E3 53 FC F4 52 4B AB C0… 0,,3651,0:36.924.164,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,3652,0:36.939.169,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 59 45 3A 78 41 FE 3D F0 6B 61 C8 B3 77 1D 64… 0,,3656,0:36.940.166,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,3657,0:36.954.171,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3661,0:36.955.168,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,3662,0:36.955.171,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 59 45 3A 78 41 FE 3D F0 6B 61 C8 B3 77 1D 64… 0,,3666,0:36.956.168,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,3667,0:36.971.173,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B 62 B4 06 AF 8D 71 0F 03 00 C6 16 83 B2 FB 1D… 0,,3671,0:36.972.170,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,3672,0:36.986.176,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3676,0:36.987.172,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,3677,0:36.987.176,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B 62 B4 06 AF 8D 71 0F 03 00 C6 16 83 B2 FB 1D… 0,,3681,0:36.988.173,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,3682,0:37.003.178,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 50 6E 9D FF 30 A3 C0 20 17 76 AB FF CF D2 46 E5… 0,,3686,0:37.004.175,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,3687,0:37.018.180,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3691,0:37.019.177,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,3692,0:37.019.180,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 50 6E 9D FF 30 A3 C0 20 17 76 AB FF CF D2 46 E5… 0,,3696,0:37.020.177,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,3697,0:37.035.182,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 A1 57 8C 42 65 B1 B5 15 5F 52 D1 41 FA 5C 19… 0,,3701,0:37.036.179,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,3702,0:37.050.184,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3706,0:37.051.181,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,3707,0:37.051.185,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 A1 57 8C 42 65 B1 B5 15 5F 52 D1 41 FA 5C 19… 0,,3711,0:37.052.181,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,3712,0:37.067.187,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD D2 F9 31 20 27 F8 41 D5 D0 3F 7A F7 8E 22 06… 0,,3716,0:37.068.184,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,3717,0:37.082.189,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3721,0:37.083.186,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,3722,0:37.083.189,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD D2 F9 31 20 27 F8 41 D5 D0 3F 7A F7 8E 22 06… 0,,3726,0:37.084.186,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,3727,0:37.099.191,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 7B 53 48 41 4B 90 45 62 14 0C 6C 81 A7 BC 0B… 0,,3731,0:37.100.188,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,3732,0:37.114.193,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3736,0:37.115.190,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,3737,0:37.115.193,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 7B 53 48 41 4B 90 45 62 14 0C 6C 81 A7 BC 0B… 0,,3741,0:37.116.190,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,3742,0:37.131.196,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D D1 9C 75 D0 DE 85 C0 4B AD 9A 85 24 62 A9 0F… 0,,3746,0:37.132.193,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,3747,0:37.146.198,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3751,0:37.147.195,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,3752,0:37.147.198,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D D1 9C 75 D0 DE 85 C0 4B AD 9A 85 24 62 A9 0F… 0,,3756,0:37.148.195,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,3757,0:37.163.200,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 91 C7 A0 56 0D C4 D7 4F 42 7E 64 A9 0A E0 EA… 0,,3761,0:37.164.197,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,3762,0:37.178.202,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3766,0:37.179.199,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,3767,0:37.179.202,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 91 C7 A0 56 0D C4 D7 4F 42 7E 64 A9 0A E0 EA… 0,,3771,0:37.180.199,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,3772,0:37.195.205,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 08 37 D6 B8 AC EF 46 86 24 C0 8A D2 09 19 CC… 0,,3776,0:37.196.201,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,3777,0:37.210.207,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3781,0:37.211.204,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,3782,0:37.211.207,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 08 37 D6 B8 AC EF 46 86 24 C0 8A D2 09 19 CC… 0,,3786,0:37.212.204,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,3787,0:37.227.209,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FB CA B4 0C CC 03 B1 B4 3F A5 98 B7 14 58 3A… 0,,3791,0:37.228.206,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,3792,0:37.242.211,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3796,0:37.243.208,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,3797,0:37.243.211,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FB CA B4 0C CC 03 B1 B4 3F A5 98 B7 14 58 3A… 0,,3801,0:37.244.208,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,3802,0:37.259.213,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA C4 32 36 7F 78 CB 75 C9 E2 55 25 F5 79 18 4E… 0,,3806,0:37.260.210,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,3807,0:37.274.216,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3811,0:37.275.212,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,3812,0:37.275.216,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA C4 32 36 7F 78 CB 75 C9 E2 55 25 F5 79 18 4E… 0,,3816,0:37.276.213,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,3817,0:37.291.218,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA DF 2E 6A 5B E4 DA 2D 8B 36 6D 19 98 CD 62 A9… 0,,3821,0:37.292.215,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,3822,0:37.306.220,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3826,0:37.307.217,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,3827,0:37.307.220,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA DF 2E 6A 5B E4 DA 2D 8B 36 6D 19 98 CD 62 A9… 0,,3831,0:37.308.217,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,3832,0:37.323.222,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 22 1B 4D E7 E4 EA E1 29 1B C5 95 7D 21 C0 87… 0,,3836,0:37.324.219,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,3837,0:37.338.224,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3841,0:37.339.221,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,3842,0:37.339.225,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 22 1B 4D E7 E4 EA E1 29 1B C5 95 7D 21 C0 87… 0,,3846,0:37.340.221,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,3847,0:37.355.227,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 27 23 31 18 1F 24 1D B7 8C 59 3C 22 71 45 71… 0,,3851,0:37.356.224,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,3852,0:37.370.229,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3856,0:37.371.226,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,3857,0:37.371.229,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 27 23 31 18 1F 24 1D B7 8C 59 3C 22 71 45 71… 0,,3861,0:37.372.226,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,3862,0:37.387.231,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C9 79 F8 84 AE 61 CF 9E 6A 83 A2 2E 59 1E B8 2E… 0,,3866,0:37.388.228,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,3867,0:37.402.233,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3871,0:37.403.230,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,3872,0:37.419.236,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 13 31 27 BB 58 66 50 8E 1C A6 E4 98 D1 13 19… 0,,3876,0:37.420.233,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,3877,0:37.434.238,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3881,0:37.435.235,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,3882,0:37.435.238,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 13 31 27 BB 58 66 50 8E 1C A6 E4 98 D1 13 19… 0,,3886,0:37.436.235,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,3887,0:37.451.240,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 E9 4C 86 43 85 A3 9D 0C C7 F9 48 93 BA 48 92… 0,,3891,0:37.452.237,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,3892,0:37.466.242,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3896,0:37.467.239,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,3897,0:37.467.242,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 E9 4C 86 43 85 A3 9D 0C C7 F9 48 93 BA 48 92… 0,,3901,0:37.468.239,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,3902,0:37.483.245,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F B5 61 C7 48 8F FA 39 50 45 02 57 03 19 D2 F4… 0,,3906,0:37.484.241,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,3907,0:37.498.247,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3911,0:37.499.244,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,3912,0:37.499.247,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F B5 61 C7 48 8F FA 39 50 45 02 57 03 19 D2 F4… 0,,3916,0:37.500.244,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,3917,0:37.515.249,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 7E 09 BD 88 74 34 C1 F0 2C 51 39 F7 49 F2 FB… 0,,3921,0:37.516.246,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,3922,0:37.530.251,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3926,0:37.531.248,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,3927,0:37.531.251,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 7E 09 BD 88 74 34 C1 F0 2C 51 39 F7 49 F2 FB… 0,,3931,0:37.532.248,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,3932,0:37.547.253,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B 2D 4E 2D 8A 39 A0 2D 33 53 3D 0A AD A8 AF 3A… 0,,3936,0:37.548.250,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,3937,0:37.562.256,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3941,0:37.563.252,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,3942,0:37.563.256,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B 2D 4E 2D 8A 39 A0 2D 33 53 3D 0A AD A8 AF 3A… 0,,3946,0:37.564.253,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,3947,0:37.579.258,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 42 06 04 5B 76 9C 5E F4 C0 5B EE 2F 79 CD 3E… 0,,3951,0:37.580.255,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,3952,0:37.594.260,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3956,0:37.595.257,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,3957,0:37.595.260,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 42 06 04 5B 76 9C 5E F4 C0 5B EE 2F 79 CD 3E… 0,,3961,0:37.596.257,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,3962,0:37.611.262,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 1F B1 9F 3C 9E 1A 0B 2E A3 D1 A9 F3 7B 1B 61… 0,,3966,0:37.612.259,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,3967,0:37.626.264,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3971,0:37.627.261,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,3972,0:37.627.265,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 1F B1 9F 3C 9E 1A 0B 2E A3 D1 A9 F3 7B 1B 61… 0,,3976,0:37.628.261,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,3977,0:37.643.267,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 D6 D9 9C CA AC E2 5C 6A 23 3F EC 08 15 81 30… 0,,3981,0:37.644.264,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,3982,0:37.658.269,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,3986,0:37.659.266,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,3987,0:37.659.269,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 D6 D9 9C CA AC E2 5C 6A 23 3F EC 08 15 81 30… 0,,3991,0:37.660.266,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,3992,0:37.675.271,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 23 15 A3 1A 9A 5A 42 08 86 27 3F 50 9A 31 62… 0,,3996,0:37.676.268,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,3997,0:37.690.273,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4001,0:37.691.270,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,4002,0:37.691.273,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 23 15 A3 1A 9A 5A 42 08 86 27 3F 50 9A 31 62… 0,,4006,0:37.692.270,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,4007,0:37.707.276,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 7C FD 72 2A C1 77 78 98 77 83 16 09 EE 6B EA… 0,,4011,0:37.708.273,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,4012,0:37.722.278,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4016,0:37.723.275,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,4017,0:37.723.278,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 7C FD 72 2A C1 77 78 98 77 83 16 09 EE 6B EA… 0,,4021,0:37.724.275,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,4022,0:37.739.280,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC FB 15 7D FB 41 70 E8 DF 4F 87 49 CB E3 51 40… 0,,4026,0:37.740.277,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,4027,0:37.754.282,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4031,0:37.755.279,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,4032,0:37.755.282,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC FB 15 7D FB 41 70 E8 DF 4F 87 49 CB E3 51 40… 0,,4036,0:37.756.279,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,4037,0:37.771.285,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 1F EF AD DA C5 52 9C AC 25 B1 F5 EA FC 99 8A… 0,,4041,0:37.772.281,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,4042,0:37.786.287,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4046,0:37.787.284,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,4047,0:37.787.287,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 1F EF AD DA C5 52 9C AC 25 B1 F5 EA FC 99 8A… 0,,4051,0:37.788.284,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,4052,0:37.803.289,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 58 DF 3F 73 9A 11 8B 84 34 EF C7 60 9C DD 59… 0,,4056,0:37.804.286,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,4057,0:37.818.291,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4061,0:37.819.288,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,4062,0:37.819.291,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 58 DF 3F 73 9A 11 8B 84 34 EF C7 60 9C DD 59… 0,,4066,0:37.820.288,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,4067,0:37.835.293,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 29 3B 81 F5 AA E2 AA 0C 46 4F BE 3E AD 0F A0… 0,,4071,0:37.836.290,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,4072,0:37.850.296,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4076,0:37.851.292,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,4077,0:37.851.296,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 29 3B 81 F5 AA E2 AA 0C 46 4F BE 3E AD 0F A0… 0,,4081,0:37.852.293,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,4082,0:37.867.298,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 CA BF A4 CA E9 E6 1F 42 8C D2 1B D9 1F 36 52… 0,,4086,0:37.868.295,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,4087,0:37.882.300,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4091,0:37.883.297,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,4092,0:37.883.300,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 CA BF A4 CA E9 E6 1F 42 8C D2 1B D9 1F 36 52… 0,,4096,0:37.884.297,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,4097,0:37.899.302,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 C3 04 39 5F 69 1F 30 59 FD 7E 11 BC 82 5B F6… 0,,4101,0:37.900.299,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,4102,0:37.914.304,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4106,0:37.915.301,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,4107,0:37.915.305,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 C3 04 39 5F 69 1F 30 59 FD 7E 11 BC 82 5B F6… 0,,4111,0:37.916.301,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,4112,0:37.931.307,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 09 E5 11 05 D0 B0 43 D2 FA D4 2D E1 32 3C 1F… 0,,4116,0:37.932.304,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,4117,0:37.946.309,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4121,0:37.947.306,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,4122,0:37.947.309,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 09 E5 11 05 D0 B0 43 D2 FA D4 2D E1 32 3C 1F… 0,,4126,0:37.948.306,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,4127,0:37.963.311,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F C2 73 3B 1A 28 C6 17 89 57 10 FC D4 21 61 95… 0,,4131,0:37.964.308,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,4132,0:37.978.313,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4136,0:37.979.310,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,4137,0:37.979.313,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F C2 73 3B 1A 28 C6 17 89 57 10 FC D4 21 61 95… 0,,4141,0:37.980.310,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,4142,0:37.995.316,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 63 0E C2 91 0F 43 E5 C8 C8 EF 79 4A E4 07 32… 0,,4146,0:37.996.313,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,4147,0:38.010.318,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4151,0:38.011.315,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,4152,0:38.027.320,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 2B 05 C1 9F 75 4B EA D2 9F 1B 7D 6E 0C E2 0B… 0,,4156,0:38.028.317,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,4157,0:38.042.322,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4161,0:38.043.319,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,4162,0:38.043.322,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 2B 05 C1 9F 75 4B EA D2 9F 1B 7D 6E 0C E2 0B… 0,,4166,0:38.044.319,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,4167,0:38.059.325,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 EF 7A 5D 6C A8 94 BB 17 F0 D7 51 71 C0 87 23… 0,,4171,0:38.060.321,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,4172,0:38.074.327,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4176,0:38.075.323,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,4177,0:38.075.327,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 EF 7A 5D 6C A8 94 BB 17 F0 D7 51 71 C0 87 23… 0,,4181,0:38.076.324,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,4182,0:38.091.329,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 54 9A 58 82 72 82 3E 83 DE 45 6A ED 75 ED FE… 0,,4186,0:38.092.326,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,4187,0:38.106.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4191,0:38.107.328,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,4192,0:38.107.331,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 54 9A 58 82 72 82 3E 83 DE 45 6A ED 75 ED FE… 0,,4196,0:38.108.328,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,4197,0:38.123.333,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 FE 81 18 9B 8D AD 7B 0A 83 66 76 EA 4C E2 C8… 0,,4201,0:38.124.330,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,4202,0:38.138.335,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4206,0:38.139.332,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,4207,0:38.139.336,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 FE 81 18 9B 8D AD 7B 0A 83 66 76 EA 4C E2 C8… 0,,4211,0:38.140.333,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,4212,0:38.155.338,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 26 52 ED 4C E2 AA 2C 53 60 95 4B DF 29 7B 42… 0,,4216,0:38.156.335,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,4217,0:38.170.340,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4221,0:38.171.337,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,4222,0:38.171.340,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 26 52 ED 4C E2 AA 2C 53 60 95 4B DF 29 7B 42… 0,,4226,0:38.172.337,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,4227,0:38.187.342,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 A5 61 4E 54 64 E2 9E D2 E0 16 8E 3E 25 39 E5… 0,,4231,0:38.188.339,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,4232,0:38.202.344,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4236,0:38.203.341,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,4237,0:38.203.345,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 A5 61 4E 54 64 E2 9E D2 E0 16 8E 3E 25 39 E5… 0,,4241,0:38.204.341,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,4242,0:38.219.347,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB BB B2 64 AF 9A 11 E1 77 39 FB A6 DB 2A 92 2D… 0,,4246,0:38.220.344,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,4247,0:38.234.349,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4251,0:38.235.346,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,4252,0:38.235.349,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB BB B2 64 AF 9A 11 E1 77 39 FB A6 DB 2A 92 2D… 0,,4256,0:38.236.346,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,4257,0:38.251.351,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 85 2D B4 E2 7C 56 39 78 8B 61 01 BD A9 38 13… 0,,4261,0:38.252.348,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,4262,0:38.266.353,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4266,0:38.267.350,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,4267,0:38.267.353,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 85 2D B4 E2 7C 56 39 78 8B 61 01 BD A9 38 13… 0,,4271,0:38.268.350,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,4272,0:38.283.356,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC E6 53 2B 86 71 1B 58 83 23 27 99 26 FE F0 7E… 0,,4276,0:38.284.353,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,4277,0:38.298.358,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4281,0:38.299.355,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,4282,0:38.299.358,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC E6 53 2B 86 71 1B 58 83 23 27 99 26 FE F0 7E… 0,,4286,0:38.300.355,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,4287,0:38.315.360,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 DE 11 BF BC 77 07 C9 4D D1 BD 9B 70 C2 A7 41… 0,,4291,0:38.316.357,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,4292,0:38.330.362,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4296,0:38.331.359,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,4297,0:38.331.362,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 DE 11 BF BC 77 07 C9 4D D1 BD 9B 70 C2 A7 41… 0,,4301,0:38.332.359,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,4302,0:38.347.365,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 59 0A 6F DF B1 7D AD 3E 63 B9 1E D7 82 9D F5… 0,,4306,0:38.348.361,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,4307,0:38.362.367,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4311,0:38.363.363,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,4312,0:38.363.367,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 59 0A 6F DF B1 7D AD 3E 63 B9 1E D7 82 9D F5… 0,,4316,0:38.364.364,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,4317,0:38.379.369,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 11 E5 84 90 6C 33 1C D5 5D D3 BB F4 8A 34 98 BA… 0,,4321,0:38.380.366,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,4322,0:38.394.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4326,0:38.395.368,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,4327,0:38.395.371,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 11 E5 84 90 6C 33 1C D5 5D D3 BB F4 8A 34 98 BA… 0,,4331,0:38.396.368,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,4332,0:38.411.373,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC A7 20 79 3F B0 45 CB AB FF 4E 5E E9 99 19 56… 0,,4336,0:38.412.370,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,4337,0:38.426.376,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4341,0:38.427.372,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,4342,0:38.427.376,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC A7 20 79 3F B0 45 CB AB FF 4E 5E E9 99 19 56… 0,,4346,0:38.428.373,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,4347,0:38.443.378,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B ED 01 E9 AB 5A B3 55 AC FE 5A 16 A5 57 44 3A… 0,,4351,0:38.444.375,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,4352,0:38.458.380,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4356,0:38.459.377,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,4357,0:38.459.380,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B ED 01 E9 AB 5A B3 55 AC FE 5A 16 A5 57 44 3A… 0,,4361,0:38.460.377,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,4362,0:38.475.382,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 65 FA FC 9A C0 A5 26 C6 13 37 D0 28 3D 65 FB… 0,,4366,0:38.476.379,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,4367,0:38.490.384,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4371,0:38.491.381,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,4372,0:38.491.384,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 65 FA FC 9A C0 A5 26 C6 13 37 D0 28 3D 65 FB… 0,,4376,0:38.492.381,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,4377,0:38.507.387,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B D6 95 CE 0D 12 9D 55 69 1C CA 7B 1C A1 47 34… 0,,4381,0:38.508.384,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,4382,0:38.522.389,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4386,0:38.523.386,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,4387,0:38.523.389,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B D6 95 CE 0D 12 9D 55 69 1C CA 7B 1C A1 47 34… 0,,4391,0:38.524.386,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,4392,0:38.539.391,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 6A 6E DD 9E D7 C2 8B 9E 94 53 45 49 C9 4C 81… 0,,4396,0:38.540.388,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,4397,0:38.554.393,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4401,0:38.555.390,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,4402,0:38.555.393,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 6A 6E DD 9E D7 C2 8B 9E 94 53 45 49 C9 4C 81… 0,,4406,0:38.556.390,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,4407,0:38.571.396,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 D9 8F 4D C6 90 CD 03 03 52 C5 8B B9 D4 D6 37… 0,,4411,0:38.572.392,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,4412,0:38.586.398,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4416,0:38.587.395,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,4417,0:38.587.398,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 D9 8F 4D C6 90 CD 03 03 52 C5 8B B9 D4 D6 37… 0,,4421,0:38.588.395,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,4422,0:38.603.400,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 1E 4B 36 AF 33 29 D0 F3 D3 88 AE 45 E9 A2 C8… 0,,4426,0:38.604.397,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,4427,0:38.618.402,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4431,0:38.619.399,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,4432,0:38.619.402,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 1E 4B 36 AF 33 29 D0 F3 D3 88 AE 45 E9 A2 C8… 0,,4436,0:38.620.399,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,4437,0:38.635.404,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 DD AD 76 8C 17 66 EE 4B E0 88 41 4A EF 17 2C… 0,,4441,0:38.636.401,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,4442,0:38.650.407,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4446,0:38.651.403,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,4447,0:38.667.409,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 6C 8F 69 D2 A1 62 D6 DE 67 8F B0 61 D6 9F 50… 0,,4451,0:38.668.406,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,4452,0:38.682.411,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4456,0:38.683.408,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,4457,0:38.683.411,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 6C 8F 69 D2 A1 62 D6 DE 67 8F B0 61 D6 9F 50… 0,,4461,0:38.684.408,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,4462,0:38.699.413,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 5B DB 08 D8 F3 FD F3 22 08 A6 46 F5 B6 25 30… 0,,4466,0:38.700.410,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,4467,0:38.714.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4471,0:38.715.412,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,4472,0:38.715.416,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 5B DB 08 D8 F3 FD F3 22 08 A6 46 F5 B6 25 30… 0,,4476,0:38.716.412,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,4477,0:38.731.418,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 30 4D B9 A4 0E F8 C4 D9 07 45 97 08 7F 25 A1… 0,,4481,0:38.732.415,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,4482,0:38.746.420,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4486,0:38.747.417,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,4487,0:38.747.420,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 30 4D B9 A4 0E F8 C4 D9 07 45 97 08 7F 25 A1… 0,,4491,0:38.748.417,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,4492,0:38.763.422,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 9B F6 F8 7C 2A C8 7B F5 56 A7 C4 85 5C CA 28… 0,,4496,0:38.764.419,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,4497,0:38.778.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4501,0:38.779.421,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,4502,0:38.779.424,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 9B F6 F8 7C 2A C8 7B F5 56 A7 C4 85 5C CA 28… 0,,4506,0:38.780.421,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,4507,0:38.795.427,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 8B 2D 1A 6D 8A 68 11 AA EF 4C 04 25 1D D4 63… 0,,4511,0:38.796.424,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,4512,0:38.810.429,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4516,0:38.811.426,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,4517,0:38.811.429,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 8B 2D 1A 6D 8A 68 11 AA EF 4C 04 25 1D D4 63… 0,,4521,0:38.812.426,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,4522,0:38.827.431,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F F8 DE D5 42 90 D5 9F 93 57 3D DE 44 9A 6C A2… 0,,4526,0:38.828.428,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,4527,0:38.842.433,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4531,0:38.843.430,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,4532,0:38.843.433,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F F8 DE D5 42 90 D5 9F 93 57 3D DE 44 9A 6C A2… 0,,4536,0:38.844.430,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,4537,0:38.859.436,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 A8 76 AA 52 BC F5 A9 11 7A 36 C3 91 4B E3 60… 0,,4541,0:38.860.432,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,4542,0:38.874.438,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4546,0:38.875.435,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,4547,0:38.875.438,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 A8 76 AA 52 BC F5 A9 11 7A 36 C3 91 4B E3 60… 0,,4551,0:38.876.435,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,4552,0:38.891.440,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 D8 22 FB 12 46 EF E8 ED D3 09 A8 6A C1 DB 14… 0,,4556,0:38.892.437,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,4557,0:38.906.442,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4561,0:38.907.439,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,4562,0:38.907.442,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 D8 22 FB 12 46 EF E8 ED D3 09 A8 6A C1 DB 14… 0,,4566,0:38.908.439,15.005.000 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,4567,0:38.923.445,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 47 70 20 FA 9C 0E CC 34 2A 7F A9 10 95 8C 4A… 0,,4571,0:38.924.441,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,4572,0:38.938.447,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4576,0:38.939.443,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,4577,0:38.939.447,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 47 70 20 FA 9C 0E CC 34 2A 7F A9 10 95 8C 4A… 0,,4581,0:38.940.444,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,4582,0:38.955.449,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 33 2E 9B A9 5E 95 A4 E0 02 35 FA 0F 1F B1 DB 44… 0,,4586,0:38.956.446,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,4587,0:38.970.451,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4591,0:38.971.448,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,4592,0:38.971.451,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 33 2E 9B A9 5E 95 A4 E0 02 35 FA 0F 1F B1 DB 44… 0,,4596,0:38.972.448,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,4597,0:38.987.453,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 D0 82 EB 98 B3 E5 AA 4D 46 B8 27 A0 12 9C 1A… 0,,4601,0:38.988.450,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,4602,0:39.002.455,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4606,0:39.003.452,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,4607,0:39.003.456,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 D0 82 EB 98 B3 E5 AA 4D 46 B8 27 A0 12 9C 1A… 0,,4611,0:39.004.452,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,4612,0:39.019.458,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 CE EA 7F 9F A2 53 F7 3C EE 03 E0 94 07 50 E4… 0,,4616,0:39.020.455,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,4617,0:39.034.460,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4621,0:39.035.457,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,4622,0:39.035.460,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 CE EA 7F 9F A2 53 F7 3C EE 03 E0 94 07 50 E4… 0,,4626,0:39.036.457,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,4627,0:39.051.462,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 1E A4 CA 4F 39 CB D9 53 C6 C3 D6 5A 89 FB 1E… 0,,4631,0:39.052.459,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,4632,0:39.066.464,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4636,0:39.067.461,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,4637,0:39.067.464,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 1E A4 CA 4F 39 CB D9 53 C6 C3 D6 5A 89 FB 1E… 0,,4641,0:39.068.461,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,4642,0:39.083.467,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 78 15 CC 2D 94 6A CF 88 A8 18 D4 A9 8F 64 31… 0,,4646,0:39.084.464,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,4647,0:39.098.469,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4651,0:39.099.466,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,4652,0:39.099.469,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 78 15 CC 2D 94 6A CF 88 A8 18 D4 A9 8F 64 31… 0,,4656,0:39.100.466,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,4657,0:39.115.471,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 78 DB 39 3E 15 D2 31 50 A3 77 B6 19 45 78 49… 0,,4661,0:39.116.468,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,4662,0:39.130.473,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4666,0:39.131.470,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,4667,0:39.131.473,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 78 DB 39 3E 15 D2 31 50 A3 77 B6 19 45 78 49… 0,,4671,0:39.132.470,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,4672,0:39.147.476,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 16 0F 5D 2F D3 55 7F 2C F2 A9 A5 D7 08 54 5B… 0,,4676,0:39.148.472,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,4677,0:39.162.478,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4681,0:39.163.475,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,4682,0:39.163.478,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 16 0F 5D 2F D3 55 7F 2C F2 A9 A5 D7 08 54 5B… 0,,4686,0:39.164.475,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,4687,0:39.179.480,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 1D 76 30 65 4F 27 24 54 E6 DE 06 1A 96 11 31… 0,,4691,0:39.180.477,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,4692,0:39.194.482,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4696,0:39.195.479,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,4697,0:39.195.482,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 1D 76 30 65 4F 27 24 54 E6 DE 06 1A 96 11 31… 0,,4701,0:39.196.479,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,4702,0:39.211.484,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 94 0C A6 3E 6C 23 C2 C6 25 9D B2 4A A5 C6 1B… 0,,4706,0:39.212.481,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,4707,0:39.226.487,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4711,0:39.227.483,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,4712,0:39.227.487,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 94 0C A6 3E 6C 23 C2 C6 25 9D B2 4A A5 C6 1B… 0,,4716,0:39.228.484,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,4717,0:39.243.489,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 AF 13 B8 51 9E FC 9A 11 DC 96 A6 50 35 96 C4… 0,,4721,0:39.244.486,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,4722,0:39.258.491,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4726,0:39.259.488,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,4727,0:39.275.493,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 FE CF 3D 6E 4D 60 CE F0 88 9B 15 71 39 A9 57… 0,,4731,0:39.276.490,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,4732,0:39.290.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4736,0:39.291.492,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,4737,0:39.291.496,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 FE CF 3D 6E 4D 60 CE F0 88 9B 15 71 39 A9 57… 0,,4741,0:39.292.492,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,4742,0:39.307.498,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 28 7A 45 1B E6 F2 9E 28 C0 A9 74 77 16 ED D0… 0,,4746,0:39.308.495,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,4747,0:39.322.500,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4751,0:39.323.497,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,4752,0:39.323.500,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 28 7A 45 1B E6 F2 9E 28 C0 A9 74 77 16 ED D0… 0,,4756,0:39.324.497,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,4757,0:39.339.502,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 C8 4B E4 EB 5E DF 2C D0 71 C8 19 A2 C5 BF B2… 0,,4761,0:39.340.499,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,4762,0:39.354.504,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4766,0:39.355.501,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,4767,0:39.355.504,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 C8 4B E4 EB 5E DF 2C D0 71 C8 19 A2 C5 BF B2… 0,,4771,0:39.356.501,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,4772,0:39.371.507,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 59 4D 99 33 AF A2 8C BF 01 A1 7B EA 72 C9 12… 0,,4776,0:39.372.504,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,4777,0:39.386.509,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4781,0:39.387.506,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,4782,0:39.387.509,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 59 4D 99 33 AF A2 8C BF 01 A1 7B EA 72 C9 12… 0,,4786,0:39.388.506,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,4787,0:39.403.511,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A E8 D7 1A 5E 9B B7 7C 35 AD 47 AE 40 EB 9A 6A… 0,,4791,0:39.404.508,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,4792,0:39.418.513,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4796,0:39.419.510,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,4797,0:39.419.513,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A E8 D7 1A 5E 9B B7 7C 35 AD 47 AE 40 EB 9A 6A… 0,,4801,0:39.420.510,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,4802,0:39.435.516,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 B0 3F D7 7B B9 AA FC A3 89 7B 53 6E 93 DD 50… 0,,4806,0:39.436.512,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,4807,0:39.450.518,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4811,0:39.451.515,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,4812,0:39.451.518,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 B0 3F D7 7B B9 AA FC A3 89 7B 53 6E 93 DD 50… 0,,4816,0:39.452.515,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,4817,0:39.467.520,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE 43 5D D2 96 F1 25 AF D3 2F F8 AC A9 66 50 55… 0,,4821,0:39.468.517,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,4822,0:39.482.522,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4826,0:39.483.519,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,4827,0:39.483.522,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE 43 5D D2 96 F1 25 AF D3 2F F8 AC A9 66 50 55… 0,,4831,0:39.484.519,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,4832,0:39.499.524,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF E3 03 24 8A E8 1B 45 42 4D B7 C5 DA 83 26 7F… 0,,4836,0:39.500.521,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,4837,0:39.514.527,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4841,0:39.515.523,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,4842,0:39.515.527,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF E3 03 24 8A E8 1B 45 42 4D B7 C5 DA 83 26 7F… 0,,4846,0:39.516.524,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,4847,0:39.531.529,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 36 4D 35 6F 4A 5C 52 1D 4B B3 1B E5 03 AF 80… 0,,4851,0:39.532.526,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,4852,0:39.546.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4856,0:39.547.528,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,4857,0:39.547.531,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 36 4D 35 6F 4A 5C 52 1D 4B B3 1B E5 03 AF 80… 0,,4861,0:39.548.528,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,4862,0:39.563.533,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 70 03 C7 22 B8 F6 63 AD D3 68 46 09 49 7B 29… 0,,4866,0:39.564.530,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,4867,0:39.578.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4871,0:39.579.532,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,4872,0:39.579.536,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 70 03 C7 22 B8 F6 63 AD D3 68 46 09 49 7B 29… 0,,4876,0:39.580.532,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,4877,0:39.595.538,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 D5 36 6F 66 F6 68 57 FC 9C 98 50 79 B9 54 FF… 0,,4881,0:39.596.535,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,4882,0:39.610.540,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4886,0:39.611.537,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,4887,0:39.611.540,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 D5 36 6F 66 F6 68 57 FC 9C 98 50 79 B9 54 FF… 0,,4891,0:39.612.537,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,4892,0:39.627.542,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 8A 9B 49 C1 67 D3 E4 B1 28 3B 6B 8A 76 44 97… 0,,4896,0:39.628.539,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,4897,0:39.642.544,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4901,0:39.643.541,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,4902,0:39.643.544,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 8A 9B 49 C1 67 D3 E4 B1 28 3B 6B 8A 76 44 97… 0,,4906,0:39.644.541,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,4907,0:39.659.547,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F8 68 8D F9 35 C0 13 3B 96 3A AD 0B F4 0E 1B A5… 0,,4911,0:39.660.544,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,4912,0:39.674.549,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4916,0:39.675.546,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,4917,0:39.675.549,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F8 68 8D F9 35 C0 13 3B 96 3A AD 0B F4 0E 1B A5… 0,,4921,0:39.676.546,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,4922,0:39.691.551,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 8E 8C 8E 3A 4E E4 25 AF E7 D2 11 6E 3F C0 87… 0,,4926,0:39.692.548,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,4927,0:39.706.553,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4931,0:39.707.550,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,4932,0:39.707.553,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 8E 8C 8E 3A 4E E4 25 AF E7 D2 11 6E 3F C0 87… 0,,4936,0:39.708.550,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,4937,0:39.723.556,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 98 1F 27 B8 FA 7C 6F 69 83 74 0F 2E E2 37 78… 0,,4941,0:39.724.552,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,4942,0:39.738.558,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4946,0:39.739.555,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,4947,0:39.739.558,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 98 1F 27 B8 FA 7C 6F 69 83 74 0F 2E E2 37 78… 0,,4951,0:39.740.555,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,4952,0:39.755.560,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 8D 35 90 2E AE C1 B9 45 63 C8 AF D1 28 8D A0… 0,,4956,0:39.756.557,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,4957,0:39.770.562,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4961,0:39.771.559,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,4962,0:39.771.562,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 8D 35 90 2E AE C1 B9 45 63 C8 AF D1 28 8D A0… 0,,4966,0:39.772.559,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,4967,0:39.787.564,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 7E 19 16 D6 66 E7 43 0F 35 EB D4 A7 0D D7 07… 0,,4971,0:39.788.561,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,4972,0:39.802.567,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4976,0:39.803.563,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,4977,0:39.803.567,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 7E 19 16 D6 66 E7 43 0F 35 EB D4 A7 0D D7 07… 0,,4981,0:39.804.564,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,4982,0:39.819.569,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 40 9A 7D BD 1E DB 21 F1 58 A9 0D 36 9E 5E 9A… 0,,4986,0:39.820.566,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,4987,0:39.834.571,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,4991,0:39.835.568,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,4992,0:39.835.571,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 40 9A 7D BD 1E DB 21 F1 58 A9 0D 36 9E 5E 9A… 0,,4996,0:39.836.568,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,4997,0:39.851.573,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E4 8B 27 38 43 7C 9E C8 E5 E5 4E 3A DF 98 C6… 0,,5001,0:39.852.570,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,5002,0:39.866.575,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5006,0:39.867.572,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,5007,0:39.867.576,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E4 8B 27 38 43 7C 9E C8 E5 E5 4E 3A DF 98 C6… 0,,5011,0:39.868.572,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,5012,0:39.883.578,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 77 B6 E8 24 4F BE 68 58 C9 86 FA 40 70 30 01… 0,,5016,0:39.884.575,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,5017,0:39.898.580,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5021,0:39.899.577,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,5022,0:39.915.582,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 13 F5 E7 6A FC C1 88 24 94 49 44 E2 96 40 01… 0,,5026,0:39.916.579,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,5027,0:39.930.584,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5031,0:39.931.581,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,5032,0:39.931.584,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 13 F5 E7 6A FC C1 88 24 94 49 44 E2 96 40 01… 0,,5036,0:39.932.581,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,5037,0:39.947.587,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC BD 70 AB 5F 56 8B 70 DC AF AD CC 43 8D 6A 10… 0,,5041,0:39.948.584,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,5042,0:39.962.589,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5046,0:39.963.586,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,5047,0:39.963.589,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC BD 70 AB 5F 56 8B 70 DC AF AD CC 43 8D 6A 10… 0,,5051,0:39.964.586,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,5052,0:39.979.591,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 29 E1 3C 14 9D 2F 6D AE 60 B7 AA 26 75 7F 84… 0,,5056,0:39.980.588,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,5057,0:39.994.593,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5061,0:39.995.590,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,5062,0:39.995.593,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 29 E1 3C 14 9D 2F 6D AE 60 B7 AA 26 75 7F 84… 0,,5066,0:39.996.590,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,5067,0:40.011.596,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 7F E7 E6 E8 9D F9 05 09 66 C7 6C 11 E8 9A 54… 0,,5071,0:40.012.592,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,5072,0:40.026.598,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5076,0:40.027.594,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,5077,0:40.027.598,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 7F E7 E6 E8 9D F9 05 09 66 C7 6C 11 E8 9A 54… 0,,5081,0:40.028.595,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,5082,0:40.043.600,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 09 26 42 2F 9C C9 0F E8 D7 03 CD EC 8B 8B 6E E5… 0,,5086,0:40.044.597,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,5087,0:40.058.602,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5091,0:40.059.599,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,5092,0:40.059.602,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 09 26 42 2F 9C C9 0F E8 D7 03 CD EC 8B 8B 6E E5… 0,,5096,0:40.060.599,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,5097,0:40.075.604,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 0B 6E C2 D2 01 EE 88 E7 21 B3 2A CB AB 1E 41… 0,,5101,0:40.076.601,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,5102,0:40.090.606,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5106,0:40.091.603,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,5107,0:40.091.607,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 0B 6E C2 D2 01 EE 88 E7 21 B3 2A CB AB 1E 41… 0,,5111,0:40.092.604,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,5112,0:40.107.609,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 2D 5A 07 96 00 75 EA DA 3A 7A 8E 88 74 FB CD… 0,,5116,0:40.108.606,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,5117,0:40.122.611,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5121,0:40.123.608,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,5122,0:40.123.611,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 2D 5A 07 96 00 75 EA DA 3A 7A 8E 88 74 FB CD… 0,,5126,0:40.124.608,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,5127,0:40.139.613,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 EC 1C 1E CA 9C D9 E7 AC 82 AA FF 44 1F ED 32… 0,,5131,0:40.140.610,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,5132,0:40.154.615,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5136,0:40.155.612,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,5137,0:40.155.616,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 EC 1C 1E CA 9C D9 E7 AC 82 AA FF 44 1F ED 32… 0,,5141,0:40.156.612,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,5142,0:40.171.618,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 61 08 79 AB 53 20 52 90 53 30 3B 64 A2 AB 87… 0,,5146,0:40.172.615,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,5147,0:40.186.620,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5151,0:40.187.617,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,5152,0:40.187.620,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 61 08 79 AB 53 20 52 90 53 30 3B 64 A2 AB 87… 0,,5156,0:40.188.617,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,5157,0:40.203.622,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF FF 04 2C 9E C5 09 A0 A4 D6 DA 2C E8 00 B8 7F… 0,,5161,0:40.204.619,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,5162,0:40.218.624,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5166,0:40.219.621,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,5167,0:40.219.624,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF FF 04 2C 9E C5 09 A0 A4 D6 DA 2C E8 00 B8 7F… 0,,5171,0:40.220.621,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,5172,0:40.235.627,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 76 1E 23 A0 8C 27 6F 92 EF 46 85 24 B3 5D D9… 0,,5176,0:40.236.624,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,5177,0:40.250.629,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5181,0:40.251.626,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,5182,0:40.251.629,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 76 1E 23 A0 8C 27 6F 92 EF 46 85 24 B3 5D D9… 0,,5186,0:40.252.626,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,5187,0:40.267.631,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 46 B2 22 0E 4E 74 E7 34 DE EF A2 3C 9D F4 21… 0,,5191,0:40.268.628,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,5192,0:40.282.633,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5196,0:40.283.630,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,5197,0:40.283.633,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 46 B2 22 0E 4E 74 E7 34 DE EF A2 3C 9D F4 21… 0,,5201,0:40.284.630,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,5202,0:40.299.636,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 73 34 48 8C 7A FB 3F 77 3B 35 94 4D 62 6F 4F… 0,,5206,0:40.300.632,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,5207,0:40.314.638,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5211,0:40.315.634,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,5212,0:40.315.638,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D4 73 34 48 8C 7A FB 3F 77 3B 35 94 4D 62 6F 4F… 0,,5216,0:40.316.635,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,5217,0:40.331.640,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C C7 C9 93 A2 06 8C 07 22 13 44 C0 DE 59 16 F5… 0,,5221,0:40.332.637,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,5222,0:40.346.642,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5226,0:40.347.639,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,5227,0:40.347.642,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C C7 C9 93 A2 06 8C 07 22 13 44 C0 DE 59 16 F5… 0,,5231,0:40.348.639,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,5232,0:40.363.644,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 5B BC 0E 87 29 19 7A 52 03 B3 BF 85 30 91 B1… 0,,5236,0:40.364.641,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,5237,0:40.378.646,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5241,0:40.379.643,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,5242,0:40.379.647,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 5B BC 0E 87 29 19 7A 52 03 B3 BF 85 30 91 B1… 0,,5246,0:40.380.644,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,5247,0:40.395.649,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 FD 3A 89 B6 92 25 E0 FC 06 F6 50 DE D5 46 CE… 0,,5251,0:40.396.646,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,5252,0:40.410.651,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5256,0:40.411.648,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,5257,0:40.411.651,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 FD 3A 89 B6 92 25 E0 FC 06 F6 50 DE D5 46 CE… 0,,5261,0:40.412.648,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,5262,0:40.427.653,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E F9 0E 83 46 03 1A 86 E5 D4 74 2B D8 19 B0 F2… 0,,5266,0:40.428.650,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,5267,0:40.442.655,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5271,0:40.443.652,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,5272,0:40.443.655,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E F9 0E 83 46 03 1A 86 E5 D4 74 2B D8 19 B0 F2… 0,,5276,0:40.444.652,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,5277,0:40.459.658,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F A1 D4 D6 A4 93 9F 7A 4B F2 FE 57 86 0B B6 19… 0,,5281,0:40.460.655,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,5282,0:40.474.660,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5286,0:40.475.657,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,5287,0:40.475.660,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F A1 D4 D6 A4 93 9F 7A 4B F2 FE 57 86 0B B6 19… 0,,5291,0:40.476.657,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,5292,0:40.491.662,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 E7 CD 40 70 16 D3 97 8D B6 9E F8 F7 32 26 78… 0,,5296,0:40.492.659,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,5297,0:40.506.664,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5301,0:40.507.661,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,5302,0:40.523.667,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 D4 31 1A E6 5A 78 D3 E1 EE 69 92 53 2A 58 93… 0,,5306,0:40.524.663,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,5307,0:40.538.669,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5311,0:40.539.666,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,5312,0:40.539.669,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 D4 31 1A E6 5A 78 D3 E1 EE 69 92 53 2A 58 93… 0,,5316,0:40.540.666,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,5317,0:40.555.671,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 3F 29 CF FE 48 4B B5 BE E0 BA 9D B3 DA 72 54… 0,,5321,0:40.556.668,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,5322,0:40.570.673,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5326,0:40.571.670,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,5327,0:40.571.673,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 3F 29 CF FE 48 4B B5 BE E0 BA 9D B3 DA 72 54… 0,,5331,0:40.572.670,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,5332,0:40.587.675,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A F1 7F 68 52 EB 7C 44 57 47 36 36 05 1A D8 29… 0,,5336,0:40.588.672,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,5337,0:40.602.678,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5341,0:40.603.674,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,5342,0:40.603.678,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A F1 7F 68 52 EB 7C 44 57 47 36 36 05 1A D8 29… 0,,5346,0:40.604.675,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,5347,0:40.619.680,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 98 44 70 0C 4F BD FB 20 50 29 BD A0 6C BA C6… 0,,5351,0:40.620.677,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,5352,0:40.634.682,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5356,0:40.635.679,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,5357,0:40.635.682,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 98 44 70 0C 4F BD FB 20 50 29 BD A0 6C BA C6… 0,,5361,0:40.636.679,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,5362,0:40.651.684,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 DC BF E8 8A B9 39 AA 6D 7F 48 56 8B F1 5E AF… 0,,5366,0:40.652.681,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,5367,0:40.666.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5371,0:40.667.683,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,5372,0:40.667.687,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 DC BF E8 8A B9 39 AA 6D 7F 48 56 8B F1 5E AF… 0,,5376,0:40.668.683,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,5377,0:40.683.689,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D A8 43 96 82 18 70 4D 5A AA 2B 21 0D 56 D9 01… 0,,5381,0:40.684.686,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,5382,0:40.698.691,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5386,0:40.699.688,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,5387,0:40.699.691,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D A8 43 96 82 18 70 4D 5A AA 2B 21 0D 56 D9 01… 0,,5391,0:40.700.688,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,5392,0:40.715.693,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 C7 E3 B6 B1 DF 6B E4 0A 21 08 F4 27 57 B5 7C… 0,,5396,0:40.716.690,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,5397,0:40.730.695,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5401,0:40.731.692,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,5402,0:40.731.696,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 C7 E3 B6 B1 DF 6B E4 0A 21 08 F4 27 57 B5 7C… 0,,5406,0:40.732.692,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,5407,0:40.747.698,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 74 AC 2D C6 A2 FA 4B D9 19 A5 00 DE 64 37 27… 0,,5411,0:40.748.695,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,5412,0:40.762.700,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5416,0:40.763.697,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,5417,0:40.763.700,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 74 AC 2D C6 A2 FA 4B D9 19 A5 00 DE 64 37 27… 0,,5421,0:40.764.697,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,5422,0:40.779.702,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 16 7D 4A 28 61 09 8C 39 EF 94 34 E8 0E A7 94… 0,,5426,0:40.780.699,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,5427,0:40.794.704,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5431,0:40.795.701,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,5432,0:40.795.704,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 16 7D 4A 28 61 09 8C 39 EF 94 34 E8 0E A7 94… 0,,5436,0:40.796.701,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,5437,0:40.811.707,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 03 3E CD E3 2C AD 47 B0 1D D3 53 B2 E4 16 A1… 0,,5441,0:40.812.703,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,5442,0:40.826.709,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5446,0:40.827.706,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,5447,0:40.827.709,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 03 3E CD E3 2C AD 47 B0 1D D3 53 B2 E4 16 A1… 0,,5451,0:40.828.706,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,5452,0:40.843.711,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 17 38 82 03 0D 8C 06 FC AF 3E 10 39 0E 4D 01… 0,,5456,0:40.844.708,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,5457,0:40.858.713,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5461,0:40.859.710,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,5462,0:40.859.713,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 17 38 82 03 0D 8C 06 FC AF 3E 10 39 0E 4D 01… 0,,5466,0:40.860.710,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,5467,0:40.875.715,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 0F 13 54 CC 46 D7 6E C9 32 AA 25 A5 56 2C 3C… 0,,5471,0:40.876.712,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,5472,0:40.890.718,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5476,0:40.891.714,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,5477,0:40.891.718,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 0F 13 54 CC 46 D7 6E C9 32 AA 25 A5 56 2C 3C… 0,,5481,0:40.892.715,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,5482,0:40.907.720,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 9B 99 FF E5 FA E8 FC E1 CA C8 42 EA 69 2E 9E… 0,,5486,0:40.908.717,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,5487,0:40.922.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5491,0:40.923.719,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,5492,0:40.923.722,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 9B 99 FF E5 FA E8 FC E1 CA C8 42 EA 69 2E 9E… 0,,5496,0:40.924.719,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,5497,0:40.939.724,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 12 C7 B7 09 B9 1E 6D 37 E8 5F 87 18 56 54 61… 0,,5501,0:40.940.721,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,5502,0:40.954.726,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5506,0:40.955.723,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,5507,0:40.955.727,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 12 C7 B7 09 B9 1E 6D 37 E8 5F 87 18 56 54 61… 0,,5511,0:40.956.723,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,5512,0:40.971.729,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 12 2E 5A 0F 5F EA 8D 2D 90 EC 65 12 2B 7C B6… 0,,5516,0:40.972.726,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,5517,0:40.986.731,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5521,0:40.987.728,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,5522,0:40.987.731,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 12 2E 5A 0F 5F EA 8D 2D 90 EC 65 12 2B 7C B6… 0,,5526,0:40.988.728,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,5527,0:41.003.733,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F4 4E 98 A2 2C 4C 43 34 0B 4E EF E8 C2 85 8A 06… 0,,5531,0:41.004.730,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,5532,0:41.018.735,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5536,0:41.019.732,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,5537,0:41.019.735,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F4 4E 98 A2 2C 4C 43 34 0B 4E EF E8 C2 85 8A 06… 0,,5541,0:41.020.732,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,5542,0:41.035.738,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 9F 33 EE 23 FA BD 25 A7 21 0F EB 47 CC 29 8C… 0,,5546,0:41.036.735,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,5547,0:41.050.740,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5551,0:41.051.737,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,5552,0:41.051.740,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 9F 33 EE 23 FA BD 25 A7 21 0F EB 47 CC 29 8C… 0,,5556,0:41.052.737,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,5557,0:41.067.742,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 F8 A8 C7 98 F5 01 4C EC 5A 11 F0 FB 0D 3D 6A… 0,,5561,0:41.068.739,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,5562,0:41.082.744,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5566,0:41.083.741,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,5567,0:41.083.744,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 F8 A8 C7 98 F5 01 4C EC 5A 11 F0 FB 0D 3D 6A… 0,,5571,0:41.084.741,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,5572,0:41.099.747,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 7F B2 25 D3 57 0F 97 76 B0 B4 86 CC 73 04 7F… 0,,5576,0:41.100.743,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,5577,0:41.114.749,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5581,0:41.115.746,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,5582,0:41.115.749,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 7F B2 25 D3 57 0F 97 76 B0 B4 86 CC 73 04 7F… 0,,5586,0:41.116.746,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,5587,0:41.131.751,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5591,0:41.132.748,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,5592,0:41.146.753,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5596,0:41.147.750,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,5597,0:41.163.755,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5601,0:41.164.752,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,5602,0:41.178.758,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5606,0:41.179.754,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,5607,0:41.179.758,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B 2B FE 5F C7 06 79 EC F6 DB DD 6B 3D 86 B3 52… 0,,5611,0:41.180.755,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,5612,0:41.195.760,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 5A 18 7B 79 D3 2C CA 33 B5 A7 40 85 1A A0 43… 0,,5616,0:41.196.757,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,5617,0:41.210.762,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5621,0:41.211.759,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,5622,0:41.211.762,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 5A 18 7B 79 D3 2C CA 33 B5 A7 40 85 1A A0 43… 0,,5626,0:41.212.759,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,5627,0:41.227.764,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 F9 9D 33 1E 22 70 D6 CD FB 29 92 92 20 96 6A… 0,,5631,0:41.228.761,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,5632,0:41.242.766,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5636,0:41.243.763,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,5637,0:41.243.767,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 F9 9D 33 1E 22 70 D6 CD FB 29 92 92 20 96 6A… 0,,5641,0:41.244.763,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,5642,0:41.259.769,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 EA 8F AE B0 1D 88 C5 8A 6C 7E 8B 02 A7 4B A3… 0,,5646,0:41.260.766,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,5647,0:41.274.771,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5651,0:41.275.768,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,5652,0:41.275.771,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 EA 8F AE B0 1D 88 C5 8A 6C 7E 8B 02 A7 4B A3… 0,,5656,0:41.276.768,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,5657,0:41.291.773,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 07 EC 7B C5 C9 EC 70 56 E7 3C 61 C6 F1 FD 75… 0,,5661,0:41.292.770,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,5662,0:41.306.775,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5666,0:41.307.772,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,5667,0:41.307.775,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 07 EC 7B C5 C9 EC 70 56 E7 3C 61 C6 F1 FD 75… 0,,5671,0:41.308.772,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,5672,0:41.323.778,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 4F E9 7D BA 92 E7 7D C8 AE F9 32 2E A9 E1 44… 0,,5676,0:41.324.775,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,5677,0:41.338.780,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5681,0:41.339.777,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,5682,0:41.339.780,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 4F E9 7D BA 92 E7 7D C8 AE F9 32 2E A9 E1 44… 0,,5686,0:41.340.777,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,5687,0:41.355.782,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA DB 79 79 07 7F 71 45 5E F4 63 4C D8 E7 C9 A7… 0,,5691,0:41.356.779,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,5692,0:41.370.784,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5696,0:41.371.781,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,5697,0:41.371.784,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA DB 79 79 07 7F 71 45 5E F4 63 4C D8 E7 C9 A7… 0,,5701,0:41.372.781,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,5702,0:41.387.787,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 E3 B9 C7 DB 0F 59 AE A3 87 86 5F 27 BC EE 04… 0,,5706,0:41.388.783,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,5707,0:41.402.789,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5711,0:41.403.786,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,5712,0:41.403.789,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 E3 B9 C7 DB 0F 59 AE A3 87 86 5F 27 BC EE 04… 0,,5716,0:41.404.786,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,5717,0:41.419.791,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E C6 C5 55 80 30 5F C0 5E 76 93 BF 7B 00 C3 31… 0,,5721,0:41.420.788,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,5722,0:41.434.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5726,0:41.435.790,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,5727,0:41.435.793,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E C6 C5 55 80 30 5F C0 5E 76 93 BF 7B 00 C3 31… 0,,5731,0:41.436.790,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,5732,0:41.451.795,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 2A 38 E2 E0 09 9E 16 0F B5 B8 44 88 AB EC 52… 0,,5736,0:41.452.792,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,5737,0:41.466.798,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5741,0:41.467.794,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,5742,0:41.467.798,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 2A 38 E2 E0 09 9E 16 0F B5 B8 44 88 AB EC 52… 0,,5746,0:41.468.795,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,5747,0:41.483.800,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 37 1B 35 3A 46 7A 1F 62 B9 FD AE 37 BD 2E EF… 0,,5751,0:41.484.797,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,5752,0:41.498.802,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5756,0:41.499.799,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,5757,0:41.499.802,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 37 1B 35 3A 46 7A 1F 62 B9 FD AE 37 BD 2E EF… 0,,5761,0:41.500.799,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,5762,0:41.515.804,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 E6 6D 0D 3A AA 20 7A 11 03 A8 8C 25 86 A3 36… 0,,5766,0:41.516.801,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,5767,0:41.530.806,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5771,0:41.531.803,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,5772,0:41.531.807,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 E6 6D 0D 3A AA 20 7A 11 03 A8 8C 25 86 A3 36… 0,,5776,0:41.532.803,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,5777,0:41.547.809,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 A5 42 56 93 25 13 8B F1 AC 8B 59 62 66 17 56… 0,,5781,0:41.548.806,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,5782,0:41.562.811,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5786,0:41.563.808,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,5787,0:41.563.811,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 A5 42 56 93 25 13 8B F1 AC 8B 59 62 66 17 56… 0,,5791,0:41.564.808,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,5792,0:41.579.813,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 BE F3 C6 B9 08 41 B7 A9 23 88 EF 6C 07 5E 6F… 0,,5796,0:41.580.810,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,5797,0:41.594.815,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5801,0:41.595.812,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,5802,0:41.595.815,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 BE F3 C6 B9 08 41 B7 A9 23 88 EF 6C 07 5E 6F… 0,,5806,0:41.596.812,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,5807,0:41.611.818,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 A4 83 2A 0E 46 17 B9 9D 22 48 B1 0B 8D F8 9C… 0,,5811,0:41.612.815,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,5812,0:41.626.820,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5816,0:41.627.817,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,5817,0:41.627.820,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 A4 83 2A 0E 46 17 B9 9D 22 48 B1 0B 8D F8 9C… 0,,5821,0:41.628.817,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,5822,0:41.643.822,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 08 F8 B0 FD AB 0C 7A 68 D2 BA B1 44 57 12 2A… 0,,5826,0:41.644.819,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,5827,0:41.658.824,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5831,0:41.659.821,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,5832,0:41.659.824,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 08 F8 B0 FD AB 0C 7A 68 D2 BA B1 44 57 12 2A… 0,,5836,0:41.660.821,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,5837,0:41.675.827,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA BF 26 7E F4 B5 F0 1F EE 8B A8 A7 CD EA FB C4… 0,,5841,0:41.676.823,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,5842,0:41.690.829,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5846,0:41.691.826,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,5847,0:41.691.829,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA BF 26 7E F4 B5 F0 1F EE 8B A8 A7 CD EA FB C4… 0,,5851,0:41.692.826,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,5852,0:41.707.831,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 96 8C 90 E9 C7 F5 38 35 3E 51 95 5E F6 1B 94… 0,,5856,0:41.708.828,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,5857,0:41.722.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5861,0:41.723.830,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,5862,0:41.723.833,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 96 8C 90 E9 C7 F5 38 35 3E 51 95 5E F6 1B 94… 0,,5866,0:41.724.830,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,5867,0:41.739.835,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 AF 88 95 89 2F 03 63 D2 E7 24 1E 7A 15 44 8E… 0,,5871,0:41.740.832,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,5872,0:41.754.838,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5876,0:41.755.834,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,5877,0:41.771.840,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 DA 25 45 05 F6 CA A2 15 B9 88 80 A3 2F DF B1… 0,,5881,0:41.772.837,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,5882,0:41.786.842,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5886,0:41.787.839,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,5887,0:41.787.842,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 DA 25 45 05 F6 CA A2 15 B9 88 80 A3 2F DF B1… 0,,5891,0:41.788.839,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,5892,0:41.803.844,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 1A 95 52 65 03 7D A7 31 43 35 32 6E 25 24 7D… 0,,5896,0:41.804.841,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,5897,0:41.818.846,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5901,0:41.819.843,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,5902,0:41.819.847,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 1A 95 52 65 03 7D A7 31 43 35 32 6E 25 24 7D… 0,,5906,0:41.820.843,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,5907,0:41.835.849,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 17 5E AE 13 27 28 F8 96 20 4E 60 6B FB B2 70… 0,,5911,0:41.836.846,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,5912,0:41.850.851,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5916,0:41.851.848,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,5917,0:41.851.851,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 17 5E AE 13 27 28 F8 96 20 4E 60 6B FB B2 70… 0,,5921,0:41.852.848,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,5922,0:41.867.853,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 C7 ED 1F EF 52 61 4D 34 30 D3 63 63 F6 E4 8C… 0,,5926,0:41.868.850,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,5927,0:41.882.855,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5931,0:41.883.852,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,5932,0:41.883.855,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 C7 ED 1F EF 52 61 4D 34 30 D3 63 63 F6 E4 8C… 0,,5936,0:41.884.852,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,5937,0:41.899.858,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 72 3C 08 53 62 43 5B 51 2A C4 AF E0 7A 6B 43… 0,,5941,0:41.900.855,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,5942,0:41.914.860,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5946,0:41.915.857,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,5947,0:41.915.860,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 72 3C 08 53 62 43 5B 51 2A C4 AF E0 7A 6B 43… 0,,5951,0:41.916.857,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,5952,0:41.931.862,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 09 5F 89 1E C0 05 F4 B4 CD D2 EF B4 3A E4 65… 0,,5956,0:41.932.859,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,5957,0:41.946.864,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5961,0:41.947.861,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,5962,0:41.947.864,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 09 5F 89 1E C0 05 F4 B4 CD D2 EF B4 3A E4 65… 0,,5966,0:41.948.861,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,5967,0:41.963.867,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 BD CA E2 7C 9C ED 2B 52 AF A7 C1 D8 01 18 82… 0,,5971,0:41.964.863,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,5972,0:41.978.869,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5976,0:41.979.865,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,5977,0:41.979.869,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 BD CA E2 7C 9C ED 2B 52 AF A7 C1 D8 01 18 82… 0,,5981,0:41.980.866,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,5982,0:41.995.871,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 18 2A 32 F7 28 94 19 29 C3 E7 F2 DB FB E7 F5… 0,,5986,0:41.996.868,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,5987,0:42.010.873,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,5991,0:42.011.870,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,5992,0:42.011.873,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 18 2A 32 F7 28 94 19 29 C3 E7 F2 DB FB E7 F5… 0,,5996,0:42.012.870,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,5997,0:42.027.875,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 39 8F 44 27 97 B0 DB 78 EF 6B 90 78 E4 6A BB… 0,,6001,0:42.028.872,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,6002,0:42.042.877,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6006,0:42.043.874,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,6007,0:42.043.878,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 39 8F 44 27 97 B0 DB 78 EF 6B 90 78 E4 6A BB… 0,,6011,0:42.044.875,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,6012,0:42.059.880,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 DA CF 22 79 58 FB D5 09 FC 92 CC EE 86 9C B1… 0,,6016,0:42.060.877,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,6017,0:42.074.882,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6021,0:42.075.879,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,6022,0:42.075.882,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 DA CF 22 79 58 FB D5 09 FC 92 CC EE 86 9C B1… 0,,6026,0:42.076.879,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,6027,0:42.091.884,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 8A A0 FB E8 5A EE F3 3E F8 E9 AE BE 48 90 A8… 0,,6031,0:42.092.881,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,6032,0:42.106.886,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6036,0:42.107.883,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,6037,0:42.107.887,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 8A A0 FB E8 5A EE F3 3E F8 E9 AE BE 48 90 A8… 0,,6041,0:42.108.883,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,6042,0:42.123.889,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 8B 55 1C B2 14 21 46 C1 E4 74 B8 73 6F B1 4F… 0,,6046,0:42.124.886,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,6047,0:42.138.891,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6051,0:42.139.888,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,6052,0:42.139.891,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 8B 55 1C B2 14 21 46 C1 E4 74 B8 73 6F B1 4F… 0,,6056,0:42.140.888,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,6057,0:42.155.893,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 1A FF 71 8D 9B E9 6B A9 41 37 DB DD E4 BC DC… 0,,6061,0:42.156.890,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,6062,0:42.170.895,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6066,0:42.171.892,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,6067,0:42.171.895,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 1A FF 71 8D 9B E9 6B A9 41 37 DB DD E4 BC DC… 0,,6071,0:42.172.892,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,6072,0:42.187.898,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 A6 C6 E6 15 40 FF C1 A4 9E 09 2B 5F 20 66 73… 0,,6076,0:42.188.895,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,6077,0:42.202.900,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6081,0:42.203.897,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,6082,0:42.203.900,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 A6 C6 E6 15 40 FF C1 A4 9E 09 2B 5F 20 66 73… 0,,6086,0:42.204.897,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,6087,0:42.219.902,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 2C 73 C1 6C 5F C5 81 D2 AB 05 2F EC 40 E1 4D… 0,,6091,0:42.220.899,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,6092,0:42.234.904,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6096,0:42.235.901,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,6097,0:42.235.904,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 2C 73 C1 6C 5F C5 81 D2 AB 05 2F EC 40 E1 4D… 0,,6101,0:42.236.901,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,6102,0:42.251.907,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A E8 BB 93 4A 78 0A EA 9E 28 E9 20 18 C0 B7 07… 0,,6106,0:42.252.903,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,6107,0:42.266.909,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6111,0:42.267.905,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,6112,0:42.267.909,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A E8 BB 93 4A 78 0A EA 9E 28 E9 20 18 C0 B7 07… 0,,6116,0:42.268.906,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,6117,0:42.283.911,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 C2 EE 6E 63 AD 46 81 BE 41 58 B1 F3 83 E8 C0… 0,,6121,0:42.284.908,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,6122,0:42.298.913,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6126,0:42.299.910,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,6127,0:42.299.913,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 C2 EE 6E 63 AD 46 81 BE 41 58 B1 F3 83 E8 C0… 0,,6131,0:42.300.910,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,6132,0:42.315.915,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 06 50 AF CE 53 D0 53 0F C2 AC 7B 8D EC 95 CE… 0,,6136,0:42.316.912,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,6137,0:42.330.917,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6141,0:42.331.914,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,6142,0:42.331.918,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 06 50 AF CE 53 D0 53 0F C2 AC 7B 8D EC 95 CE… 0,,6146,0:42.332.914,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,6147,0:42.347.920,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 DD C8 EB 92 7F 8F BB 43 BD 96 80 D9 4A 18 13… 0,,6151,0:42.348.917,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,6152,0:42.362.922,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6156,0:42.363.919,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,6157,0:42.363.922,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 DD C8 EB 92 7F 8F BB 43 BD 96 80 D9 4A 18 13… 0,,6161,0:42.364.919,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,6162,0:42.379.924,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6166,0:42.380.921,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,6167,0:42.394.926,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6171,0:42.395.923,16.005.041 ms,,,,,[17 SOF],[Frames: 1377 - 1393] 0,,6172,0:42.411.929,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6176,0:42.412.926,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,6177,0:42.426.931,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6181,0:42.427.928,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,6182,0:42.427.931,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 02 1E 0C 61 93 28 BB B0 BA 83 B8 02 1A DB E1… 0,,6186,0:42.428.928,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,6187,0:42.443.933,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 42 29 42 9F F5 3C 02 31 14 5C 96 11 A6 AE B1… 0,,6191,0:42.444.930,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,6192,0:42.458.935,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6196,0:42.459.932,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,6197,0:42.459.935,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 42 29 42 9F F5 3C 02 31 14 5C 96 11 A6 AE B1… 0,,6201,0:42.460.932,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,6202,0:42.475.938,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B3 4B 3E 4D 30 3E 79 30 26 5F F0 24 18 BC 00… 0,,6206,0:42.476.934,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,6207,0:42.490.940,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6211,0:42.491.937,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,6212,0:42.491.940,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B3 4B 3E 4D 30 3E 79 30 26 5F F0 24 18 BC 00… 0,,6216,0:42.492.937,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,6217,0:42.507.942,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 03 40 E6 3C 7D F6 3F 11 83 F6 78 AD 3B 70 58… 0,,6221,0:42.508.939,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,6222,0:42.522.944,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6226,0:42.523.941,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,6227,0:42.523.944,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 03 40 E6 3C 7D F6 3F 11 83 F6 78 AD 3B 70 58… 0,,6231,0:42.524.941,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,6232,0:42.539.946,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB E8 59 D2 89 3A E5 0E 6A 2F 91 1C F4 F7 B2 DE… 0,,6236,0:42.540.943,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,6237,0:42.554.949,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6241,0:42.555.945,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,6242,0:42.555.949,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB E8 59 D2 89 3A E5 0E 6A 2F 91 1C F4 F7 B2 DE… 0,,6246,0:42.556.946,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,6247,0:42.571.951,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 CB BA FA 44 65 02 BC A7 72 77 DE 3F 44 EF AE… 0,,6251,0:42.572.948,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,6252,0:42.586.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6256,0:42.587.950,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,6257,0:42.587.953,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 CB BA FA 44 65 02 BC A7 72 77 DE 3F 44 EF AE… 0,,6261,0:42.588.950,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,6262,0:42.603.955,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 AC C6 1E E1 19 FC 70 45 32 52 21 9E E6 E8 65… 0,,6266,0:42.604.952,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,6267,0:42.618.957,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6271,0:42.619.954,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,6272,0:42.619.958,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 AC C6 1E E1 19 FC 70 45 32 52 21 9E E6 E8 65… 0,,6276,0:42.620.954,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,6277,0:42.635.960,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 5E DB C5 5C B7 58 3B DE EB 7D D1 10 B6 59 DB… 0,,6281,0:42.636.957,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,6282,0:42.650.962,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6286,0:42.651.959,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,6287,0:42.651.962,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 5E DB C5 5C B7 58 3B DE EB 7D D1 10 B6 59 DB… 0,,6291,0:42.652.959,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,6292,0:42.667.964,50.812 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 8A 79 B7 74 4E 5C F0 8D 0E 91 90 F8 AB 0D 06… 0,,6296,0:42.668.961,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,6297,0:42.682.966,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6301,0:42.683.963,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,6302,0:42.683.966,50.854 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 8A 79 B7 74 4E 5C F0 8D 0E 91 90 F8 AB 0D 06… 0,,6306,0:42.684.963,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,6307,0:42.699.969,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 EA 5D C7 54 B9 7A D7 60 17 2B 2E 1F F6 D8 2B… 0,,6311,0:42.700.966,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,6312,0:42.714.971,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6316,0:42.715.968,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,6317,0:42.715.971,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 EA 5D C7 54 B9 7A D7 60 17 2B 2E 1F F6 D8 2B… 0,,6321,0:42.716.968,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,6322,0:42.731.973,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 2B 22 5E 38 AA 43 97 B4 C7 78 25 EA 95 05 91… 0,,6326,0:42.732.970,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,6327,0:42.746.975,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6331,0:42.747.972,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,6332,0:42.747.975,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 2B 22 5E 38 AA 43 97 B4 C7 78 25 EA 95 05 91… 0,,6336,0:42.748.972,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,6337,0:42.763.978,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF E2 31 C6 B0 D7 B9 CE 64 C2 EC A2 6C AC D1 80… 0,,6341,0:42.764.974,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,6342,0:42.778.980,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6346,0:42.779.977,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,6347,0:42.779.980,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF E2 31 C6 B0 D7 B9 CE 64 C2 EC A2 6C AC D1 80… 0,,6351,0:42.780.977,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,6352,0:42.795.982,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 B2 A2 AB 90 51 01 AF A9 56 4D 4E 87 47 CD 67… 0,,6356,0:42.796.979,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,6357,0:42.810.984,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6361,0:42.811.981,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,6362,0:42.811.984,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 B2 A2 AB 90 51 01 AF A9 56 4D 4E 87 47 CD 67… 0,,6366,0:42.812.981,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,6367,0:42.827.986,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F 9F CF 3A 43 79 A1 93 12 9E 02 A9 89 17 D6 96… 0,,6371,0:42.828.983,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,6372,0:42.842.989,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6376,0:42.843.985,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,6377,0:42.843.989,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F 9F CF 3A 43 79 A1 93 12 9E 02 A9 89 17 D6 96… 0,,6381,0:42.844.986,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,6382,0:42.859.991,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F8 04 1E 26 45 C6 11 A9 33 73 19 5B B3 D3 9D 76… 0,,6386,0:42.860.988,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,6387,0:42.874.993,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6391,0:42.875.990,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,6392,0:42.875.993,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F8 04 1E 26 45 C6 11 A9 33 73 19 5B B3 D3 9D 76… 0,,6396,0:42.876.990,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,6397,0:42.891.995,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 05 12 92 01 BA 7C A8 12 F7 6B D1 AC D3 78 F3… 0,,6401,0:42.892.992,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,6402,0:42.906.997,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6406,0:42.907.994,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,6407,0:42.907.998,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 05 12 92 01 BA 7C A8 12 F7 6B D1 AC D3 78 F3… 0,,6411,0:42.908.994,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,6412,0:42.924.000,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 58 40 81 D2 33 40 4F 78 79 48 AD 5F 4B C8 7F… 0,,6416,0:42.924.997,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,6417,0:42.939.002,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6421,0:42.939.999,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,6422,0:42.940.002,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 58 40 81 D2 33 40 4F 78 79 48 AD 5F 4B C8 7F… 0,,6426,0:42.940.999,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,6427,0:42.956.004,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 43 D5 36 0C 12 1E 2F BE 7A 73 8B 34 BC 86 95… 0,,6431,0:42.957.001,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,6432,0:42.971.006,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6436,0:42.972.003,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,6437,0:42.972.006,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 43 D5 36 0C 12 1E 2F BE 7A 73 8B 34 BC 86 95… 0,,6441,0:42.973.003,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,6442,0:42.988.009,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B A8 54 AE B1 26 F4 EE 27 C6 75 6D 22 19 C3 58… 0,,6446,0:42.989.006,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,6447,0:43.003.011,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6451,0:43.004.008,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,6452,0:43.020.013,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 65 45 6C 53 46 FC 1D AF 34 52 63 C9 0F 06 9C… 0,,6456,0:43.021.010,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,6457,0:43.035.015,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6461,0:43.036.012,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,6462,0:43.036.015,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 65 45 6C 53 46 FC 1D AF 34 52 63 C9 0F 06 9C… 0,,6466,0:43.037.012,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,6467,0:43.052.018,50.729 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 3A 07 9C 6B 1B 17 FF A5 A6 4E BD 1F 4A D6 FF… 0,,6471,0:43.053.014,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,6472,0:43.067.020,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6476,0:43.068.017,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,6477,0:43.068.020,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 3A 07 9C 6B 1B 17 FF A5 A6 4E BD 1F 4A D6 FF… 0,,6481,0:43.069.017,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,6482,0:43.084.022,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 AB 21 A1 70 27 6B E9 81 BE 3A 4B C8 66 D0 05… 0,,6486,0:43.085.019,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,6487,0:43.099.024,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6491,0:43.100.021,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,6492,0:43.100.024,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 AB 21 A1 70 27 6B E9 81 BE 3A 4B C8 66 D0 05… 0,,6496,0:43.101.021,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,6497,0:43.116.026,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 81 62 76 F1 BA 80 10 82 15 F6 9E 83 56 B5 65… 0,,6501,0:43.117.023,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,6502,0:43.131.029,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6506,0:43.132.025,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,6507,0:43.132.029,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 81 62 76 F1 BA 80 10 82 15 F6 9E 83 56 B5 65… 0,,6511,0:43.133.026,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,6512,0:43.148.031,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E D6 B2 14 EB 88 ED 29 FA 88 3E 21 2E E7 64 0B… 0,,6516,0:43.149.028,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,6517,0:43.163.033,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6521,0:43.164.030,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,6522,0:43.164.033,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E D6 B2 14 EB 88 ED 29 FA 88 3E 21 2E E7 64 0B… 0,,6526,0:43.165.030,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,6527,0:43.180.035,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 49 85 96 48 67 6A 09 75 D0 66 20 9A C4 4D 81… 0,,6531,0:43.181.032,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,6532,0:43.195.037,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6536,0:43.196.034,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,6537,0:43.196.038,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 49 85 96 48 67 6A 09 75 D0 66 20 9A C4 4D 81… 0,,6541,0:43.197.034,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,6542,0:43.212.040,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 B8 52 2D 42 6F FE 35 05 0E B2 54 6B 0C 7F 7B… 0,,6546,0:43.213.037,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,6547,0:43.227.042,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6551,0:43.228.039,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,6552,0:43.228.042,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 B8 52 2D 42 6F FE 35 05 0E B2 54 6B 0C 7F 7B… 0,,6556,0:43.229.039,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,6557,0:43.244.044,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 E6 35 AF D5 F1 F1 A5 CA 77 78 EE EF A6 BE D6… 0,,6561,0:43.245.041,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,6562,0:43.259.046,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6566,0:43.260.043,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,6567,0:43.260.046,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 E6 35 AF D5 F1 F1 A5 CA 77 78 EE EF A6 BE D6… 0,,6571,0:43.261.043,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,6572,0:43.276.049,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 DE 2E C1 38 25 07 DE 1A AA EB 7E 47 E0 BA CD… 0,,6576,0:43.277.046,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,6577,0:43.291.051,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6581,0:43.292.048,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,6582,0:43.292.051,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 DE 2E C1 38 25 07 DE 1A AA EB 7E 47 E0 BA CD… 0,,6586,0:43.293.048,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,6587,0:43.308.053,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 C1 6D 87 81 F6 9C 7E 8F D8 AD 49 CA 36 2C C4… 0,,6591,0:43.309.050,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,6592,0:43.323.055,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6596,0:43.324.052,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,6597,0:43.324.055,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 C1 6D 87 81 F6 9C 7E 8F D8 AD 49 CA 36 2C C4… 0,,6601,0:43.325.052,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,6602,0:43.340.058,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 3F D3 97 FB 77 41 39 10 FD 95 E0 3B AF 20 9D… 0,,6606,0:43.341.054,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,6607,0:43.355.060,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6611,0:43.356.057,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,6612,0:43.356.060,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 3F D3 97 FB 77 41 39 10 FD 95 E0 3B AF 20 9D… 0,,6616,0:43.357.057,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,6617,0:43.372.062,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 B4 C9 0C A4 F8 6E 31 83 28 C4 FF 95 89 EC 7B… 0,,6621,0:43.373.059,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,6622,0:43.387.064,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6626,0:43.388.061,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,6627,0:43.388.064,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 B4 C9 0C A4 F8 6E 31 83 28 C4 FF 95 89 EC 7B… 0,,6631,0:43.389.061,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,6632,0:43.404.066,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C CC 8E F8 B3 B0 0E 8E 93 D6 93 AA D8 91 53 5B… 0,,6636,0:43.405.063,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,6637,0:43.419.069,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6641,0:43.420.065,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,6642,0:43.420.069,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C CC 8E F8 B3 B0 0E 8E 93 D6 93 AA D8 91 53 5B… 0,,6646,0:43.421.066,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,6647,0:43.436.071,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 09 E3 98 81 95 D1 67 B7 00 9D E7 14 45 56 AF… 0,,6651,0:43.437.068,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,6652,0:43.451.073,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6656,0:43.452.070,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,6657,0:43.452.073,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 09 E3 98 81 95 D1 67 B7 00 9D E7 14 45 56 AF… 0,,6661,0:43.453.070,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,6662,0:43.468.075,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 0E E8 C6 19 00 5F AE E5 21 18 7C E6 DB 9D 14… 0,,6666,0:43.469.072,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,6667,0:43.483.077,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6671,0:43.484.074,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,6672,0:43.484.078,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E 0E E8 C6 19 00 5F AE E5 21 18 7C E6 DB 9D 14… 0,,6676,0:43.485.074,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,6677,0:43.500.080,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA 71 9E 84 62 C4 D0 08 67 8A 05 D7 6A 03 3A 5F… 0,,6681,0:43.501.077,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,6682,0:43.515.082,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6686,0:43.516.079,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,6687,0:43.516.082,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA 71 9E 84 62 C4 D0 08 67 8A 05 D7 6A 03 3A 5F… 0,,6691,0:43.517.079,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,6692,0:43.532.084,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 6D F8 0C 22 A3 1A 3E 16 B0 A0 15 C9 F8 82 F3… 0,,6696,0:43.533.081,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,6697,0:43.547.086,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6701,0:43.548.083,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,6702,0:43.548.086,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 6D F8 0C 22 A3 1A 3E 16 B0 A0 15 C9 F8 82 F3… 0,,6706,0:43.549.083,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,6707,0:43.564.089,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 7A 6F 26 8A 82 3C FB 9B AB 7A C4 E6 BB C7 14… 0,,6711,0:43.565.086,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,6712,0:43.579.091,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6716,0:43.580.088,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,6717,0:43.580.091,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 7A 6F 26 8A 82 3C FB 9B AB 7A C4 E6 BB C7 14… 0,,6721,0:43.581.088,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,6722,0:43.596.093,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 42 61 37 31 88 FF 0F 56 2D BE AD 01 E9 27 97… 0,,6726,0:43.597.090,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,6727,0:43.611.095,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6731,0:43.612.092,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,6732,0:43.612.095,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 42 61 37 31 88 FF 0F 56 2D BE AD 01 E9 27 97… 0,,6736,0:43.613.092,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,6737,0:43.628.098,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E E7 5B 3F 6C B0 94 A2 53 ED B2 3A C9 0D CA B5… 0,,6741,0:43.629.094,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,6742,0:43.643.100,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6746,0:43.644.097,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,6747,0:43.644.100,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E E7 5B 3F 6C B0 94 A2 53 ED B2 3A C9 0D CA B5… 0,,6751,0:43.645.097,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,6752,0:43.660.102,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 29 6A 50 89 9E A5 C5 21 B5 7B FF B9 E7 8D 34… 0,,6756,0:43.661.099,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,6757,0:43.675.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6761,0:43.676.101,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,6762,0:43.676.104,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 29 6A 50 89 9E A5 C5 21 B5 7B FF B9 E7 8D 34… 0,,6766,0:43.677.101,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,6767,0:43.692.106,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C A0 90 A6 2D 72 28 64 FA 40 3E 42 CA AD 3F BD… 0,,6771,0:43.693.103,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,6772,0:43.707.108,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6776,0:43.708.105,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,6777,0:43.708.109,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C A0 90 A6 2D 72 28 64 FA 40 3E 42 CA AD 3F BD… 0,,6781,0:43.709.106,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,6782,0:43.724.111,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D BF 16 19 DB 60 05 87 4B 79 B0 C1 C5 08 B0 19… 0,,6786,0:43.725.108,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,6787,0:43.739.113,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6791,0:43.740.110,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,6792,0:43.740.113,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D BF 16 19 DB 60 05 87 4B 79 B0 C1 C5 08 B0 19… 0,,6796,0:43.741.110,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,6797,0:43.756.115,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C AF 93 EC EC CC FF BD AF ED 45 4C 18 6E 77 22… 0,,6801,0:43.757.112,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,6802,0:43.771.117,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6806,0:43.772.114,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,6807,0:43.772.118,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C AF 93 EC EC CC FF BD AF ED 45 4C 18 6E 77 22… 0,,6811,0:43.773.114,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,6812,0:43.788.120,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 15 EB 46 28 AD F2 5D F8 0E A7 10 7B F8 27 A8… 0,,6816,0:43.789.117,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,6817,0:43.803.122,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6821,0:43.804.119,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,6822,0:43.804.122,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 15 EB 46 28 AD F2 5D F8 0E A7 10 7B F8 27 A8… 0,,6826,0:43.805.119,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,6827,0:43.820.124,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 0D CA 40 F5 6F 75 EB 7C E4 2E F0 FA C3 6B D0… 0,,6831,0:43.821.121,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,6832,0:43.835.126,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6836,0:43.836.123,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,6837,0:43.836.126,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 0D CA 40 F5 6F 75 EB 7C E4 2E F0 FA C3 6B D0… 0,,6841,0:43.837.123,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,6842,0:43.852.129,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 0E 63 B4 26 16 C6 49 1D 4F 59 1F C8 AE A1 96… 0,,6846,0:43.853.126,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,6847,0:43.867.131,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6851,0:43.868.128,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,6852,0:43.868.131,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 0E 63 B4 26 16 C6 49 1D 4F 59 1F C8 AE A1 96… 0,,6856,0:43.869.128,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,6857,0:43.884.133,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 68 1B 79 0A A2 A9 9C 9B 4D 42 A3 90 51 F3 20… 0,,6861,0:43.885.130,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,6862,0:43.899.135,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6866,0:43.900.132,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,6867,0:43.900.135,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 68 1B 79 0A A2 A9 9C 9B 4D 42 A3 90 51 F3 20… 0,,6871,0:43.901.132,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,6872,0:43.916.138,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 8B DF 36 2B 4F 0E 8E 95 A1 32 00 BC 38 91 DE… 0,,6876,0:43.917.134,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,6877,0:43.931.140,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6881,0:43.932.136,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,6882,0:43.932.140,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 8B DF 36 2B 4F 0E 8E 95 A1 32 00 BC 38 91 DE… 0,,6886,0:43.933.137,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,6887,0:43.948.142,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 37 9B A3 26 80 43 7D C7 8B F2 95 BC CB 6A B0… 0,,6891,0:43.949.139,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,6892,0:43.963.144,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6896,0:43.964.141,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,6897,0:43.964.144,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 37 9B A3 26 80 43 7D C7 8B F2 95 BC CB 6A B0… 0,,6901,0:43.965.141,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,6902,0:43.980.146,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 97 08 BE CE 8F 22 B4 19 32 81 BD 2E D0 E1 5C… 0,,6906,0:43.981.143,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,6907,0:43.995.148,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6911,0:43.996.145,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,6912,0:43.996.149,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 97 08 BE CE 8F 22 B4 19 32 81 BD 2E D0 E1 5C… 0,,6916,0:43.997.146,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,6917,0:44.012.151,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA B5 72 5F 51 1D B8 9D CF 94 03 A7 2A 46 1B E1… 0,,6921,0:44.013.148,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,6922,0:44.027.153,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6926,0:44.028.150,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,6927,0:44.028.153,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA B5 72 5F 51 1D B8 9D CF 94 03 A7 2A 46 1B E1… 0,,6931,0:44.029.150,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,6932,0:44.044.155,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 17 00 B2 04 65 2C DA 39 83 80 6F CD 2B 7E 1E… 0,,6936,0:44.045.152,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,6937,0:44.059.157,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6941,0:44.060.154,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,6942,0:44.060.158,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 17 00 B2 04 65 2C DA 39 83 80 6F CD 2B 7E 1E… 0,,6946,0:44.061.154,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,6947,0:44.076.160,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 38 67 41 12 3D C7 B8 24 0B 27 CB 6C 86 43 D9… 0,,6951,0:44.077.157,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,6952,0:44.091.162,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6956,0:44.092.159,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,6957,0:44.092.162,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 38 67 41 12 3D C7 B8 24 0B 27 CB 6C 86 43 D9… 0,,6961,0:44.093.159,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,6962,0:44.108.164,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 7E E1 5B AF 5A B4 9A B6 A7 95 CB 29 14 68 70… 0,,6966,0:44.109.161,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,6967,0:44.123.166,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6971,0:44.124.163,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,6972,0:44.124.166,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 7E E1 5B AF 5A B4 9A B6 A7 95 CB 29 14 68 70… 0,,6976,0:44.125.163,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,6977,0:44.140.169,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 7F AA 49 AF C5 15 AA 86 AC 8F 50 7C E8 AA 20… 0,,6981,0:44.141.165,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,6982,0:44.155.171,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,6986,0:44.156.168,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,6987,0:44.156.171,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 7F AA 49 AF C5 15 AA 86 AC 8F 50 7C E8 AA 20… 0,,6991,0:44.157.168,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,6992,0:44.172.173,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6F 0A 18 7E C0 02 17 D4 5D 3A F6 D6 AB 7B 1D… 0,,6996,0:44.173.170,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,6997,0:44.187.175,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7001,0:44.188.172,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,7002,0:44.188.175,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6F 0A 18 7E C0 02 17 D4 5D 3A F6 D6 AB 7B 1D… 0,,7006,0:44.189.172,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,7007,0:44.204.177,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 69 57 98 ED 6E C3 05 36 95 E4 F6 EC E2 C5 0B… 0,,7011,0:44.205.174,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,7012,0:44.219.180,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7016,0:44.220.176,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,7017,0:44.220.180,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 69 57 98 ED 6E C3 05 36 95 E4 F6 EC E2 C5 0B… 0,,7021,0:44.221.177,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,7022,0:44.236.182,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 2A D0 CB F6 61 FF B7 06 92 2B C6 BA 5F F8 42… 0,,7026,0:44.237.179,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,7027,0:44.251.184,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7031,0:44.252.181,16.005.041 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,7032,0:44.268.186,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 BC 18 C2 97 1B CC 80 D5 6E CB 85 F0 51 6C 7B… 0,,7036,0:44.269.183,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,7037,0:44.283.188,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7041,0:44.284.185,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,7042,0:44.284.189,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 BC 18 C2 97 1B CC 80 D5 6E CB 85 F0 51 6C 7B… 0,,7046,0:44.285.185,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,7047,0:44.300.191,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 1F ED 17 49 33 06 4A FB C8 28 2C 17 C7 BC 8E… 0,,7051,0:44.301.188,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,7052,0:44.315.193,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7056,0:44.316.190,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,7057,0:44.316.193,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 1F ED 17 49 33 06 4A FB C8 28 2C 17 C7 BC 8E… 0,,7061,0:44.317.190,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,7062,0:44.332.195,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 80 D5 FD 88 DB C8 9A 7E 4E 73 E4 C6 EF 78 75… 0,,7066,0:44.333.192,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,7067,0:44.347.197,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7071,0:44.348.194,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,7072,0:44.348.197,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 80 D5 FD 88 DB C8 9A 7E 4E 73 E4 C6 EF 78 75… 0,,7076,0:44.349.194,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,7077,0:44.364.200,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 47 60 BD 72 54 03 D4 F6 71 47 9B 8B F3 77 33… 0,,7081,0:44.365.197,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,7082,0:44.379.202,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7086,0:44.380.199,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,7087,0:44.380.202,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 47 60 BD 72 54 03 D4 F6 71 47 9B 8B F3 77 33… 0,,7091,0:44.381.199,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,7092,0:44.396.204,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 8B D9 B6 FD 2D 3F 63 59 BE 92 34 A2 5E E8 EE… 0,,7096,0:44.397.201,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,7097,0:44.411.206,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7101,0:44.412.203,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,7102,0:44.412.206,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 8B D9 B6 FD 2D 3F 63 59 BE 92 34 A2 5E E8 EE… 0,,7106,0:44.413.203,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,7107,0:44.428.209,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E E7 D3 76 A7 17 8D FB E3 BF 84 2F 16 C2 90 E8… 0,,7111,0:44.429.205,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,7112,0:44.443.211,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7116,0:44.444.208,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,7117,0:44.444.211,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E E7 D3 76 A7 17 8D FB E3 BF 84 2F 16 C2 90 E8… 0,,7121,0:44.445.208,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,7122,0:44.460.213,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 3D 06 12 DE 8B 56 7D C3 9C 5A 84 51 42 15 83… 0,,7126,0:44.461.210,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,7127,0:44.475.215,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7131,0:44.476.212,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,7132,0:44.476.215,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 3D 06 12 DE 8B 56 7D C3 9C 5A 84 51 42 15 83… 0,,7136,0:44.477.212,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,7137,0:44.492.217,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 9E 1E 7B DE FD 39 7A 05 47 C0 6D 57 85 11 A5… 0,,7141,0:44.493.214,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,7142,0:44.507.220,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7146,0:44.508.216,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,7147,0:44.508.220,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 9E 1E 7B DE FD 39 7A 05 47 C0 6D 57 85 11 A5… 0,,7151,0:44.509.217,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,7152,0:44.524.222,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 C2 77 FA F9 10 49 83 A9 D2 D1 3A 8B F2 A6 A1… 0,,7156,0:44.525.219,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,7157,0:44.539.224,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7161,0:44.540.221,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,7162,0:44.540.224,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 C2 77 FA F9 10 49 83 A9 D2 D1 3A 8B F2 A6 A1… 0,,7166,0:44.541.221,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,7167,0:44.556.226,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 93 78 D7 69 B2 50 C3 56 29 3A 06 26 69 1C 3C… 0,,7171,0:44.557.223,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,7172,0:44.571.229,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7176,0:44.572.225,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,7177,0:44.572.229,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 93 78 D7 69 B2 50 C3 56 29 3A 06 26 69 1C 3C… 0,,7181,0:44.573.225,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,7182,0:44.588.231,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD D1 CD 02 16 52 F2 D0 0F 51 0D 95 7E 47 22 00… 0,,7186,0:44.589.228,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,7187,0:44.603.233,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7191,0:44.604.230,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,7192,0:44.604.233,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD D1 CD 02 16 52 F2 D0 0F 51 0D 95 7E 47 22 00… 0,,7196,0:44.605.230,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,7197,0:44.620.235,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 41 1B 9A 14 AB 81 3F 39 6D A7 A0 13 11 19 F0… 0,,7201,0:44.621.232,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,7202,0:44.635.237,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7206,0:44.636.234,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,7207,0:44.636.237,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 41 1B 9A 14 AB 81 3F 39 6D A7 A0 13 11 19 F0… 0,,7211,0:44.637.234,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,7212,0:44.652.240,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 4F C1 84 6E 73 8D A1 88 2C CF 81 D1 70 C0 9D… 0,,7216,0:44.653.237,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,7217,0:44.667.242,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7221,0:44.668.239,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,7222,0:44.668.242,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 4F C1 84 6E 73 8D A1 88 2C CF 81 D1 70 C0 9D… 0,,7226,0:44.669.239,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,7227,0:44.684.244,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 6B CF 0B 9F 92 3B 6B DC C5 BA 3C 72 8C 65 C9… 0,,7231,0:44.685.241,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,7232,0:44.699.246,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7236,0:44.700.243,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,7237,0:44.700.246,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 6B CF 0B 9F 92 3B 6B DC C5 BA 3C 72 8C 65 C9… 0,,7241,0:44.701.243,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,7242,0:44.716.249,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 20 57 E7 9F D7 44 E8 5A 99 57 E3 38 C1 F7 35… 0,,7246,0:44.717.245,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,7247,0:44.731.251,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7251,0:44.732.248,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,7252,0:44.732.251,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 20 57 E7 9F D7 44 E8 5A 99 57 E3 38 C1 F7 35… 0,,7256,0:44.733.248,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,7257,0:44.748.253,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE FF 7D CA 09 C4 51 96 57 40 FA 88 0C 12 67 3C… 0,,7261,0:44.749.250,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,7262,0:44.763.255,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7266,0:44.764.252,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,7267,0:44.764.255,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE FF 7D CA 09 C4 51 96 57 40 FA 88 0C 12 67 3C… 0,,7271,0:44.765.252,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,7272,0:44.780.257,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 94 A3 61 B9 7A EF 7E 62 AB 57 5E 5C 3B 6B 5E… 0,,7276,0:44.781.254,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,7277,0:44.795.260,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7281,0:44.796.256,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,7282,0:44.796.260,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 94 A3 61 B9 7A EF 7E 62 AB 57 5E 5C 3B 6B 5E… 0,,7286,0:44.797.257,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,7287,0:44.812.262,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 1B E5 1A A2 1E 16 35 1B 63 85 18 EF 67 B8 9C… 0,,7291,0:44.813.259,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,7292,0:44.827.264,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7296,0:44.828.261,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,7297,0:44.828.264,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 1B E5 1A A2 1E 16 35 1B 63 85 18 EF 67 B8 9C… 0,,7301,0:44.829.261,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,7302,0:44.844.266,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6F 73 23 65 C2 26 F8 49 4D 54 04 1C D2 71 A3 63… 0,,7306,0:44.845.263,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,7307,0:44.859.268,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7311,0:44.860.265,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,7312,0:44.860.269,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6F 73 23 65 C2 26 F8 49 4D 54 04 1C D2 71 A3 63… 0,,7316,0:44.861.265,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,7317,0:44.876.271,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 2F 45 F9 44 08 D9 61 2B 73 BD 18 F9 E8 85 B3… 0,,7321,0:44.877.268,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,7322,0:44.891.273,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7326,0:44.892.270,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,7327,0:44.892.273,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 2F 45 F9 44 08 D9 61 2B 73 BD 18 F9 E8 85 B3… 0,,7331,0:44.893.270,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,7332,0:44.908.275,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 B1 B2 F5 CE 94 1A 01 CD 21 8B 83 FB 28 BE A8… 0,,7336,0:44.909.272,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,7337,0:44.923.277,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7341,0:44.924.274,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,7342,0:44.924.277,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 B1 B2 F5 CE 94 1A 01 CD 21 8B 83 FB 28 BE A8… 0,,7346,0:44.925.274,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,7347,0:44.940.280,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 96 2F 77 B2 16 25 2F DD 57 2D 79 B7 30 C1 E2… 0,,7351,0:44.941.277,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,7352,0:44.955.282,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7356,0:44.956.279,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,7357,0:44.956.282,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 96 2F 77 B2 16 25 2F DD 57 2D 79 B7 30 C1 E2… 0,,7361,0:44.957.279,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,7362,0:44.972.284,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 93 6A E4 32 32 A6 01 6F EA 74 55 36 54 71 77… 0,,7366,0:44.973.281,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,7367,0:44.987.286,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7371,0:44.988.283,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,7372,0:44.988.286,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 93 6A E4 32 32 A6 01 6F EA 74 55 36 54 71 77… 0,,7376,0:44.989.283,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,7377,0:45.004.289,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 D8 B3 3B F8 57 01 DF A3 45 F6 1B 58 AE DA CC… 0,,7381,0:45.005.285,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,7382,0:45.019.291,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7386,0:45.020.288,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,7387,0:45.020.291,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 D8 B3 3B F8 57 01 DF A3 45 F6 1B 58 AE DA CC… 0,,7391,0:45.021.288,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,7392,0:45.036.293,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 70 80 2D AE 7E 15 1F 43 F0 0B D1 A3 E0 34 4D… 0,,7396,0:45.037.290,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,7397,0:45.051.295,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7401,0:45.052.292,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,7402,0:45.052.295,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 70 80 2D AE 7E 15 1F 43 F0 0B D1 A3 E0 34 4D… 0,,7406,0:45.053.292,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,7407,0:45.068.298,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 38 53 88 2D F7 F2 0A 21 B0 10 6C 8E 3C 8D 16… 0,,7411,0:45.069.294,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,7412,0:45.083.300,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7416,0:45.084.296,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,7417,0:45.084.300,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 38 53 88 2D F7 F2 0A 21 B0 10 6C 8E 3C 8D 16… 0,,7421,0:45.085.297,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,7422,0:45.100.302,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 37 C9 86 52 E0 1B B4 0F 3A 3C 9F EF 18 93 46… 0,,7426,0:45.101.299,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,7427,0:45.115.304,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7431,0:45.116.301,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,7432,0:45.116.304,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 37 C9 86 52 E0 1B B4 0F 3A 3C 9F EF 18 93 46… 0,,7436,0:45.117.301,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,7437,0:45.132.306,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 95 04 EC 45 07 95 46 C0 62 85 89 33 03 7D A1… 0,,7441,0:45.133.303,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,7442,0:45.147.308,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7446,0:45.148.305,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,7447,0:45.148.309,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 95 04 EC 45 07 95 46 C0 62 85 89 33 03 7D A1… 0,,7451,0:45.149.305,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,7452,0:45.164.311,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 AF 78 A2 E0 54 F8 FB 37 E2 35 E3 08 63 99 A7… 0,,7456,0:45.165.308,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,7457,0:45.179.313,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7461,0:45.180.310,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,7462,0:45.180.313,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 AF 78 A2 E0 54 F8 FB 37 E2 35 E3 08 63 99 A7… 0,,7466,0:45.181.310,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,7467,0:45.196.315,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C A9 12 30 3F 62 A5 DB 73 6B F8 5F 58 1B D4 03… 0,,7471,0:45.197.312,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,7472,0:45.211.317,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7476,0:45.212.314,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,7477,0:45.212.317,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C A9 12 30 3F 62 A5 DB 73 6B F8 5F 58 1B D4 03… 0,,7481,0:45.213.314,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,7482,0:45.228.320,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 83 C1 63 3B 10 5D 90 8C 6C B9 E8 7E 33 E3 16… 0,,7486,0:45.229.317,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,7487,0:45.243.322,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7491,0:45.244.319,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,7492,0:45.244.322,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 83 C1 63 3B 10 5D 90 8C 6C B9 E8 7E 33 E3 16… 0,,7496,0:45.245.319,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,7497,0:45.260.324,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 1A C1 27 D7 03 2C 3B D7 5C 04 7D 4D 72 C6 EE… 0,,7501,0:45.261.321,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,7502,0:45.275.326,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7506,0:45.276.323,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,7507,0:45.276.326,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 1A C1 27 D7 03 2C 3B D7 5C 04 7D 4D 72 C6 EE… 0,,7511,0:45.277.323,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,7512,0:45.292.329,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 81 5A B3 B4 DE C8 1D F8 24 3D 7B 14 24 4A 88… 0,,7516,0:45.293.325,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,7517,0:45.307.331,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7521,0:45.308.328,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,7522,0:45.308.331,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 81 5A B3 B4 DE C8 1D F8 24 3D 7B 14 24 4A 88… 0,,7526,0:45.309.328,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,7527,0:45.324.333,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF 17 78 49 58 CB F2 39 5D 45 EA C1 20 4E D3 88… 0,,7531,0:45.325.330,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,7532,0:45.339.335,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7536,0:45.340.332,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,7537,0:45.340.335,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF 17 78 49 58 CB F2 39 5D 45 EA C1 20 4E D3 88… 0,,7541,0:45.341.332,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,7542,0:45.356.337,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F B0 3A A2 60 45 95 AC E7 E6 DD D7 F3 22 A6 21… 0,,7546,0:45.357.334,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,7547,0:45.371.340,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7551,0:45.372.336,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,7552,0:45.372.340,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F B0 3A A2 60 45 95 AC E7 E6 DD D7 F3 22 A6 21… 0,,7556,0:45.373.337,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,7557,0:45.388.342,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 A4 91 D1 15 54 64 87 D6 60 A9 05 E0 1F D8 D1… 0,,7561,0:45.389.339,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,7562,0:45.403.344,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7566,0:45.404.341,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,7567,0:45.404.344,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 A4 91 D1 15 54 64 87 D6 60 A9 05 E0 1F D8 D1… 0,,7571,0:45.405.341,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,7572,0:45.420.346,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 60 3E 87 84 A8 A3 5F 47 AE A5 25 14 57 F1 7A… 0,,7576,0:45.421.343,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,7577,0:45.435.348,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7581,0:45.436.345,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,7582,0:45.436.349,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 60 3E 87 84 A8 A3 5F 47 AE A5 25 14 57 F1 7A… 0,,7586,0:45.437.345,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,7587,0:45.452.351,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 21 73 CC DA E8 45 5E ED 2C 08 F9 5E CE 3B 11… 0,,7591,0:45.453.348,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,7592,0:45.467.353,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7596,0:45.468.350,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,7597,0:45.468.353,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 21 73 CC DA E8 45 5E ED 2C 08 F9 5E CE 3B 11… 0,,7601,0:45.469.350,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,7602,0:45.484.355,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 97 5A B5 C5 37 5D 3E 58 64 26 29 E5 D2 EC 66… 0,,7606,0:45.485.352,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,7607,0:45.499.357,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7611,0:45.500.354,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,7612,0:45.516.360,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F C0 8D 89 92 E0 4D A9 76 90 15 7B A4 9B 5F 7F… 0,,7616,0:45.517.357,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,7617,0:45.531.362,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7621,0:45.532.359,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,7622,0:45.532.362,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F C0 8D 89 92 E0 4D A9 76 90 15 7B A4 9B 5F 7F… 0,,7626,0:45.533.359,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,7627,0:45.548.364,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC AD A7 26 19 A9 87 48 39 F8 42 60 02 5D F0 1B… 0,,7631,0:45.549.361,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,7632,0:45.563.366,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7636,0:45.564.363,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,7637,0:45.564.366,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC AD A7 26 19 A9 87 48 39 F8 42 60 02 5D F0 1B… 0,,7641,0:45.565.363,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,7642,0:45.580.369,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 51 35 55 24 D0 F6 8B 4B 6D D2 59 97 4A 62 56… 0,,7646,0:45.581.365,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,7647,0:45.595.371,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7651,0:45.596.367,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,7652,0:45.596.371,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 51 35 55 24 D0 F6 8B 4B 6D D2 59 97 4A 62 56… 0,,7656,0:45.597.368,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,7657,0:45.612.373,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 C5 B5 9B B1 7D EA CC A4 E2 B8 E1 91 33 F1 2A… 0,,7661,0:45.613.370,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,7662,0:45.627.375,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7666,0:45.628.372,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,7667,0:45.628.375,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 C5 B5 9B B1 7D EA CC A4 E2 B8 E1 91 33 F1 2A… 0,,7671,0:45.629.372,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,7672,0:45.644.377,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 11 CA 4B 1C BF A5 8D 88 14 E7 B5 8A 02 63 8C… 0,,7676,0:45.645.374,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,7677,0:45.659.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7681,0:45.660.376,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,7682,0:45.660.380,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 11 CA 4B 1C BF A5 8D 88 14 E7 B5 8A 02 63 8C… 0,,7686,0:45.661.377,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,7687,0:45.676.382,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 0D 4C 97 53 5D EF B1 A5 D2 AB 44 3D 5C 11 FC… 0,,7691,0:45.677.379,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,7692,0:45.691.384,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7696,0:45.692.381,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,7697,0:45.692.384,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 0D 4C 97 53 5D EF B1 A5 D2 AB 44 3D 5C 11 FC… 0,,7701,0:45.693.381,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,7702,0:45.708.386,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 67 25 14 73 19 CA E9 E1 64 E1 02 8E C7 37 07… 0,,7706,0:45.709.383,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,7707,0:45.723.388,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7711,0:45.724.385,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,7712,0:45.724.389,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 67 25 14 73 19 CA E9 E1 64 E1 02 8E C7 37 07… 0,,7716,0:45.725.385,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,7717,0:45.740.391,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC C0 5F 3E D8 B3 66 96 21 98 22 40 95 B2 79 B3… 0,,7721,0:45.741.388,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,7722,0:45.755.393,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7726,0:45.756.390,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,7727,0:45.756.393,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC C0 5F 3E D8 B3 66 96 21 98 22 40 95 B2 79 B3… 0,,7731,0:45.757.390,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,7732,0:45.772.395,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 4D 6A F5 0F 6B 6C 8C 57 5C 44 00 6C 16 6D 19… 0,,7736,0:45.773.392,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,7737,0:45.787.397,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7741,0:45.788.394,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,7742,0:45.788.397,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 4D 6A F5 0F 6B 6C 8C 57 5C 44 00 6C 16 6D 19… 0,,7746,0:45.789.394,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,7747,0:45.804.400,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 69 EE 6A DC 5A 83 E1 69 48 F3 77 21 9C 1F C3… 0,,7751,0:45.805.397,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,7752,0:45.819.402,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7756,0:45.820.399,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,7757,0:45.820.402,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 69 EE 6A DC 5A 83 E1 69 48 F3 77 21 9C 1F C3… 0,,7761,0:45.821.399,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,7762,0:45.836.404,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 8F 96 7E 04 EB 7A B1 EB E5 9D 32 F7 60 A3 A4… 0,,7766,0:45.837.401,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,7767,0:45.851.406,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7771,0:45.852.403,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,7772,0:45.852.406,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 8F 96 7E 04 EB 7A B1 EB E5 9D 32 F7 60 A3 A4… 0,,7776,0:45.853.403,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,7777,0:45.868.409,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 31 78 71 F6 28 AA 72 8D 8B A1 7A 34 FD 37 AE… 0,,7781,0:45.869.405,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,7782,0:45.883.411,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7786,0:45.884.407,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,7787,0:45.884.411,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 31 78 71 F6 28 AA 72 8D 8B A1 7A 34 FD 37 AE… 0,,7791,0:45.885.408,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,7792,0:45.900.413,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 B5 F9 C0 5B 1F C1 F4 04 CB E6 E0 32 71 03 3C… 0,,7796,0:45.901.410,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,7797,0:45.915.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7801,0:45.916.412,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,7802,0:45.916.415,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 B5 F9 C0 5B 1F C1 F4 04 CB E6 E0 32 71 03 3C… 0,,7806,0:45.917.412,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,7807,0:45.932.417,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 F9 BD 4F 43 B0 27 3C 73 34 7C D6 2F EB 7D 98… 0,,7811,0:45.933.414,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,7812,0:45.947.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7816,0:45.948.416,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,7817,0:45.948.420,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 F9 BD 4F 43 B0 27 3C 73 34 7C D6 2F EB 7D 98… 0,,7821,0:45.949.416,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,7822,0:45.964.422,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 7C B3 74 CA 7B D5 AF 56 FE 1E F4 B0 52 2E 28… 0,,7826,0:45.965.419,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,7827,0:45.979.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7831,0:45.980.421,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,7832,0:45.980.424,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 7C B3 74 CA 7B D5 AF 56 FE 1E F4 B0 52 2E 28… 0,,7836,0:45.981.421,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,7837,0:45.996.426,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 0B 86 24 1D C3 98 A5 F1 0A B0 3F 3F 24 F6 42… 0,,7841,0:45.997.423,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,7842,0:46.011.428,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7846,0:46.012.425,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,7847,0:46.012.428,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 0B 86 24 1D C3 98 A5 F1 0A B0 3F 3F 24 F6 42… 0,,7851,0:46.013.425,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,7852,0:46.028.431,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 5F 2E FB 4F 5D F0 59 16 D7 2B 74 A0 8E 18 C7… 0,,7856,0:46.029.428,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,7857,0:46.043.433,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7861,0:46.044.430,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,7862,0:46.044.433,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 5F 2E FB 4F 5D F0 59 16 D7 2B 74 A0 8E 18 C7… 0,,7866,0:46.045.430,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,7867,0:46.060.435,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 65 B4 53 20 6A 4B 58 43 C7 3D B5 BD 67 A4 C2… 0,,7871,0:46.061.432,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,7872,0:46.075.437,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7876,0:46.076.434,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,7877,0:46.076.437,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 65 B4 53 20 6A 4B 58 43 C7 3D B5 BD 67 A4 C2… 0,,7881,0:46.077.434,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,7882,0:46.092.440,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 5B 3F 84 E5 71 F4 3F 09 5D 73 A8 4D 05 B8 A9… 0,,7886,0:46.093.436,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,7887,0:46.107.442,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7891,0:46.108.439,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,7892,0:46.124.444,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 B3 72 1E BB 53 71 7E 6F D4 71 59 78 26 B6 0B… 0,,7896,0:46.125.441,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,7897,0:46.139.446,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7901,0:46.140.443,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,7902,0:46.140.446,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 B3 72 1E BB 53 71 7E 6F D4 71 59 78 26 B6 0B… 0,,7906,0:46.141.443,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,7907,0:46.156.448,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 72 B8 05 01 19 0D 06 8A C7 19 AC 71 96 4B 73… 0,,7911,0:46.157.445,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,7912,0:46.171.451,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7916,0:46.172.447,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,7917,0:46.172.451,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 72 B8 05 01 19 0D 06 8A C7 19 AC 71 96 4B 73… 0,,7921,0:46.173.448,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,7922,0:46.188.453,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 11 64 D6 22 CC C9 A5 C3 D1 BC 4B E6 5C 98 5C… 0,,7926,0:46.189.450,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,7927,0:46.203.455,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7931,0:46.204.452,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,7932,0:46.204.455,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 11 64 D6 22 CC C9 A5 C3 D1 BC 4B E6 5C 98 5C… 0,,7936,0:46.205.452,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,7937,0:46.220.457,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 68 F5 49 23 40 53 70 06 AC A8 94 D6 B2 C0 F1… 0,,7941,0:46.221.454,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,7942,0:46.235.459,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7946,0:46.236.456,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,7947,0:46.236.460,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 68 F5 49 23 40 53 70 06 AC A8 94 D6 B2 C0 F1… 0,,7951,0:46.237.456,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,7952,0:46.252.462,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 99 A6 DC D8 41 49 E9 9D C1 CC 7B D6 71 26 0A… 0,,7956,0:46.253.459,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,7957,0:46.267.464,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7961,0:46.268.461,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,7962,0:46.268.464,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 99 A6 DC D8 41 49 E9 9D C1 CC 7B D6 71 26 0A… 0,,7966,0:46.269.461,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,7967,0:46.284.466,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 38 5F 46 0B C9 6D 42 83 55 4B 1F 73 DD 77 10… 0,,7971,0:46.285.463,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,7972,0:46.299.468,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7976,0:46.300.465,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,7977,0:46.300.468,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 38 5F 46 0B C9 6D 42 83 55 4B 1F 73 DD 77 10… 0,,7981,0:46.301.465,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,7982,0:46.316.471,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 F2 31 E0 98 84 F0 60 64 1F 01 46 69 8A 02 7B… 0,,7986,0:46.317.468,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,7987,0:46.331.473,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,7991,0:46.332.470,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,7992,0:46.332.473,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 F2 31 E0 98 84 F0 60 64 1F 01 46 69 8A 02 7B… 0,,7996,0:46.333.470,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,7997,0:46.348.475,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 12 B0 16 76 6A 8D B2 84 7C A9 AA 7B 0D 1F 16… 0,,8001,0:46.349.472,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,8002,0:46.363.477,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8006,0:46.364.474,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,8007,0:46.364.477,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 12 B0 16 76 6A 8D B2 84 7C A9 AA 7B 0D 1F 16… 0,,8011,0:46.365.474,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,8012,0:46.380.480,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 DA B9 EE A7 35 33 89 C2 2E 05 79 ED C1 68 71… 0,,8016,0:46.381.476,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,8017,0:46.395.482,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8021,0:46.396.479,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,8022,0:46.396.482,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 DA B9 EE A7 35 33 89 C2 2E 05 79 ED C1 68 71… 0,,8026,0:46.397.479,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,8027,0:46.412.484,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 F1 85 CE 65 12 12 DB 24 DF 87 6D 96 5A 85 0D… 0,,8031,0:46.413.481,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,8032,0:46.427.486,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8036,0:46.428.483,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,8037,0:46.428.486,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 F1 85 CE 65 12 12 DB 24 DF 87 6D 96 5A 85 0D… 0,,8041,0:46.429.483,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,8042,0:46.444.488,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 E8 71 6E 0A E9 73 3C A7 2A CB A1 91 4B 85 D0… 0,,8046,0:46.445.485,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,8047,0:46.459.491,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8051,0:46.460.487,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,8052,0:46.460.491,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 E8 71 6E 0A E9 73 3C A7 2A CB A1 91 4B 85 D0… 0,,8056,0:46.461.488,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,8057,0:46.476.493,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 4C 2E 19 7A 24 68 64 85 6E F2 BD FB EA 01 88… 0,,8061,0:46.477.490,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,8062,0:46.491.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8066,0:46.492.492,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,8067,0:46.492.495,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 4C 2E 19 7A 24 68 64 85 6E F2 BD FB EA 01 88… 0,,8071,0:46.493.492,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,8072,0:46.508.497,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 0B A0 98 46 88 ED 7A BE 06 64 F4 D2 33 AF 8D… 0,,8076,0:46.509.494,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,8077,0:46.523.499,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8081,0:46.524.496,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,8082,0:46.524.500,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 0B A0 98 46 88 ED 7A BE 06 64 F4 D2 33 AF 8D… 0,,8086,0:46.525.496,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,8087,0:46.540.502,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 89 78 69 EA 7A DF 0B 35 55 93 AD E4 57 99 22… 0,,8091,0:46.541.499,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,8092,0:46.555.504,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8096,0:46.556.501,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,8097,0:46.556.504,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 89 78 69 EA 7A DF 0B 35 55 93 AD E4 57 99 22… 0,,8101,0:46.557.501,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,8102,0:46.572.506,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 E1 E7 BD 55 7C 91 9A 6B A3 4F 3A E9 26 CD DA… 0,,8106,0:46.573.503,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,8107,0:46.587.508,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8111,0:46.588.505,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,8112,0:46.588.508,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 E1 E7 BD 55 7C 91 9A 6B A3 4F 3A E9 26 CD DA… 0,,8116,0:46.589.505,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,8117,0:46.604.511,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 93 CE 2E 1E DE 6B 33 9B E4 1E AA 37 33 66 CB… 0,,8121,0:46.605.508,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,8122,0:46.619.513,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8126,0:46.620.510,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,8127,0:46.620.513,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 93 CE 2E 1E DE 6B 33 9B E4 1E AA 37 33 66 CB… 0,,8131,0:46.621.510,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,8132,0:46.636.515,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF D6 B0 97 45 2B EC 68 97 9F FF 14 5A 30 8E FA… 0,,8136,0:46.637.512,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,8137,0:46.651.517,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8141,0:46.652.514,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,8142,0:46.652.517,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF D6 B0 97 45 2B EC 68 97 9F FF 14 5A 30 8E FA… 0,,8146,0:46.653.514,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,8147,0:46.668.520,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 FC E1 28 8A B1 14 0F F8 9C D0 B5 9B 66 0A DB… 0,,8151,0:46.669.516,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,8152,0:46.683.522,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8156,0:46.684.519,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,8157,0:46.684.522,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 FC E1 28 8A B1 14 0F F8 9C D0 B5 9B 66 0A DB… 0,,8161,0:46.685.519,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,8162,0:46.700.524,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 AA CE 3A 61 9D C4 77 7F F9 97 18 66 54 82 E5… 0,,8166,0:46.701.521,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,8167,0:46.715.526,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8171,0:46.716.523,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,8172,0:46.716.526,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 AA CE 3A 61 9D C4 77 7F F9 97 18 66 54 82 E5… 0,,8176,0:46.717.523,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,8177,0:46.732.528,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 63 0F 3D 61 9A 3F DA 21 FB F3 34 3D 7D 91 56… 0,,8181,0:46.733.525,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,8182,0:46.747.531,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8186,0:46.748.527,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,8187,0:46.764.533,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 9F C4 ED B3 DA B9 A8 69 A1 D7 27 C5 D9 94 5B… 0,,8191,0:46.765.530,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,8192,0:46.779.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8196,0:46.780.532,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,8197,0:46.780.535,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 9F C4 ED B3 DA B9 A8 69 A1 D7 27 C5 D9 94 5B… 0,,8201,0:46.781.532,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,8202,0:46.796.537,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 C1 0E 01 EE 30 F1 13 66 7E 3C 68 60 27 86 B4… 0,,8206,0:46.797.534,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,8207,0:46.811.539,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8211,0:46.812.536,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,8212,0:46.812.540,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 C1 0E 01 EE 30 F1 13 66 7E 3C 68 60 27 86 B4… 0,,8216,0:46.813.536,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,8217,0:46.828.542,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A E7 BB E2 0B A2 D3 B0 23 67 68 F5 1D 9D 00 7D… 0,,8221,0:46.829.539,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,8222,0:46.843.544,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8226,0:46.844.541,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,8227,0:46.844.544,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A E7 BB E2 0B A2 D3 B0 23 67 68 F5 1D 9D 00 7D… 0,,8231,0:46.845.541,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,8232,0:46.860.546,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD F8 CC C1 4C 1F D5 A2 C4 E5 CA 09 85 79 28 F5… 0,,8236,0:46.861.543,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,8237,0:46.875.548,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8241,0:46.876.545,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,8242,0:46.876.549,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD F8 CC C1 4C 1F D5 A2 C4 E5 CA 09 85 79 28 F5… 0,,8246,0:46.877.545,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,8247,0:46.892.551,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 94 18 48 BE A4 7D 88 B4 D7 08 E4 F7 96 B0 1B… 0,,8251,0:46.893.548,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,8252,0:46.907.553,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8256,0:46.908.550,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,8257,0:46.908.553,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 94 18 48 BE A4 7D 88 B4 D7 08 E4 F7 96 B0 1B… 0,,8261,0:46.909.550,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,8262,0:46.924.555,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C B2 D2 74 49 87 14 24 FD E3 90 C3 03 E1 1C 2A… 0,,8266,0:46.925.552,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,8267,0:46.939.557,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8271,0:46.940.554,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,8272,0:46.940.557,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C B2 D2 74 49 87 14 24 FD E3 90 C3 03 E1 1C 2A… 0,,8276,0:46.941.554,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,8277,0:46.956.560,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 97 9D 5C F6 3D 52 7C 15 94 D2 B7 D2 0F 3F 3A… 0,,8281,0:46.957.556,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,8282,0:46.971.562,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8286,0:46.972.559,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,8287,0:46.972.562,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 97 9D 5C F6 3D 52 7C 15 94 D2 B7 D2 0F 3F 3A… 0,,8291,0:46.973.559,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,8292,0:46.988.564,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 2A 2A FC 14 DB A2 71 C6 94 EC 58 B9 B9 45 89… 0,,8296,0:46.989.561,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,8297,0:47.003.566,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8301,0:47.004.563,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,8302,0:47.004.566,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 2A 2A FC 14 DB A2 71 C6 94 EC 58 B9 B9 45 89… 0,,8306,0:47.005.563,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,8307,0:47.020.568,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 DD E0 93 40 94 A5 5E 7C 20 03 21 4F D8 E2 B7… 0,,8311,0:47.021.565,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,8312,0:47.035.571,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8316,0:47.036.567,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,8317,0:47.036.571,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 DD E0 93 40 94 A5 5E 7C 20 03 21 4F D8 E2 B7… 0,,8321,0:47.037.568,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,8322,0:47.052.573,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 49 9A CC 62 68 79 81 19 93 D0 E2 A7 7A 24 59… 0,,8326,0:47.053.570,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,8327,0:47.067.575,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8331,0:47.068.572,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,8332,0:47.068.575,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 49 9A CC 62 68 79 81 19 93 D0 E2 A7 7A 24 59… 0,,8336,0:47.069.572,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,8337,0:47.084.577,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 8D 5A FA 40 A2 B7 6B 07 62 5A FD 20 F9 3F 5B… 0,,8341,0:47.085.574,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,8342,0:47.099.579,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8346,0:47.100.576,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,8347,0:47.100.580,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 8D 5A FA 40 A2 B7 6B 07 62 5A FD 20 F9 3F 5B… 0,,8351,0:47.101.576,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,8352,0:47.116.582,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 51 8E 04 26 EC 1B 99 35 68 3D 00 A0 08 5A 06… 0,,8356,0:47.117.579,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,8357,0:47.131.584,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8361,0:47.132.581,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,8362,0:47.132.584,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 51 8E 04 26 EC 1B 99 35 68 3D 00 A0 08 5A 06… 0,,8366,0:47.133.581,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,8367,0:47.148.586,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF 5A D0 95 29 B0 25 11 FA 18 8A B8 60 83 BE CE… 0,,8371,0:47.149.583,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,8372,0:47.163.588,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8376,0:47.164.585,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,8377,0:47.164.588,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF 5A D0 95 29 B0 25 11 FA 18 8A B8 60 83 BE CE… 0,,8381,0:47.165.585,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,8382,0:47.180.591,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 7F 12 E3 AC DA C6 B6 DC E0 BC 1C 0C 0A 24 C6… 0,,8386,0:47.181.588,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,8387,0:47.195.593,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8391,0:47.196.590,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,8392,0:47.196.593,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 7F 12 E3 AC DA C6 B6 DC E0 BC 1C 0C 0A 24 C6… 0,,8396,0:47.197.590,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,8397,0:47.212.595,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 88 3D 74 FD 72 B4 18 FC 9A 9A DC 43 93 14 3D… 0,,8401,0:47.213.592,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,8402,0:47.227.597,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8406,0:47.228.594,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,8407,0:47.228.597,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 88 3D 74 FD 72 B4 18 FC 9A 9A DC 43 93 14 3D… 0,,8411,0:47.229.594,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,8412,0:47.244.600,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 09 C3 AD 4C FF E5 3F 58 F1 18 C3 61 B7 B2 89… 0,,8416,0:47.245.596,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,8417,0:47.259.602,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8421,0:47.260.598,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,8422,0:47.260.602,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 09 C3 AD 4C FF E5 3F 58 F1 18 C3 61 B7 B2 89… 0,,8426,0:47.261.599,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,8427,0:47.276.604,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 35 83 90 7D B3 CB 1E 04 AC CE 0B 1B F5 4D EC… 0,,8431,0:47.277.601,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,8432,0:47.291.606,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8436,0:47.292.603,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,8437,0:47.292.606,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 35 83 90 7D B3 CB 1E 04 AC CE 0B 1B F5 4D EC… 0,,8441,0:47.293.603,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,8442,0:47.308.608,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 8F 29 77 FC 78 16 E3 DA 36 B0 8E CE 35 96 BE… 0,,8446,0:47.309.605,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,8447,0:47.323.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8451,0:47.324.607,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,8452,0:47.324.611,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 8F 29 77 FC 78 16 E3 DA 36 B0 8E CE 35 96 BE… 0,,8456,0:47.325.608,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,8457,0:47.340.613,50.729 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 9D F2 B1 F2 BE EE 6F 6A 08 6E 8F FF A0 BD 97… 0,,8461,0:47.341.610,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,8462,0:47.355.615,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8466,0:47.356.612,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,8467,0:47.372.617,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 31 B4 72 9C 8A 35 A9 B1 75 CB D6 41 39 A9 FF… 0,,8471,0:47.373.614,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,8472,0:47.387.619,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8476,0:47.388.616,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,8477,0:47.388.620,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 31 B4 72 9C 8A 35 A9 B1 75 CB D6 41 39 A9 FF… 0,,8481,0:47.389.616,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,8482,0:47.404.622,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 52 33 9F F5 CE 4F 38 49 D6 1D 37 9D C9 C7 78… 0,,8486,0:47.405.619,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,8487,0:47.419.624,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8491,0:47.420.621,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,8492,0:47.420.624,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 52 33 9F F5 CE 4F 38 49 D6 1D 37 9D C9 C7 78… 0,,8496,0:47.421.621,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,8497,0:47.436.626,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FF 17 80 BE CB EE 5A D2 F9 5D 2C 69 9B 62 8D… 0,,8501,0:47.437.623,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,8502,0:47.451.628,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8506,0:47.452.625,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,8507,0:47.452.628,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FF 17 80 BE CB EE 5A D2 F9 5D 2C 69 9B 62 8D… 0,,8511,0:47.453.625,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,8512,0:47.468.631,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 21 7D 17 0B DB 64 6D 9F 8F 15 64 B0 DB DF F8… 0,,8516,0:47.469.628,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,8517,0:47.483.633,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8521,0:47.484.630,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,8522,0:47.484.633,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 21 7D 17 0B DB 64 6D 9F 8F 15 64 B0 DB DF F8… 0,,8526,0:47.485.630,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,8527,0:47.500.635,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 01 CA AD B2 2B 69 F7 DB 6F 8C 5D 37 1E 91 03… 0,,8531,0:47.501.632,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,8532,0:47.515.637,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8536,0:47.516.634,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,8537,0:47.516.637,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 01 CA AD B2 2B 69 F7 DB 6F 8C 5D 37 1E 91 03… 0,,8541,0:47.517.634,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,8542,0:47.532.640,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 61 EE 17 92 3B 75 3F A9 2B 24 D3 57 EC 2A 81… 0,,8546,0:47.533.636,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,8547,0:47.547.642,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8551,0:47.548.638,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,8552,0:47.548.642,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 61 EE 17 92 3B 75 3F A9 2B 24 D3 57 EC 2A 81… 0,,8556,0:47.549.639,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,8557,0:47.564.644,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 3F 23 A5 E4 F9 63 23 8B D7 04 74 DB 89 57 D2… 0,,8561,0:47.565.641,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,8562,0:47.579.646,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8566,0:47.580.643,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,8567,0:47.580.646,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 3F 23 A5 E4 F9 63 23 8B D7 04 74 DB 89 57 D2… 0,,8571,0:47.581.643,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,8572,0:47.596.648,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 AA 08 EB 97 3F 8C 0A 19 F7 C2 42 41 1D 8A CC… 0,,8576,0:47.597.645,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,8577,0:47.611.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8581,0:47.612.647,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,8582,0:47.612.651,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 AA 08 EB 97 3F 8C 0A 19 F7 C2 42 41 1D 8A CC… 0,,8586,0:47.613.648,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,8587,0:47.628.653,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 09 8F AA 95 0A D1 53 7F 1E 60 15 CB 08 77 1E… 0,,8591,0:47.629.650,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,8592,0:47.643.655,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8596,0:47.644.652,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,8597,0:47.644.655,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 09 8F AA 95 0A D1 53 7F 1E 60 15 CB 08 77 1E… 0,,8601,0:47.645.652,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,8602,0:47.660.657,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 87 99 60 31 AA C5 A2 09 9C 61 36 04 62 71 0C… 0,,8606,0:47.661.654,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,8607,0:47.675.659,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8611,0:47.676.656,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,8612,0:47.676.660,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 87 99 60 31 AA C5 A2 09 9C 61 36 04 62 71 0C… 0,,8616,0:47.677.656,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,8617,0:47.692.662,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 DF 22 64 35 AB 09 45 8B 0D 84 1F E8 FF E6 15… 0,,8621,0:47.693.659,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,8622,0:47.707.664,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8626,0:47.708.661,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,8627,0:47.708.664,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 DF 22 64 35 AB 09 45 8B 0D 84 1F E8 FF E6 15… 0,,8631,0:47.709.661,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,8632,0:47.724.666,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 2C B3 55 23 D8 48 C3 05 E5 8C 59 69 E3 0F 47… 0,,8636,0:47.725.663,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,8637,0:47.739.668,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8641,0:47.740.665,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,8642,0:47.740.668,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 2C B3 55 23 D8 48 C3 05 E5 8C 59 69 E3 0F 47… 0,,8646,0:47.741.665,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,8647,0:47.756.671,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E F9 DA D2 70 DB 75 FD 63 B6 41 33 B1 68 CD AD… 0,,8651,0:47.757.667,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,8652,0:47.771.673,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8656,0:47.772.670,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,8657,0:47.772.673,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E F9 DA D2 70 DB 75 FD 63 B6 41 33 B1 68 CD AD… 0,,8661,0:47.773.670,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,8662,0:47.788.675,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 E8 2D 36 CA 38 AF 7A 8F 2C 03 6D C0 0C 10 7A… 0,,8666,0:47.789.672,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,8667,0:47.803.677,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8671,0:47.804.674,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,8672,0:47.804.677,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 E8 2D 36 CA 38 AF 7A 8F 2C 03 6D C0 0C 10 7A… 0,,8676,0:47.805.674,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,8677,0:47.820.679,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 58 93 0C D0 37 BA 15 57 13 DD BA 43 1F 05 35… 0,,8681,0:47.821.676,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,8682,0:47.835.682,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8686,0:47.836.678,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,8687,0:47.836.682,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 58 93 0C D0 37 BA 15 57 13 DD BA 43 1F 05 35… 0,,8691,0:47.837.679,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,8692,0:47.852.684,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 55 48 12 E3 14 49 CD 83 C0 74 30 39 8E 23 87… 0,,8696,0:47.853.681,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,8697,0:47.867.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8701,0:47.868.683,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,8702,0:47.868.686,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 55 48 12 E3 14 49 CD 83 C0 74 30 39 8E 23 87… 0,,8706,0:47.869.683,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,8707,0:47.884.688,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 7D BF 04 A6 FC C2 A8 1B 50 D7 CA D9 31 EB 17… 0,,8711,0:47.885.685,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,8712,0:47.899.690,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8716,0:47.900.687,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,8717,0:47.900.691,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 7D BF 04 A6 FC C2 A8 1B 50 D7 CA D9 31 EB 17… 0,,8721,0:47.901.687,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,8722,0:47.916.693,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E3 DC DA D7 4F 8F 29 AE 25 53 6F 36 48 0E 11… 0,,8726,0:47.917.690,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,8727,0:47.931.695,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8731,0:47.932.692,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,8732,0:47.932.695,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E3 DC DA D7 4F 8F 29 AE 25 53 6F 36 48 0E 11… 0,,8736,0:47.933.692,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,8737,0:47.948.697,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 58 1F 4F 72 71 2C 9E D3 A1 81 96 C3 94 5C 63… 0,,8741,0:47.949.694,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,8742,0:47.963.699,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8746,0:47.964.696,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,8747,0:47.964.699,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 58 1F 4F 72 71 2C 9E D3 A1 81 96 C3 94 5C 63… 0,,8751,0:47.965.696,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,8752,0:47.980.702,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 1E 5F F8 0C BF 01 71 9E 15 3F DC D7 BF D4 A5… 0,,8756,0:47.981.699,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,8757,0:47.995.704,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8761,0:47.996.701,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,8762,0:48.012.706,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A 7B 83 81 70 B4 73 4D C9 30 6C 06 50 B2 89 68… 0,,8766,0:48.013.703,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,8767,0:48.027.708,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8771,0:48.028.705,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,8772,0:48.028.708,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A 7B 83 81 70 B4 73 4D C9 30 6C 06 50 B2 89 68… 0,,8776,0:48.029.705,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,8777,0:48.044.711,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B B5 75 EE 4B 2B 7B 00 77 46 34 E0 8F 1A CB 9D… 0,,8781,0:48.045.707,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,8782,0:48.059.713,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8786,0:48.060.710,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,8787,0:48.060.713,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B B5 75 EE 4B 2B 7B 00 77 46 34 E0 8F 1A CB 9D… 0,,8791,0:48.061.710,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,8792,0:48.076.715,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 F2 C1 D6 65 4C 6C EA 60 37 48 5C 04 E8 56 7C… 0,,8796,0:48.077.712,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,8797,0:48.091.717,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8801,0:48.092.714,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,8802,0:48.092.717,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 F2 C1 D6 65 4C 6C EA 60 37 48 5C 04 E8 56 7C… 0,,8806,0:48.093.714,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,8807,0:48.108.719,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD CB 7E 16 B5 5B 61 D3 97 A7 5E 34 5B F7 ED 5B… 0,,8811,0:48.109.716,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,8812,0:48.123.722,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8816,0:48.124.718,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,8817,0:48.124.722,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD CB 7E 16 B5 5B 61 D3 97 A7 5E 34 5B F7 ED 5B… 0,,8821,0:48.125.719,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,8822,0:48.140.724,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 05 BF 21 49 13 48 CE 3A BF 85 5A BC 4F AA 73… 0,,8826,0:48.141.721,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,8827,0:48.155.726,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8831,0:48.156.723,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,8832,0:48.156.726,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 05 BF 21 49 13 48 CE 3A BF 85 5A BC 4F AA 73… 0,,8836,0:48.157.723,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,8837,0:48.172.728,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 E6 30 C4 8B BD C1 38 0D E2 AA 8B D8 35 BF A8… 0,,8841,0:48.173.725,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,8842,0:48.187.730,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8846,0:48.188.727,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,8847,0:48.188.731,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 E6 30 C4 8B BD C1 38 0D E2 AA 8B D8 35 BF A8… 0,,8851,0:48.189.727,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,8852,0:48.204.733,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 C5 BB 19 2C 22 7F 75 35 AD 95 A0 F6 B3 6D FB… 0,,8856,0:48.205.730,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,8857,0:48.219.735,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8861,0:48.220.732,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,8862,0:48.220.735,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 C5 BB 19 2C 22 7F 75 35 AD 95 A0 F6 B3 6D FB… 0,,8866,0:48.221.732,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,8867,0:48.236.737,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 4A 8B 35 AA 97 10 02 44 1D DD A1 82 13 2A 1B… 0,,8871,0:48.237.734,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,8872,0:48.251.739,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8876,0:48.252.736,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,8877,0:48.252.739,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 4A 8B 35 AA 97 10 02 44 1D DD A1 82 13 2A 1B… 0,,8881,0:48.253.736,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,8882,0:48.268.742,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 8D 3D 65 86 7E F3 B5 80 C9 CE 5B 30 E5 9B F9… 0,,8886,0:48.269.739,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,8887,0:48.283.744,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8891,0:48.284.741,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,8892,0:48.284.744,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 8D 3D 65 86 7E F3 B5 80 C9 CE 5B 30 E5 9B F9… 0,,8896,0:48.285.741,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,8897,0:48.300.746,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 AD DC 3C F2 60 8F FE DB 83 19 70 2A 9B A5 53… 0,,8901,0:48.301.743,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,8902,0:48.315.748,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8906,0:48.316.745,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,8907,0:48.316.748,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 AD DC 3C F2 60 8F FE DB 83 19 70 2A 9B A5 53… 0,,8911,0:48.317.745,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,8912,0:48.332.751,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 4E 92 82 49 A6 8F 1A 69 CE 82 07 5A 99 B1 9D… 0,,8916,0:48.333.747,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,8917,0:48.347.753,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8921,0:48.348.750,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,8922,0:48.348.753,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 4E 92 82 49 A6 8F 1A 69 CE 82 07 5A 99 B1 9D… 0,,8926,0:48.349.750,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,8927,0:48.364.755,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 C1 A1 61 56 11 59 7B 49 E4 20 63 E1 F9 1E B7… 0,,8931,0:48.365.752,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,8932,0:48.379.757,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8936,0:48.380.754,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,8937,0:48.380.757,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 C1 A1 61 56 11 59 7B 49 E4 20 63 E1 F9 1E B7… 0,,8941,0:48.381.754,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,8942,0:48.396.759,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 5E 4A 29 B7 68 21 49 02 62 B6 06 F0 71 49 7F… 0,,8946,0:48.397.756,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,8947,0:48.411.762,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8951,0:48.412.758,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,8952,0:48.412.762,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 5E 4A 29 B7 68 21 49 02 62 B6 06 F0 71 49 7F… 0,,8956,0:48.413.759,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,8957,0:48.428.764,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 20 CC F9 26 06 2B 8F 67 80 1B 3B A9 10 2F 79… 0,,8961,0:48.429.761,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,8962,0:48.443.766,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8966,0:48.444.763,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,8967,0:48.444.766,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 20 CC F9 26 06 2B 8F 67 80 1B 3B A9 10 2F 79… 0,,8971,0:48.445.763,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,8972,0:48.460.768,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 92 7F 03 52 A8 DA A1 5A AA 2D 30 E3 6E D7 18… 0,,8976,0:48.461.765,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,8977,0:48.475.770,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8981,0:48.476.767,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,8982,0:48.476.771,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 92 7F 03 52 A8 DA A1 5A AA 2D 30 E3 6E D7 18… 0,,8986,0:48.477.767,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,8987,0:48.492.773,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 DD 6D 63 98 60 58 54 B8 73 1D 3A A8 B6 FA A3… 0,,8991,0:48.493.770,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,8992,0:48.507.775,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,8996,0:48.508.772,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,8997,0:48.508.775,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 DD 6D 63 98 60 58 54 B8 73 1D 3A A8 B6 FA A3… 0,,9001,0:48.509.772,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,9002,0:48.524.777,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 D8 34 FF F3 85 D9 54 91 0F CE 86 F2 44 F0 63… 0,,9006,0:48.525.774,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,9007,0:48.539.779,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9011,0:48.540.776,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,9012,0:48.540.779,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 D8 34 FF F3 85 D9 54 91 0F CE 86 F2 44 F0 63… 0,,9016,0:48.541.776,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,9017,0:48.556.782,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 CB 27 8E 68 62 7B 06 41 BD CB 8C B8 31 73 6D… 0,,9021,0:48.557.779,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,9022,0:48.571.784,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9026,0:48.572.781,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,9027,0:48.572.784,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 CB 27 8E 68 62 7B 06 41 BD CB 8C B8 31 73 6D… 0,,9031,0:48.573.781,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,9032,0:48.588.786,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE C4 1F 84 D5 15 1F 44 76 9D 56 5E 5D DA 77 A4… 0,,9036,0:48.589.783,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,9037,0:48.603.788,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9041,0:48.604.785,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,9042,0:48.620.791,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D B1 E8 4F A2 1A 69 89 B8 3D 1F 90 E9 59 80 3E… 0,,9046,0:48.621.787,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,9047,0:48.635.793,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9051,0:48.636.790,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,9052,0:48.636.793,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D B1 E8 4F A2 1A 69 89 B8 3D 1F 90 E9 59 80 3E… 0,,9056,0:48.637.790,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,9057,0:48.652.795,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 32 DE 5F E4 40 1B CA 6B 6A 93 02 D6 7B 9F B9… 0,,9061,0:48.653.792,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,9062,0:48.667.797,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9066,0:48.668.794,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,9067,0:48.668.797,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 32 DE 5F E4 40 1B CA 6B 6A 93 02 D6 7B 9F B9… 0,,9071,0:48.669.794,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,9072,0:48.684.799,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F FD 6F 0F 24 49 63 DB 08 C5 AC 77 EB 5E 19 C9… 0,,9076,0:48.685.796,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,9077,0:48.699.802,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9081,0:48.700.798,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,9082,0:48.700.802,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F FD 6F 0F 24 49 63 DB 08 C5 AC 77 EB 5E 19 C9… 0,,9086,0:48.701.799,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,9087,0:48.716.804,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 17 5F D7 CF A7 46 D1 79 48 37 67 E4 42 A0 FF… 0,,9091,0:48.717.801,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,9092,0:48.731.806,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9096,0:48.732.803,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,9097,0:48.732.806,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 17 5F D7 CF A7 46 D1 79 48 37 67 E4 42 A0 FF… 0,,9101,0:48.733.803,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,9102,0:48.748.808,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B DD B7 F0 CD A9 E9 C9 B6 10 85 F1 84 42 56 F7… 0,,9106,0:48.749.805,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,9107,0:48.763.810,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9111,0:48.764.807,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,9112,0:48.764.811,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B DD B7 F0 CD A9 E9 C9 B6 10 85 F1 84 42 56 F7… 0,,9116,0:48.765.807,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,9117,0:48.780.813,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 1F 36 E2 77 C8 8B EF B6 BA 9E CC A8 FB D5 37… 0,,9121,0:48.781.810,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,9122,0:48.795.815,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9126,0:48.796.812,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,9127,0:48.796.815,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 1F 36 E2 77 C8 8B EF B6 BA 9E CC A8 FB D5 37… 0,,9131,0:48.797.812,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,9132,0:48.812.817,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 DB 99 9E 17 4E 46 06 3E E5 E6 76 CD DB F3 DB… 0,,9136,0:48.813.814,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,9137,0:48.827.819,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9141,0:48.828.816,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,9142,0:48.828.819,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 DB 99 9E 17 4E 46 06 3E E5 E6 76 CD DB F3 DB… 0,,9146,0:48.829.816,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,9147,0:48.844.822,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B EB 7B 76 20 69 8A D7 7E 51 F8 CA 84 7B 2E 91… 0,,9151,0:48.845.819,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,9152,0:48.859.824,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9156,0:48.860.821,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,9157,0:48.860.824,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B EB 7B 76 20 69 8A D7 7E 51 F8 CA 84 7B 2E 91… 0,,9161,0:48.861.821,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,9162,0:48.876.826,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D ED 8A B9 02 A8 DF 0C 44 B3 3E BD B2 90 15 04… 0,,9166,0:48.877.823,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,9167,0:48.891.828,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9171,0:48.892.825,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,9172,0:48.892.828,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D ED 8A B9 02 A8 DF 0C 44 B3 3E BD B2 90 15 04… 0,,9176,0:48.893.825,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,9177,0:48.908.831,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA E7 C6 DA 19 22 9A 03 BA 51 C8 05 3E 8B 1E E3… 0,,9181,0:48.909.827,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,9182,0:48.923.833,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9186,0:48.924.830,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,9187,0:48.924.833,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA E7 C6 DA 19 22 9A 03 BA 51 C8 05 3E 8B 1E E3… 0,,9191,0:48.925.830,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,9192,0:48.940.835,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D E4 0D C5 CE 4A C2 99 7F 0C A2 B8 5D 81 27 21… 0,,9196,0:48.941.832,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,9197,0:48.955.837,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9201,0:48.956.834,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,9202,0:48.956.837,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D E4 0D C5 CE 4A C2 99 7F 0C A2 B8 5D 81 27 21… 0,,9206,0:48.957.834,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,9207,0:48.972.839,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 13 6F 02 EB D6 6E B1 46 26 6A 76 E1 A7 46 3B… 0,,9211,0:48.973.836,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,9212,0:48.987.842,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9216,0:48.988.838,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,9217,0:48.988.842,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 13 6F 02 EB D6 6E B1 46 26 6A 76 E1 A7 46 3B… 0,,9221,0:48.989.839,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,9222,0:49.004.844,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 49 5A 42 60 C5 BC 1E A0 7F F5 CC F2 69 FF 85… 0,,9226,0:49.005.841,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,9227,0:49.019.846,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9231,0:49.020.843,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,9232,0:49.020.846,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 49 5A 42 60 C5 BC 1E A0 7F F5 CC F2 69 FF 85… 0,,9236,0:49.021.843,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,9237,0:49.036.848,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 5E 16 AE 21 C9 67 9C 3D 02 DE B4 39 EF 8A 7B… 0,,9241,0:49.037.845,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,9242,0:49.051.850,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9246,0:49.052.847,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,9247,0:49.052.851,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 5E 16 AE 21 C9 67 9C 3D 02 DE B4 39 EF 8A 7B… 0,,9251,0:49.053.847,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,9252,0:49.068.853,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 31 D6 F5 4A 05 44 5D C4 26 8A 50 45 B5 E7 46… 0,,9256,0:49.069.850,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,9257,0:49.083.855,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9261,0:49.084.852,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,9262,0:49.084.855,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 31 D6 F5 4A 05 44 5D C4 26 8A 50 45 B5 E7 46… 0,,9266,0:49.085.852,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,9267,0:49.100.857,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 8F 32 AB 23 2A 8A 3D 33 40 E3 03 2A 18 D0 CA… 0,,9271,0:49.101.854,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,9272,0:49.115.859,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9276,0:49.116.856,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,9277,0:49.116.859,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 8F 32 AB 23 2A 8A 3D 33 40 E3 03 2A 18 D0 CA… 0,,9281,0:49.117.856,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,9282,0:49.132.862,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 0B 52 4A 64 E0 B2 0F 3D CC 5C D0 9A 14 A6 03… 0,,9286,0:49.133.859,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,9287,0:49.147.864,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9291,0:49.148.861,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,9292,0:49.148.864,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 0B 52 4A 64 E0 B2 0F 3D CC 5C D0 9A 14 A6 03… 0,,9296,0:49.149.861,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,9297,0:49.164.866,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F9 DE A1 26 72 4E 14 69 C1 F3 C0 CE D6 6F D1 E9… 0,,9301,0:49.165.863,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,9302,0:49.179.868,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9306,0:49.180.865,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,9307,0:49.180.868,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F9 DE A1 26 72 4E 14 69 C1 F3 C0 CE D6 6F D1 E9… 0,,9311,0:49.181.865,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,9312,0:49.196.871,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 86 DF 44 17 5D 5A 37 1C 94 85 29 9F F8 01 B9… 0,,9316,0:49.197.867,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,9317,0:49.211.873,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9321,0:49.212.869,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,9322,0:49.212.873,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 86 DF 44 17 5D 5A 37 1C 94 85 29 9F F8 01 B9… 0,,9326,0:49.213.870,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,9327,0:49.228.875,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 B5 B9 44 A1 00 F9 B1 61 A7 C3 AD 02 3B 27 6F… 0,,9331,0:49.229.872,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,9332,0:49.243.877,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9336,0:49.244.874,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,9337,0:49.260.879,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F 26 7C 8A F4 97 6F 4E FF 60 10 C7 32 83 59 4A… 0,,9341,0:49.261.876,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,9342,0:49.275.881,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9346,0:49.276.878,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,9347,0:49.276.882,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3F 26 7C 8A F4 97 6F 4E FF 60 10 C7 32 83 59 4A… 0,,9351,0:49.277.879,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,9352,0:49.292.884,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 4B DE E4 9D 23 40 79 E3 A9 26 B8 52 94 35 4A… 0,,9356,0:49.293.881,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,9357,0:49.307.886,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9361,0:49.308.883,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,9362,0:49.308.886,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 4B DE E4 9D 23 40 79 E3 A9 26 B8 52 94 35 4A… 0,,9366,0:49.309.883,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,9367,0:49.324.888,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 C7 F8 32 88 53 F6 7B FE C7 25 6A 03 B1 0A A7… 0,,9371,0:49.325.885,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,9372,0:49.339.890,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9376,0:49.340.887,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,9377,0:49.340.891,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 C7 F8 32 88 53 F6 7B FE C7 25 6A 03 B1 0A A7… 0,,9381,0:49.341.887,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,9382,0:49.356.893,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 9F BE 0D AE 40 64 05 20 CD 2A 20 26 E1 54 F2… 0,,9386,0:49.357.890,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,9387,0:49.371.895,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9391,0:49.372.892,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,9392,0:49.372.895,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 9F BE 0D AE 40 64 05 20 CD 2A 20 26 E1 54 F2… 0,,9396,0:49.373.892,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,9397,0:49.388.897,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 2F 26 47 C7 EE 79 C7 E6 67 26 03 88 FF F4 AC… 0,,9401,0:49.389.894,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,9402,0:49.403.899,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9406,0:49.404.896,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,9407,0:49.404.899,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 2F 26 47 C7 EE 79 C7 E6 67 26 03 88 FF F4 AC… 0,,9411,0:49.405.896,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,9412,0:49.420.902,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B A4 87 D0 BD 0C 92 F9 A8 0D 48 14 64 08 BE 68… 0,,9416,0:49.421.899,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,9417,0:49.435.904,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9421,0:49.436.901,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,9422,0:49.436.904,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B A4 87 D0 BD 0C 92 F9 A8 0D 48 14 64 08 BE 68… 0,,9426,0:49.437.901,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,9427,0:49.452.906,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D F2 D5 85 8B 73 E4 AB 34 C7 00 13 5B 62 7B AB… 0,,9431,0:49.453.903,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,9432,0:49.467.908,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9436,0:49.468.905,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,9437,0:49.468.908,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D F2 D5 85 8B 73 E4 AB 34 C7 00 13 5B 62 7B AB… 0,,9441,0:49.469.905,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,9442,0:49.484.910,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED 20 C7 6B 9B 96 AB C1 2C 1F 0E CF 99 E8 A0 8E… 0,,9446,0:49.485.907,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,9447,0:49.499.913,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9451,0:49.500.909,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,9452,0:49.500.913,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED 20 C7 6B 9B 96 AB C1 2C 1F 0E CF 99 E8 A0 8E… 0,,9456,0:49.501.910,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,9457,0:49.516.915,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 97 76 0F 80 04 25 15 91 80 8F A1 C7 C8 AE 8B… 0,,9461,0:49.517.912,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,9462,0:49.531.917,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9466,0:49.532.914,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,9467,0:49.532.917,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 97 76 0F 80 04 25 15 91 80 8F A1 C7 C8 AE 8B… 0,,9471,0:49.533.914,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,9472,0:49.548.919,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 7F E1 6A 03 E8 49 9E E2 90 FB B3 F2 98 49 37… 0,,9476,0:49.549.916,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,9477,0:49.563.921,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9481,0:49.564.918,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,9482,0:49.564.922,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 7F E1 6A 03 E8 49 9E E2 90 FB B3 F2 98 49 37… 0,,9486,0:49.565.918,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,9487,0:49.580.924,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 FE B0 71 3B 46 86 ED 26 2C D7 07 49 5A 8B 6F… 0,,9491,0:49.581.921,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,9492,0:49.595.926,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9496,0:49.596.923,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,9497,0:49.596.926,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 FE B0 71 3B 46 86 ED 26 2C D7 07 49 5A 8B 6F… 0,,9501,0:49.597.923,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,9502,0:49.612.928,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A B4 A2 BC 34 AA 17 F5 41 F1 77 64 92 75 D2 A7… 0,,9506,0:49.613.925,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,9507,0:49.627.930,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9511,0:49.628.927,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,9512,0:49.628.930,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A B4 A2 BC 34 AA 17 F5 41 F1 77 64 92 75 D2 A7… 0,,9516,0:49.629.927,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,9517,0:49.644.933,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A F2 7F 10 65 7B 31 E1 74 00 A2 AE B5 8B 4F F9… 0,,9521,0:49.645.930,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,9522,0:49.659.935,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9526,0:49.660.932,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,9527,0:49.660.935,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A F2 7F 10 65 7B 31 E1 74 00 A2 AE B5 8B 4F F9… 0,,9531,0:49.661.932,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,9532,0:49.676.937,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B DC 37 4D 74 4C 3E 40 F0 7E 70 5B 63 EB 70 A0… 0,,9536,0:49.677.934,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,9537,0:49.691.939,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9541,0:49.692.936,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,9542,0:49.692.939,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B DC 37 4D 74 4C 3E 40 F0 7E 70 5B 63 EB 70 A0… 0,,9546,0:49.693.936,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,9547,0:49.708.942,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 34 F2 B2 47 0A 9A 67 A5 4B 4C 95 ED 4E 8D 0B… 0,,9551,0:49.709.938,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,9552,0:49.723.944,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9556,0:49.724.941,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,9557,0:49.724.944,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 34 F2 B2 47 0A 9A 67 A5 4B 4C 95 ED 4E 8D 0B… 0,,9561,0:49.725.941,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,9562,0:49.740.946,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 28 E2 E5 68 86 B7 7E 2D 16 3F E6 34 EB 01 2A… 0,,9566,0:49.741.943,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,9567,0:49.755.948,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9571,0:49.756.945,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,9572,0:49.756.948,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 28 E2 E5 68 86 B7 7E 2D 16 3F E6 34 EB 01 2A… 0,,9576,0:49.757.945,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,9577,0:49.772.950,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B DB 19 1E 00 5F 77 47 CC 68 3B 3B 2C 9F C2 70… 0,,9581,0:49.773.947,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,9582,0:49.787.953,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9586,0:49.788.949,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,9587,0:49.788.953,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B DB 19 1E 00 5F 77 47 CC 68 3B 3B 2C 9F C2 70… 0,,9591,0:49.789.950,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,9592,0:49.804.955,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 88 54 DF A5 C7 35 01 8F CE 64 E2 1D 28 36 6E… 0,,9596,0:49.805.952,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,9597,0:49.819.957,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9601,0:49.820.954,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,9602,0:49.820.957,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 88 54 DF A5 C7 35 01 8F CE 64 E2 1D 28 36 6E… 0,,9606,0:49.821.954,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,9607,0:49.836.959,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 77 25 8E 74 95 1A F7 92 21 89 93 56 F6 6A 99… 0,,9611,0:49.837.956,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,9612,0:49.851.961,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9616,0:49.852.958,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,9617,0:49.868.964,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 4C 51 C6 B9 A1 89 91 68 68 E7 0E C5 5F 7C 9D… 0,,9621,0:49.869.961,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,9622,0:49.883.966,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9626,0:49.884.963,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,9627,0:49.884.966,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 4C 51 C6 B9 A1 89 91 68 68 E7 0E C5 5F 7C 9D… 0,,9631,0:49.885.963,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,9632,0:49.900.968,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 17 88 0C DE 3D 36 A9 24 35 0B 23 64 9F EF 54… 0,,9636,0:49.901.965,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,9637,0:49.915.970,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9641,0:49.916.967,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,9642,0:49.916.970,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 17 88 0C DE 3D 36 A9 24 35 0B 23 64 9F EF 54… 0,,9646,0:49.917.967,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,9647,0:49.932.973,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF F3 1D F8 2A 49 CF EE DA ED 83 AD 82 6F 28 45… 0,,9651,0:49.933.970,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,9652,0:49.947.975,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9656,0:49.948.972,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,9657,0:49.948.975,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF F3 1D F8 2A 49 CF EE DA ED 83 AD 82 6F 28 45… 0,,9661,0:49.949.972,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,9662,0:49.964.977,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 89 28 D5 73 5E 8E 40 7E 8B 0D 50 44 C0 F7 0A… 0,,9666,0:49.965.974,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,9667,0:49.979.979,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9671,0:49.980.976,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,9672,0:49.980.979,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B 89 28 D5 73 5E 8E 40 7E 8B 0D 50 44 C0 F7 0A… 0,,9676,0:49.981.976,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,9677,0:49.996.982,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 07 9A 9E 17 74 84 6F E0 CD F8 F0 E5 4B F9 D8… 0,,9681,0:49.997.978,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,9682,0:50.011.984,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9686,0:50.012.981,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,9687,0:50.012.984,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 07 9A 9E 17 74 84 6F E0 CD F8 F0 E5 4B F9 D8… 0,,9691,0:50.013.981,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,9692,0:50.028.986,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 CA F5 B2 1D DF 0B 74 0A A2 0B 43 58 3B 98 4D… 0,,9696,0:50.029.983,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,9697,0:50.043.988,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9701,0:50.044.985,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,9702,0:50.044.988,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 CA F5 B2 1D DF 0B 74 0A A2 0B 43 58 3B 98 4D… 0,,9706,0:50.045.985,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,9707,0:50.060.990,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 9A D6 6A CC C5 E6 3F 26 25 0B 11 5C 04 15 19… 0,,9711,0:50.061.987,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,9712,0:50.075.993,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9716,0:50.076.989,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,9717,0:50.076.993,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 9A D6 6A CC C5 E6 3F 26 25 0B 11 5C 04 15 19… 0,,9721,0:50.077.990,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,9722,0:50.092.995,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C D9 86 A2 C5 DD 30 6B 75 9F 61 22 62 89 2A DE… 0,,9726,0:50.093.992,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,9727,0:50.107.997,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9731,0:50.108.994,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,9732,0:50.108.997,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C D9 86 A2 C5 DD 30 6B 75 9F 61 22 62 89 2A DE… 0,,9736,0:50.109.994,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,9737,0:50.124.999,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 12 0D 98 BA C4 8D F5 49 EF 49 97 BE 3A 97 25… 0,,9741,0:50.125.996,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,9742,0:50.140.001,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9746,0:50.140.998,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,9747,0:50.141.002,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 12 0D 98 BA C4 8D F5 49 EF 49 97 BE 3A 97 25… 0,,9751,0:50.141.998,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,9752,0:50.157.004,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F 71 49 FB 29 02 65 C4 7B E6 F7 2E 40 6C 4D 35… 0,,9756,0:50.158.001,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,9757,0:50.172.006,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9761,0:50.173.003,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,9762,0:50.173.006,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F 71 49 FB 29 02 65 C4 7B E6 F7 2E 40 6C 4D 35… 0,,9766,0:50.174.003,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,9767,0:50.189.008,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 C0 1A 13 50 3E B1 BC 13 55 18 09 54 7A 9B B5… 0,,9771,0:50.190.005,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,9772,0:50.204.010,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9776,0:50.205.007,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,9777,0:50.205.010,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 C0 1A 13 50 3E B1 BC 13 55 18 09 54 7A 9B B5… 0,,9781,0:50.206.007,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,9782,0:50.221.013,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 3B 85 9C 3C 02 CC 6E 9B 85 37 F0 FE CD 65 D1… 0,,9786,0:50.222.010,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,9787,0:50.236.015,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9791,0:50.237.012,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,9792,0:50.237.015,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 3B 85 9C 3C 02 CC 6E 9B 85 37 F0 FE CD 65 D1… 0,,9796,0:50.238.012,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,9797,0:50.253.017,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B 4F AB 80 6E E0 CA EC B2 A7 74 6C 64 64 5F 75… 0,,9801,0:50.254.014,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,9802,0:50.268.019,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9806,0:50.269.016,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,9807,0:50.269.019,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B 4F AB 80 6E E0 CA EC B2 A7 74 6C 64 64 5F 75… 0,,9811,0:50.270.016,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,9812,0:50.285.022,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 87 FD 73 89 5D 62 F2 EE 28 71 C4 07 9A 68 1A B9… 0,,9816,0:50.286.018,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,9817,0:50.300.024,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9821,0:50.301.021,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,9822,0:50.301.024,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 87 FD 73 89 5D 62 F2 EE 28 71 C4 07 9A 68 1A B9… 0,,9826,0:50.302.021,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,9827,0:50.317.026,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 3D 2D 70 AE BA 60 A8 5E ED 42 4E F8 35 5C FB… 0,,9831,0:50.318.023,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,9832,0:50.332.028,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9836,0:50.333.025,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,9837,0:50.333.028,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 3D 2D 70 AE BA 60 A8 5E ED 42 4E F8 35 5C FB… 0,,9841,0:50.334.025,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,9842,0:50.349.030,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 7B C4 EB E8 CA 34 27 40 1C 62 92 06 F6 7C C0… 0,,9846,0:50.350.027,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,9847,0:50.364.033,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9851,0:50.365.029,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,9852,0:50.365.033,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 7B C4 EB E8 CA 34 27 40 1C 62 92 06 F6 7C C0… 0,,9856,0:50.366.030,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,9857,0:50.381.035,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 E1 74 6F 65 8F 45 5A 1B 9A D7 C6 77 45 DC 2C… 0,,9861,0:50.382.032,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,9862,0:50.396.037,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9866,0:50.397.034,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,9867,0:50.397.037,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 E1 74 6F 65 8F 45 5A 1B 9A D7 C6 77 45 DC 2C… 0,,9871,0:50.398.034,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,9872,0:50.413.039,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE 63 DA E1 21 78 0C 18 A8 32 A7 77 7B 1A 38 22… 0,,9876,0:50.414.036,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,9877,0:50.428.041,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9881,0:50.429.038,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,9882,0:50.429.042,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE 63 DA E1 21 78 0C 18 A8 32 A7 77 7B 1A 38 22… 0,,9886,0:50.430.038,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,9887,0:50.445.044,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 21 B1 E2 9D E8 60 54 B6 67 08 5D 4D BD 0F F4… 0,,9891,0:50.446.041,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,9892,0:50.460.046,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9896,0:50.461.043,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,9897,0:50.461.046,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD 21 B1 E2 9D E8 60 54 B6 67 08 5D 4D BD 0F F4… 0,,9901,0:50.462.043,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,9902,0:50.477.048,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 71 CD C1 F6 97 86 C2 A0 DE CB A3 3A 72 3D 25… 0,,9906,0:50.478.045,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,9907,0:50.492.050,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9911,0:50.493.047,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,9912,0:50.509.053,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 09 62 4C 5C 39 4D 2D 7F 67 44 00 2B 99 91 92 5D… 0,,9916,0:50.510.050,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,9917,0:50.524.055,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9921,0:50.525.052,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,9922,0:50.525.055,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 09 62 4C 5C 39 4D 2D 7F 67 44 00 2B 99 91 92 5D… 0,,9926,0:50.526.052,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,9927,0:50.541.057,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 7E F2 A6 BD A4 E2 68 28 EB 30 2B CE 4F D7 0E… 0,,9931,0:50.542.054,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,9932,0:50.556.059,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9936,0:50.557.056,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,9937,0:50.557.059,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 7E F2 A6 BD A4 E2 68 28 EB 30 2B CE 4F D7 0E… 0,,9941,0:50.558.056,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,9942,0:50.573.062,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 B0 26 1C D3 0F A8 4E 44 2C 8A 9F 69 4D 7F B1… 0,,9946,0:50.574.058,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,9947,0:50.588.064,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9951,0:50.589.061,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,9952,0:50.589.064,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 B0 26 1C D3 0F A8 4E 44 2C 8A 9F 69 4D 7F B1… 0,,9956,0:50.590.061,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,9957,0:50.605.066,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 A7 87 18 D8 2F 87 E0 54 97 D1 C8 FF 89 37 34… 0,,9961,0:50.606.063,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,9962,0:50.620.068,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9966,0:50.621.065,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,9967,0:50.621.068,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 A7 87 18 D8 2F 87 E0 54 97 D1 C8 FF 89 37 34… 0,,9971,0:50.622.065,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,9972,0:50.637.070,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 6B 57 40 27 E0 F4 A9 5A 21 93 B3 87 C5 39 66… 0,,9976,0:50.638.067,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,9977,0:50.652.073,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9981,0:50.653.069,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,9982,0:50.653.073,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 6B 57 40 27 E0 F4 A9 5A 21 93 B3 87 C5 39 66… 0,,9986,0:50.654.070,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,9987,0:50.669.075,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 E6 31 50 F3 9D 5D 67 0B 9C B5 D2 9A EE 5E EF… 0,,9991,0:50.670.072,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,9992,0:50.684.077,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,9996,0:50.685.074,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,9997,0:50.685.077,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 E6 31 50 F3 9D 5D 67 0B 9C B5 D2 9A EE 5E EF… 0,,10001,0:50.686.074,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,10002,0:50.701.079,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E A3 4D 38 80 DB E3 F1 04 6F 0E 4F 4B A7 20 11… 0,,10006,0:50.702.076,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,10007,0:50.716.081,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10011,0:50.717.078,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,10012,0:50.717.082,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E A3 4D 38 80 DB E3 F1 04 6F 0E 4F 4B A7 20 11… 0,,10016,0:50.718.078,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,10017,0:50.733.084,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 6B A3 45 A9 D9 CC A3 40 FC 50 4C 42 7B 0F D9… 0,,10021,0:50.734.081,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,10022,0:50.748.086,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10026,0:50.749.083,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,10027,0:50.749.086,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 6B A3 45 A9 D9 CC A3 40 FC 50 4C 42 7B 0F D9… 0,,10031,0:50.750.083,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,10032,0:50.765.088,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 A7 36 B0 2D AA 58 84 C4 98 43 67 6F A1 C3 57… 0,,10036,0:50.766.085,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,10037,0:50.780.090,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10041,0:50.781.087,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,10042,0:50.781.090,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 A7 36 B0 2D AA 58 84 C4 98 43 67 6F A1 C3 57… 0,,10046,0:50.782.087,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,10047,0:50.797.093,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 0B F6 B5 4A 36 C2 DD 75 D7 81 01 39 E7 38 5E… 0,,10051,0:50.798.090,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,10052,0:50.812.095,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10056,0:50.813.092,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,10057,0:50.813.095,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 0B F6 B5 4A 36 C2 DD 75 D7 81 01 39 E7 38 5E… 0,,10061,0:50.814.092,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,10062,0:50.829.097,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 29 D0 EF 80 7E 3F 41 74 82 E8 EC 90 12 86 4B… 0,,10066,0:50.830.094,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,10067,0:50.844.099,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10071,0:50.845.096,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,10072,0:50.845.099,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 29 D0 EF 80 7E 3F 41 74 82 E8 EC 90 12 86 4B… 0,,10076,0:50.846.096,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,10077,0:50.861.102,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 5A FE 00 5C 4E 78 A4 7C 8E 22 98 6D CD ED F6… 0,,10081,0:50.862.098,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,10082,0:50.876.104,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10086,0:50.877.100,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,10087,0:50.877.104,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 5A FE 00 5C 4E 78 A4 7C 8E 22 98 6D CD ED F6… 0,,10091,0:50.878.101,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,10092,0:50.893.106,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CA 0F 19 C3 0B DF 60 C6 15 48 7A 96 35 3C 56 FA… 0,,10096,0:50.894.103,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,10097,0:50.908.108,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10101,0:50.909.105,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,10102,0:50.909.108,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CA 0F 19 C3 0B DF 60 C6 15 48 7A 96 35 3C 56 FA… 0,,10106,0:50.910.105,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,10107,0:50.925.110,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 3F 28 5B 51 A0 F4 A9 1E 3B BA 98 A2 BA 30 50… 0,,10111,0:50.926.107,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,10112,0:50.940.112,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10116,0:50.941.109,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,10117,0:50.941.113,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 3F 28 5B 51 A0 F4 A9 1E 3B BA 98 A2 BA 30 50… 0,,10121,0:50.942.110,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,10122,0:50.957.115,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 F9 DE 35 47 23 8E C7 16 69 02 1F B4 40 F0 28… 0,,10126,0:50.958.112,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,10127,0:50.972.117,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10131,0:50.973.114,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,10132,0:50.973.117,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 F9 DE 35 47 23 8E C7 16 69 02 1F B4 40 F0 28… 0,,10136,0:50.974.114,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,10137,0:50.989.119,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 2D 06 A9 F1 EA 3B 8C EA 05 19 89 75 33 8B 5F… 0,,10141,0:50.990.116,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,10142,0:51.004.121,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10146,0:51.005.118,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,10147,0:51.005.122,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 2D 06 A9 F1 EA 3B 8C EA 05 19 89 75 33 8B 5F… 0,,10151,0:51.006.118,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,10152,0:51.021.124,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 1A 66 27 88 9A 4E B2 49 A9 F9 E1 F3 CA 2D 0F… 0,,10156,0:51.022.121,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,10157,0:51.036.126,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10161,0:51.037.123,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,10162,0:51.037.126,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 1A 66 27 88 9A 4E B2 49 A9 F9 E1 F3 CA 2D 0F… 0,,10166,0:51.038.123,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,10167,0:51.053.128,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 B6 73 CF 23 69 40 26 21 DE C4 B0 78 A3 43 B2… 0,,10171,0:51.054.125,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,10172,0:51.068.130,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10176,0:51.069.127,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,10177,0:51.069.130,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 B6 73 CF 23 69 40 26 21 DE C4 B0 78 A3 43 B2… 0,,10181,0:51.070.127,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,10182,0:51.085.133,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 0C DA BC 78 60 33 30 60 EF F4 20 B8 71 E1 1C… 0,,10186,0:51.086.130,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,10187,0:51.100.135,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10191,0:51.101.132,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,10192,0:51.117.137,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 0F A2 15 57 A7 E7 B7 55 27 7B B9 62 6E E5 45… 0,,10196,0:51.118.134,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,10197,0:51.132.139,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10201,0:51.133.136,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,10202,0:51.133.139,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 0F A2 15 57 A7 E7 B7 55 27 7B B9 62 6E E5 45… 0,,10206,0:51.134.136,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,10207,0:51.149.142,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 C2 6F 6E 27 07 2C 43 A4 0A 60 C8 52 6A 68 B3… 0,,10211,0:51.150.138,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,10212,0:51.164.144,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10216,0:51.165.140,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,10217,0:51.165.144,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 C2 6F 6E 27 07 2C 43 A4 0A 60 C8 52 6A 68 B3… 0,,10221,0:51.166.141,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,10222,0:51.181.146,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E 8B FB 80 53 D9 E2 29 38 FA 13 5A 9C 4B 71 E9… 0,,10226,0:51.182.143,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,10227,0:51.196.148,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10231,0:51.197.145,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,10232,0:51.197.148,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E 8B FB 80 53 D9 E2 29 38 FA 13 5A 9C 4B 71 E9… 0,,10236,0:51.198.145,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,10237,0:51.213.150,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 76 E5 4D B5 7F D0 67 86 1D 5E B1 E6 3D A2 2E… 0,,10241,0:51.214.147,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,10242,0:51.228.153,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10246,0:51.229.149,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,10247,0:51.229.153,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 76 E5 4D B5 7F D0 67 86 1D 5E B1 E6 3D A2 2E… 0,,10251,0:51.230.149,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,10252,0:51.245.155,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C B4 E2 F4 AF 86 66 88 23 9E BA E9 0D F1 69 8B… 0,,10256,0:51.246.152,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,10257,0:51.260.157,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10261,0:51.261.154,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,10262,0:51.261.157,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C B4 E2 F4 AF 86 66 88 23 9E BA E9 0D F1 69 8B… 0,,10266,0:51.262.154,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,10267,0:51.277.159,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 0A 64 76 AB 99 AA 20 34 7B 7D A8 C9 C9 8B BC… 0,,10271,0:51.278.156,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,10272,0:51.292.161,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10276,0:51.293.158,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,10277,0:51.293.161,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 0A 64 76 AB 99 AA 20 34 7B 7D A8 C9 C9 8B BC… 0,,10281,0:51.294.158,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,10282,0:51.309.164,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 08 82 C6 B9 BF 6A C4 59 99 D4 DD 9D 39 90 2F… 0,,10286,0:51.310.161,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,10287,0:51.324.166,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10291,0:51.325.163,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,10292,0:51.325.166,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 08 82 C6 B9 BF 6A C4 59 99 D4 DD 9D 39 90 2F… 0,,10296,0:51.326.163,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,10297,0:51.341.168,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 3D 97 BA 83 4D 56 DC BB 89 4E 55 98 27 6E 41… 0,,10301,0:51.342.165,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,10302,0:51.356.170,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10306,0:51.357.167,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,10307,0:51.357.170,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 3D 97 BA 83 4D 56 DC BB 89 4E 55 98 27 6E 41… 0,,10311,0:51.358.167,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,10312,0:51.373.173,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B FC C3 92 42 49 25 51 E2 06 9A 3B AD A8 F5 16… 0,,10316,0:51.374.169,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,10317,0:51.388.175,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10321,0:51.389.172,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,10322,0:51.389.175,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B FC C3 92 42 49 25 51 E2 06 9A 3B AD A8 F5 16… 0,,10326,0:51.390.172,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,10327,0:51.405.177,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 1E F7 41 7A 92 97 58 DE F9 9F 7E D2 D9 25 E8… 0,,10331,0:51.406.174,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,10332,0:51.420.179,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10336,0:51.421.176,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,10337,0:51.421.179,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 1E F7 41 7A 92 97 58 DE F9 9F 7E D2 D9 25 E8… 0,,10341,0:51.422.176,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,10342,0:51.437.181,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D BD 8C BB D9 20 75 D0 A8 9B 1A 4E C3 3A B5 3E… 0,,10346,0:51.438.178,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,10347,0:51.452.184,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10351,0:51.453.180,2.833 us,,,,,[1 SOF],[Frame: 193] 0,,10352,0:51.453.184,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D BD 8C BB D9 20 75 D0 A8 9B 1A 4E C3 3A B5 3E… 0,,10356,0:51.454.181,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,10357,0:51.469.186,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 38 63 A7 F7 4C 65 D2 2C 68 A2 BF 73 01 8E B8… 0,,10361,0:51.470.183,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,10362,0:51.484.188,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10366,0:51.485.185,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,10367,0:51.485.188,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 38 63 A7 F7 4C 65 D2 2C 68 A2 BF 73 01 8E B8… 0,,10371,0:51.486.185,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,10372,0:51.501.190,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B DC A4 16 EF 7D BC F5 B3 C3 A6 73 30 BA 00 DE… 0,,10376,0:51.502.187,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,10377,0:51.516.192,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10381,0:51.517.189,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,10382,0:51.517.193,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B DC A4 16 EF 7D BC F5 B3 C3 A6 73 30 BA 00 DE… 0,,10386,0:51.518.189,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,10387,0:51.533.195,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 35 94 D2 EB 22 8F EE A8 4C C4 A5 B0 2F 51 7C… 0,,10391,0:51.534.192,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,10392,0:51.548.197,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10396,0:51.549.194,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,10397,0:51.549.197,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 35 94 D2 EB 22 8F EE A8 4C C4 A5 B0 2F 51 7C… 0,,10401,0:51.550.194,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,10402,0:51.565.199,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 8A 40 E4 EC 87 46 3D 8B 64 2E B5 47 89 21 24… 0,,10406,0:51.566.196,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,10407,0:51.580.201,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10411,0:51.581.198,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,10412,0:51.581.201,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 8A 40 E4 EC 87 46 3D 8B 64 2E B5 47 89 21 24… 0,,10416,0:51.582.198,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,10417,0:51.597.204,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 60 EF 07 D1 5A 48 6A 2A 23 5E 86 13 92 C5 52… 0,,10421,0:51.598.201,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,10422,0:51.612.206,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10426,0:51.613.203,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,10427,0:51.613.206,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 60 EF 07 D1 5A 48 6A 2A 23 5E 86 13 92 C5 52… 0,,10431,0:51.614.203,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,10432,0:51.629.208,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 70 4E C3 93 AB 2F 92 BC 3A 4F 48 12 9F B4 89… 0,,10436,0:51.630.205,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,10437,0:51.644.210,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10441,0:51.645.207,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,10442,0:51.645.210,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 70 4E C3 93 AB 2F 92 BC 3A 4F 48 12 9F B4 89… 0,,10446,0:51.646.207,15.004.916 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,10447,0:51.661.213,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 30 BD DC 25 FE 45 37 C7 39 A5 0D 17 7E E6 4F… 0,,10451,0:51.662.209,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,10452,0:51.676.215,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10456,0:51.677.212,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,10457,0:51.677.215,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 30 BD DC 25 FE 45 37 C7 39 A5 0D 17 7E E6 4F… 0,,10461,0:51.678.212,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,10462,0:51.693.217,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 29 9C 3F FB 18 1B 7F 93 3F F1 2F 15 52 03 F9… 0,,10466,0:51.694.214,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,10467,0:51.708.219,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10471,0:51.709.216,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,10472,0:51.709.219,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 29 9C 3F FB 18 1B 7F 93 3F F1 2F 15 52 03 F9… 0,,10476,0:51.710.216,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,10477,0:51.725.221,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 58 1C 43 A4 A7 F5 C4 98 53 CC 5C 3A D8 36 03… 0,,10481,0:51.726.218,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,10482,0:51.740.224,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10486,0:51.741.220,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,10487,0:51.757.226,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 71 17 4E 0D 01 D3 9C 34 49 2E 10 D1 9A 12 B4… 0,,10491,0:51.758.223,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,10492,0:51.772.228,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10496,0:51.773.225,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,10497,0:51.773.228,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 71 17 4E 0D 01 D3 9C 34 49 2E 10 D1 9A 12 B4… 0,,10501,0:51.774.225,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,10502,0:51.789.230,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 5B 72 BC B4 0E 2D 92 84 D7 D4 0D 33 80 20 42… 0,,10506,0:51.790.227,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,10507,0:51.804.232,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10511,0:51.805.229,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,10512,0:51.805.233,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 5B 72 BC B4 0E 2D 92 84 D7 D4 0D 33 80 20 42… 0,,10516,0:51.806.229,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,10517,0:51.821.235,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 B6 EA C9 BC B5 A5 19 A9 F1 D6 F2 64 26 08 E2… 0,,10521,0:51.822.232,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,10522,0:51.836.237,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10526,0:51.837.234,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,10527,0:51.837.237,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 B6 EA C9 BC B5 A5 19 A9 F1 D6 F2 64 26 08 E2… 0,,10531,0:51.838.234,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,10532,0:51.853.239,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB ED 73 FA BA FD 77 D8 3B 9D B0 90 EB BE 21 CC… 0,,10536,0:51.854.236,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,10537,0:51.868.241,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10541,0:51.869.238,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,10542,0:51.869.241,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB ED 73 FA BA FD 77 D8 3B 9D B0 90 EB BE 21 CC… 0,,10546,0:51.870.238,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,10547,0:51.885.244,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 39 E5 B0 5E 0B 75 1F 32 3C B7 3F 96 4C 11 9F… 0,,10551,0:51.886.241,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,10552,0:51.900.246,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10556,0:51.901.243,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,10557,0:51.901.246,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 39 E5 B0 5E 0B 75 1F 32 3C B7 3F 96 4C 11 9F… 0,,10561,0:51.902.243,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,10562,0:51.917.248,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 4B F4 29 69 EE C8 29 E1 B4 0B B3 0F 17 3A 24… 0,,10566,0:51.918.245,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,10567,0:51.932.250,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10571,0:51.933.247,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,10572,0:51.933.250,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 4B F4 29 69 EE C8 29 E1 B4 0B B3 0F 17 3A 24… 0,,10576,0:51.934.247,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,10577,0:51.949.253,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 A2 31 B6 00 88 D4 71 6A 9C A9 A5 59 DB C8 D7… 0,,10581,0:51.950.249,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,10582,0:51.964.255,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10586,0:51.965.252,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,10587,0:51.965.255,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 A2 31 B6 00 88 D4 71 6A 9C A9 A5 59 DB C8 D7… 0,,10591,0:51.966.252,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,10592,0:51.981.257,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 1E C5 D5 2A D7 B1 E8 D9 17 93 96 C8 C3 3F 1B… 0,,10596,0:51.982.254,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,10597,0:51.996.259,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10601,0:51.997.256,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,10602,0:51.997.259,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 1E C5 D5 2A D7 B1 E8 D9 17 93 96 C8 C3 3F 1B… 0,,10606,0:51.998.256,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,10607,0:52.013.261,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 E3 CD 8C D0 A2 A4 94 C1 0B F4 F0 22 01 48 84… 0,,10611,0:52.014.258,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,10612,0:52.028.264,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10616,0:52.029.260,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,10617,0:52.029.264,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 E3 CD 8C D0 A2 A4 94 C1 0B F4 F0 22 01 48 84… 0,,10621,0:52.030.261,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,10622,0:52.045.266,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 09 32 F4 63 DB 39 29 2C EA 84 90 CC 87 F8 27… 0,,10626,0:52.046.263,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,10627,0:52.060.268,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10631,0:52.061.265,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,10632,0:52.061.268,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 09 32 F4 63 DB 39 29 2C EA 84 90 CC 87 F8 27… 0,,10636,0:52.062.265,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,10637,0:52.077.270,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E AB 9D 40 E0 E8 0B 31 26 F2 DF 7E 6D 18 03 40… 0,,10641,0:52.078.267,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,10642,0:52.092.272,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10646,0:52.093.269,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,10647,0:52.093.273,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E AB 9D 40 E0 E8 0B 31 26 F2 DF 7E 6D 18 03 40… 0,,10651,0:52.094.269,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,10652,0:52.109.275,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A E2 9D 68 F0 BB 46 98 2A 72 B4 56 28 21 A4 8C… 0,,10656,0:52.110.272,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,10657,0:52.124.277,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10661,0:52.125.274,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,10662,0:52.125.277,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A E2 9D 68 F0 BB 46 98 2A 72 B4 56 28 21 A4 8C… 0,,10666,0:52.126.274,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,10667,0:52.141.279,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 02 65 2F 78 7F FA 05 B5 14 C3 7A 22 43 5B 71… 0,,10671,0:52.142.276,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,10672,0:52.156.281,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10676,0:52.157.278,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,10677,0:52.157.281,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 02 65 2F 78 7F FA 05 B5 14 C3 7A 22 43 5B 71… 0,,10681,0:52.158.278,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,10682,0:52.173.284,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F C5 70 63 BA 60 CD B7 A9 F1 9C 19 02 68 60 48… 0,,10686,0:52.174.281,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,10687,0:52.188.286,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10691,0:52.189.283,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,10692,0:52.189.286,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F C5 70 63 BA 60 CD B7 A9 F1 9C 19 02 68 60 48… 0,,10696,0:52.190.283,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,10697,0:52.205.288,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 60 8A 92 B8 A7 11 3D A4 BB F2 4F 77 2C E1 AF… 0,,10701,0:52.206.285,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,10702,0:52.220.290,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10706,0:52.221.287,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,10707,0:52.221.290,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 60 8A 92 B8 A7 11 3D A4 BB F2 4F 77 2C E1 AF… 0,,10711,0:52.222.287,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,10712,0:52.237.293,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D 90 85 D3 F5 0D 28 C4 96 73 87 5F 55 6D 3C B0… 0,,10716,0:52.238.289,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,10717,0:52.252.295,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10721,0:52.253.292,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,10722,0:52.253.295,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D 90 85 D3 F5 0D 28 C4 96 73 87 5F 55 6D 3C B0… 0,,10726,0:52.254.292,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,10727,0:52.269.297,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 8E 74 90 6A 8D 04 95 B2 E9 5D 7B 19 32 CD 34… 0,,10731,0:52.270.294,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,10732,0:52.284.299,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10736,0:52.285.296,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,10737,0:52.285.299,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 8E 74 90 6A 8D 04 95 B2 E9 5D 7B 19 32 CD 34… 0,,10741,0:52.286.296,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,10742,0:52.301.301,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 E0 16 6D A0 17 F6 11 A4 64 AA B8 FA A9 B9 11… 0,,10746,0:52.302.298,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,10747,0:52.316.304,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10751,0:52.317.300,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,10752,0:52.317.304,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 E0 16 6D A0 17 F6 11 A4 64 AA B8 FA A9 B9 11… 0,,10756,0:52.318.301,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,10757,0:52.333.306,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 33 CF BB A2 B8 0E FC 1C BB 68 B0 74 78 43 65… 0,,10761,0:52.334.303,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,10762,0:52.348.308,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10766,0:52.349.305,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,10767,0:52.365.310,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 65 C7 8D 0D D6 9F 05 DC 73 B9 45 11 BD 96 BD… 0,,10771,0:52.366.307,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,10772,0:52.380.312,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10776,0:52.381.309,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,10777,0:52.381.313,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 65 C7 8D 0D D6 9F 05 DC 73 B9 45 11 BD 96 BD… 0,,10781,0:52.382.309,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,10782,0:52.397.315,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 1C E9 B4 3C 50 79 2C 6E 04 19 B5 88 E0 36 88… 0,,10786,0:52.398.312,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,10787,0:52.412.317,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10791,0:52.413.314,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,10792,0:52.413.317,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 1C E9 B4 3C 50 79 2C 6E 04 19 B5 88 E0 36 88… 0,,10796,0:52.414.314,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,10797,0:52.429.319,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 0C 3E BF BA 61 AC 52 CF 0A C5 B0 5B 31 D2 16… 0,,10801,0:52.430.316,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,10802,0:52.444.321,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10806,0:52.445.318,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,10807,0:52.445.321,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 0C 3E BF BA 61 AC 52 CF 0A C5 B0 5B 31 D2 16… 0,,10811,0:52.446.318,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,10812,0:52.461.324,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 6A 60 72 E8 35 CD 0B D5 99 79 FE 20 72 2A E1… 0,,10816,0:52.462.321,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,10817,0:52.476.326,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10821,0:52.477.323,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,10822,0:52.477.326,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 6A 60 72 E8 35 CD 0B D5 99 79 FE 20 72 2A E1… 0,,10826,0:52.478.323,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,10827,0:52.493.328,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 FF 82 9C 38 5A 9C 36 E6 3B 74 C3 31 F4 23 B8… 0,,10831,0:52.494.325,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,10832,0:52.508.330,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10836,0:52.509.327,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,10837,0:52.509.330,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 FF 82 9C 38 5A 9C 36 E6 3B 74 C3 31 F4 23 B8… 0,,10841,0:52.510.327,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,10842,0:52.525.333,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5C 1B F0 EC 10 46 82 A5 E1 87 A3 51 AB 6D 77 F9… 0,,10846,0:52.526.329,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,10847,0:52.540.335,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10851,0:52.541.331,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,10852,0:52.541.335,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5C 1B F0 EC 10 46 82 A5 E1 87 A3 51 AB 6D 77 F9… 0,,10856,0:52.542.332,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,10857,0:52.557.337,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 CF 1B 38 3E D0 24 B3 1E 8E 8B FD BD 79 30 68… 0,,10861,0:52.558.334,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,10862,0:52.572.339,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10866,0:52.573.336,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,10867,0:52.573.339,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 CF 1B 38 3E D0 24 B3 1E 8E 8B FD BD 79 30 68… 0,,10871,0:52.574.336,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,10872,0:52.589.341,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AE 1A 15 2B CD 84 6A A9 9B 68 CB A5 BE 33 D0 0B… 0,,10876,0:52.590.338,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,10877,0:52.604.343,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10881,0:52.605.340,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,10882,0:52.605.344,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AE 1A 15 2B CD 84 6A A9 9B 68 CB A5 BE 33 D0 0B… 0,,10886,0:52.606.341,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,10887,0:52.621.346,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 00 11 87 7B 5E 61 34 E4 DF B9 1F DC 40 44 05… 0,,10891,0:52.622.343,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,10892,0:52.636.348,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10896,0:52.637.345,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,10897,0:52.637.348,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 00 11 87 7B 5E 61 34 E4 DF B9 1F DC 40 44 05… 0,,10901,0:52.638.345,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,10902,0:52.653.350,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 AE 18 2E F6 34 F2 93 66 F3 D6 9E ED 1A 2B C2… 0,,10906,0:52.654.347,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,10907,0:52.668.352,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10911,0:52.669.349,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,10912,0:52.669.353,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 AE 18 2E F6 34 F2 93 66 F3 D6 9E ED 1A 2B C2… 0,,10916,0:52.670.349,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,10917,0:52.685.355,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF F5 E2 D2 0D 49 E3 FD 97 8E 79 9D 55 13 C4 B4… 0,,10921,0:52.686.352,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,10922,0:52.700.357,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10926,0:52.701.354,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,10927,0:52.701.357,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF F5 E2 D2 0D 49 E3 FD 97 8E 79 9D 55 13 C4 B4… 0,,10931,0:52.702.354,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,10932,0:52.717.359,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AA B6 C4 F9 9E 2A C1 E3 E2 A5 EB FF AA 14 91 C0… 0,,10936,0:52.718.356,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,10937,0:52.732.361,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10941,0:52.733.358,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,10942,0:52.733.361,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AA B6 C4 F9 9E 2A C1 E3 E2 A5 EB FF AA 14 91 C0… 0,,10946,0:52.734.358,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,10947,0:52.749.364,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 3A BC 18 A1 D9 25 E0 3C 01 44 A8 4D 8D 7F BD… 0,,10951,0:52.750.361,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,10952,0:52.764.366,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10956,0:52.765.363,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,10957,0:52.765.366,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 3A BC 18 A1 D9 25 E0 3C 01 44 A8 4D 8D 7F BD… 0,,10961,0:52.766.363,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,10962,0:52.781.368,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 4E D6 F2 6F 47 E0 1C C9 70 4B 1D 14 E7 70 84… 0,,10966,0:52.782.365,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,10967,0:52.796.370,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10971,0:52.797.367,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,10972,0:52.797.370,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 4E D6 F2 6F 47 E0 1C C9 70 4B 1D 14 E7 70 84… 0,,10976,0:52.798.367,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,10977,0:52.813.373,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 77 79 59 8B 3A 05 C5 E1 9E 36 BA 91 61 27 6C… 0,,10981,0:52.814.369,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,10982,0:52.828.375,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,10986,0:52.829.371,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,10987,0:52.829.375,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 77 79 59 8B 3A 05 C5 E1 9E 36 BA 91 61 27 6C… 0,,10991,0:52.830.372,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,10992,0:52.845.377,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 02 F8 4B 7D CE 75 B1 E3 01 F4 CC 00 9A EC 50… 0,,10996,0:52.846.374,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,10997,0:52.860.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11001,0:52.861.376,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,11002,0:52.861.379,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 02 F8 4B 7D CE 75 B1 E3 01 F4 CC 00 9A EC 50… 0,,11006,0:52.862.376,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,11007,0:52.877.381,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 0F C0 F5 01 8D 6B B4 96 00 A3 C7 CF 39 0D 3B… 0,,11011,0:52.878.378,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,11012,0:52.892.383,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11016,0:52.893.380,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,11017,0:52.893.384,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 0F C0 F5 01 8D 6B B4 96 00 A3 C7 CF 39 0D 3B… 0,,11021,0:52.894.380,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,11022,0:52.909.386,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 67 A7 7A 84 5F F2 2C 6D 25 2D 5F 65 A7 4E 41… 0,,11026,0:52.910.383,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,11027,0:52.924.388,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11031,0:52.925.385,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,11032,0:52.925.388,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 67 A7 7A 84 5F F2 2C 6D 25 2D 5F 65 A7 4E 41… 0,,11036,0:52.926.385,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,11037,0:52.941.390,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A 2D 21 ED E9 E7 68 51 4C 13 A8 E5 2D 5C E2 EE… 0,,11041,0:52.942.387,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,11042,0:52.956.392,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11046,0:52.957.389,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,11047,0:52.957.392,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A 2D 21 ED E9 E7 68 51 4C 13 A8 E5 2D 5C E2 EE… 0,,11051,0:52.958.389,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,11052,0:52.973.395,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D4 9A 9B 2C 9B A0 7D 48 9F 15 76 79 A2 74 3A 33… 0,,11056,0:52.974.392,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,11057,0:52.988.397,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11061,0:52.989.394,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,11062,0:53.005.399,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 AE 78 DA 78 17 3B 67 C3 2A D8 BE BE FB 40 E7… 0,,11066,0:53.006.396,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,11067,0:53.020.401,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11071,0:53.021.398,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,11072,0:53.021.401,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 AE 78 DA 78 17 3B 67 C3 2A D8 BE BE FB 40 E7… 0,,11076,0:53.022.398,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,11077,0:53.037.404,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 29 4A 18 01 A2 F8 4D 82 D2 30 DD 96 48 FC 62… 0,,11081,0:53.038.400,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,11082,0:53.052.406,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11086,0:53.053.403,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,11087,0:53.053.406,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 29 4A 18 01 A2 F8 4D 82 D2 30 DD 96 48 FC 62… 0,,11091,0:53.054.403,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,11092,0:53.069.408,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 32 DF 8F CE 0E A0 41 1B 86 B7 6D 47 96 84 01… 0,,11096,0:53.070.405,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,11097,0:53.084.410,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11101,0:53.085.407,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,11102,0:53.085.410,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 32 DF 8F CE 0E A0 41 1B 86 B7 6D 47 96 84 01… 0,,11106,0:53.086.407,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,11107,0:53.101.412,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A 69 E3 46 29 32 21 48 DB 26 8C 03 D9 25 1F B1… 0,,11111,0:53.102.409,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,11112,0:53.116.415,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11116,0:53.117.411,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,11117,0:53.117.415,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A 69 E3 46 29 32 21 48 DB 26 8C 03 D9 25 1F B1… 0,,11121,0:53.118.412,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,11122,0:53.133.417,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 04 06 56 C9 2D ED C7 AF 6C AC B0 10 1E 23 88… 0,,11126,0:53.134.414,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,11127,0:53.148.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11131,0:53.149.416,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,11132,0:53.149.419,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 04 06 56 C9 2D ED C7 AF 6C AC B0 10 1E 23 88… 0,,11136,0:53.150.416,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,11137,0:53.165.421,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 A8 A8 BA 6B 8B 67 01 11 51 4D 2E E4 76 79 71… 0,,11141,0:53.166.418,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,11142,0:53.180.424,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11146,0:53.181.420,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,11147,0:53.181.424,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 A8 A8 BA 6B 8B 67 01 11 51 4D 2E E4 76 79 71… 0,,11151,0:53.182.420,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,11152,0:53.197.426,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 2A C4 A6 69 12 BF 90 36 BB 3A F0 41 66 38 CD… 0,,11156,0:53.198.423,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,11157,0:53.212.428,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11161,0:53.213.425,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,11162,0:53.213.428,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 2A C4 A6 69 12 BF 90 36 BB 3A F0 41 66 38 CD… 0,,11166,0:53.214.425,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,11167,0:53.229.430,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 8E 15 B0 F8 4D E9 A9 C2 B2 89 12 B7 7C 04 57… 0,,11171,0:53.230.427,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,11172,0:53.244.432,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11176,0:53.245.429,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,11177,0:53.245.433,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 8E 15 B0 F8 4D E9 A9 C2 B2 89 12 B7 7C 04 57… 0,,11181,0:53.246.429,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,11182,0:53.261.435,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C 3A 6F 98 AD 5E DC 07 73 EA A6 ED F2 A6 C7 F5… 0,,11186,0:53.262.432,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,11187,0:53.276.437,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11191,0:53.277.434,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,11192,0:53.277.437,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C 3A 6F 98 AD 5E DC 07 73 EA A6 ED F2 A6 C7 F5… 0,,11196,0:53.278.434,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,11197,0:53.293.439,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB AF 23 FC 31 7F 38 B0 9D DA BF 54 C2 51 56 AB… 0,,11201,0:53.294.436,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,11202,0:53.308.441,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11206,0:53.309.438,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,11207,0:53.309.441,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB AF 23 FC 31 7F 38 B0 9D DA BF 54 C2 51 56 AB… 0,,11211,0:53.310.438,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,11212,0:53.325.444,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 3C 63 D0 93 EA 5A 3C 7E 81 D8 ED C4 0C 5C 97… 0,,11216,0:53.326.440,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,11217,0:53.340.446,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11221,0:53.341.443,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,11222,0:53.341.446,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 3C 63 D0 93 EA 5A 3C 7E 81 D8 ED C4 0C 5C 97… 0,,11226,0:53.342.443,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,11227,0:53.357.448,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF BE 71 BF 68 64 CE 22 77 29 60 8B 7E CD 27 CE… 0,,11231,0:53.358.445,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,11232,0:53.372.450,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11236,0:53.373.447,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,11237,0:53.373.450,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF BE 71 BF 68 64 CE 22 77 29 60 8B 7E CD 27 CE… 0,,11241,0:53.374.447,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,11242,0:53.389.452,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E D8 D2 75 F2 91 32 0C B9 C0 11 F0 FD B6 DE 51… 0,,11246,0:53.390.449,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,11247,0:53.404.455,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11251,0:53.405.451,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,11252,0:53.405.455,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E D8 D2 75 F2 91 32 0C B9 C0 11 F0 FD B6 DE 51… 0,,11256,0:53.406.452,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,11257,0:53.421.457,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E DC 3D F9 90 98 B3 EA 29 64 62 DE E5 00 33 EF… 0,,11261,0:53.422.454,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,11262,0:53.436.459,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11266,0:53.437.456,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,11267,0:53.437.459,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E DC 3D F9 90 98 B3 EA 29 64 62 DE E5 00 33 EF… 0,,11271,0:53.438.456,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,11272,0:53.453.461,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 E3 C1 66 89 28 19 4D C7 3C 5B B6 23 EB 1A 2E… 0,,11276,0:53.454.458,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,11277,0:53.468.463,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11281,0:53.469.460,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,11282,0:53.469.464,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 E3 C1 66 89 28 19 4D C7 3C 5B B6 23 EB 1A 2E… 0,,11286,0:53.470.460,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,11287,0:53.485.466,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 36 78 41 70 85 E5 87 B3 04 DE 23 61 5B 1C 3F… 0,,11291,0:53.486.463,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,11292,0:53.500.468,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11296,0:53.501.465,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,11297,0:53.501.468,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 36 78 41 70 85 E5 87 B3 04 DE 23 61 5B 1C 3F… 0,,11301,0:53.502.465,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,11302,0:53.517.470,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 E7 88 64 A4 17 97 71 71 ED 42 DC CC EA 8E 81… 0,,11306,0:53.518.467,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,11307,0:53.532.472,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11311,0:53.533.469,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,11312,0:53.533.472,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 E7 88 64 A4 17 97 71 71 ED 42 DC CC EA 8E 81… 0,,11316,0:53.534.469,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,11317,0:53.549.475,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 60 A4 09 5D B6 75 6D 36 93 33 A8 77 21 EC 6A… 0,,11321,0:53.550.472,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,11322,0:53.564.477,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11326,0:53.565.474,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,11327,0:53.565.477,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 60 A4 09 5D B6 75 6D 36 93 33 A8 77 21 EC 6A… 0,,11331,0:53.566.474,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,11332,0:53.581.479,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 6D 05 D3 30 67 5D 14 3F 84 3A 3D 14 59 9C 4E… 0,,11336,0:53.582.476,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,11337,0:53.596.481,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11341,0:53.597.478,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,11342,0:53.613.484,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 93 AD C3 87 63 A5 B9 38 9B 3A 26 C7 26 68 59… 0,,11346,0:53.614.480,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,11347,0:53.628.486,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11351,0:53.629.483,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,11352,0:53.629.486,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 93 AD C3 87 63 A5 B9 38 9B 3A 26 C7 26 68 59… 0,,11356,0:53.630.483,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,11357,0:53.645.488,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 6F 6D D3 D8 88 CC 69 DE FD 5E 01 D1 71 8B 03… 0,,11361,0:53.646.485,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,11362,0:53.660.490,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11366,0:53.661.487,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,11367,0:53.661.490,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 6F 6D D3 D8 88 CC 69 DE FD 5E 01 D1 71 8B 03… 0,,11371,0:53.662.487,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,11372,0:53.677.492,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 05 38 4F F1 C0 89 78 21 B5 95 64 72 94 15 0E… 0,,11376,0:53.678.489,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,11377,0:53.692.495,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11381,0:53.693.491,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,11382,0:53.693.495,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 05 38 4F F1 C0 89 78 21 B5 95 64 72 94 15 0E… 0,,11386,0:53.694.492,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,11387,0:53.709.497,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 67 81 C1 35 D3 1C 85 2D 2F 41 5A 28 AD 6D 90… 0,,11391,0:53.710.494,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,11392,0:53.724.499,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11396,0:53.725.496,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,11397,0:53.725.499,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 67 81 C1 35 D3 1C 85 2D 2F 41 5A 28 AD 6D 90… 0,,11401,0:53.726.496,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,11402,0:53.741.501,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B DF C0 79 CD 18 82 85 C6 2B 0F 3E 86 0D 4B 76… 0,,11406,0:53.742.498,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,11407,0:53.756.503,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11411,0:53.757.500,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,11412,0:53.757.504,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B DF C0 79 CD 18 82 85 C6 2B 0F 3E 86 0D 4B 76… 0,,11416,0:53.758.500,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,11417,0:53.773.506,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC 2E 88 4B 57 3F 90 F6 8B FC B2 81 47 FE 15 14… 0,,11421,0:53.774.503,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,11422,0:53.788.508,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11426,0:53.789.505,2.833 us,,,,,[1 SOF],[Frame: 481] 0,,11427,0:53.789.508,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC 2E 88 4B 57 3F 90 F6 8B FC B2 81 47 FE 15 14… 0,,11431,0:53.790.505,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,11432,0:53.805.510,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 87 D6 EA FE 0C 00 A6 AA 28 FF 96 B2 E7 33 07… 0,,11436,0:53.806.507,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,11437,0:53.820.512,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11441,0:53.821.509,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,11442,0:53.821.512,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 87 D6 EA FE 0C 00 A6 AA 28 FF 96 B2 E7 33 07… 0,,11446,0:53.822.509,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,11447,0:53.837.515,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 DD 29 31 A0 AE 97 0B 8A C5 E4 1C 68 12 AE 16… 0,,11451,0:53.838.512,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,11452,0:53.852.517,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11456,0:53.853.514,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,11457,0:53.853.517,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 DD 29 31 A0 AE 97 0B 8A C5 E4 1C 68 12 AE 16… 0,,11461,0:53.854.514,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,11462,0:53.869.519,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 DB 39 E3 F9 92 52 74 33 2D 5F 37 6C 45 67 02… 0,,11466,0:53.870.516,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,11467,0:53.884.521,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11471,0:53.885.518,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,11472,0:53.885.521,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 DB 39 E3 F9 92 52 74 33 2D 5F 37 6C 45 67 02… 0,,11476,0:53.886.518,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,11477,0:53.901.524,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 73 80 F2 90 F2 40 55 E4 B3 D3 55 3C 5D EA 22… 0,,11481,0:53.902.520,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,11482,0:53.916.526,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11486,0:53.917.523,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,11487,0:53.917.526,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 73 80 F2 90 F2 40 55 E4 B3 D3 55 3C 5D EA 22… 0,,11491,0:53.918.523,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,11492,0:53.933.528,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 39 46 60 59 8A BC 08 B0 57 43 1F 18 AA 11 8A… 0,,11496,0:53.934.525,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,11497,0:53.948.530,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11501,0:53.949.527,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,11502,0:53.949.530,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 39 46 60 59 8A BC 08 B0 57 43 1F 18 AA 11 8A… 0,,11506,0:53.950.527,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,11507,0:53.965.532,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 6A 63 4F 0F DB DA 23 7F CC E9 A4 12 19 F2 3A… 0,,11511,0:53.966.529,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,11512,0:53.980.535,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11516,0:53.981.531,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,11517,0:53.981.535,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 6A 63 4F 0F DB DA 23 7F CC E9 A4 12 19 F2 3A… 0,,11521,0:53.982.532,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,11522,0:53.997.537,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 55 7D 5F 8B 35 8C D8 A3 EA AC B6 12 0E CD B7… 0,,11526,0:53.998.534,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,11527,0:54.012.539,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11531,0:54.013.536,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,11532,0:54.013.539,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 55 7D 5F 8B 35 8C D8 A3 EA AC B6 12 0E CD B7… 0,,11536,0:54.014.536,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,11537,0:54.029.541,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 5D 7A 31 8B 71 D6 80 4A E8 03 15 F7 77 18 46… 0,,11541,0:54.030.538,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,11542,0:54.044.543,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11546,0:54.045.540,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,11547,0:54.045.544,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 5D 7A 31 8B 71 D6 80 4A E8 03 15 F7 77 18 46… 0,,11551,0:54.046.540,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,11552,0:54.061.546,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 91 D5 43 8D 64 BB C9 03 E0 09 42 2F 77 68 60… 0,,11556,0:54.062.543,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,11557,0:54.076.548,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11561,0:54.077.545,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,11562,0:54.077.548,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 91 D5 43 8D 64 BB C9 03 E0 09 42 2F 77 68 60… 0,,11566,0:54.078.545,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,11567,0:54.093.550,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C BD F2 9F 20 14 CE BA 74 74 61 30 AD 5E C0 1D… 0,,11571,0:54.094.547,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,11572,0:54.108.552,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11576,0:54.109.549,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,11577,0:54.109.552,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C BD F2 9F 20 14 CE BA 74 74 61 30 AD 5E C0 1D… 0,,11581,0:54.110.549,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,11582,0:54.125.555,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 80 01 05 56 BA 19 3F B8 F2 5A E8 11 65 CE 8B… 0,,11586,0:54.126.552,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,11587,0:54.140.557,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11591,0:54.141.554,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,11592,0:54.141.557,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 80 01 05 56 BA 19 3F B8 F2 5A E8 11 65 CE 8B… 0,,11596,0:54.142.554,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,11597,0:54.157.559,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 AC 5E ED 11 D6 CC 7C E2 2D C2 E0 0B D8 27 56… 0,,11601,0:54.158.556,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,11602,0:54.172.561,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11606,0:54.173.558,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,11607,0:54.173.561,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 AC 5E ED 11 D6 CC 7C E2 2D C2 E0 0B D8 27 56… 0,,11611,0:54.174.558,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,11612,0:54.189.564,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8B A1 02 05 F8 1B CD 5C 05 BE B3 F0 99 0D 47 EF… 0,,11616,0:54.190.560,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,11617,0:54.204.566,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11621,0:54.205.562,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,11622,0:54.205.566,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8B A1 02 05 F8 1B CD 5C 05 BE B3 F0 99 0D 47 EF… 0,,11626,0:54.206.563,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,11627,0:54.221.568,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11631,0:54.222.565,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,11632,0:54.236.570,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11636,0:54.237.567,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,11637,0:54.253.572,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11641,0:54.254.569,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,11642,0:54.268.574,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11646,0:54.269.571,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,11647,0:54.269.575,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 97 56 CC 23 95 31 5E 08 3D 95 71 BC AF 90 5A… 0,,11651,0:54.270.572,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,11652,0:54.285.577,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F E2 D1 02 E0 D5 B3 D9 12 7D 3E 93 E8 D5 A0 D9… 0,,11656,0:54.286.574,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,11657,0:54.300.579,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11661,0:54.301.576,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,11662,0:54.301.579,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F E2 D1 02 E0 D5 B3 D9 12 7D 3E 93 E8 D5 A0 D9… 0,,11666,0:54.302.576,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,11667,0:54.317.581,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 2D 06 3D F9 A1 DA D6 D9 36 DF A4 FD F5 75 EE… 0,,11671,0:54.318.578,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,11672,0:54.332.583,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11676,0:54.333.580,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,11677,0:54.333.584,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 2D 06 3D F9 A1 DA D6 D9 36 DF A4 FD F5 75 EE… 0,,11681,0:54.334.580,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,11682,0:54.349.586,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 D7 3E 80 A7 48 54 AF 58 1C 6F F1 21 BA FF C0… 0,,11686,0:54.350.583,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,11687,0:54.364.588,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11691,0:54.365.585,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,11692,0:54.365.588,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 D7 3E 80 A7 48 54 AF 58 1C 6F F1 21 BA FF C0… 0,,11696,0:54.366.585,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,11697,0:54.381.590,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC A4 9E 28 64 3A D9 38 0B 56 E7 8F 42 4B E2 6D… 0,,11701,0:54.382.587,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,11702,0:54.396.592,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11706,0:54.397.589,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,11707,0:54.397.592,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC A4 9E 28 64 3A D9 38 0B 56 E7 8F 42 4B E2 6D… 0,,11711,0:54.398.589,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,11712,0:54.413.595,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C F5 85 5B 45 D4 30 C7 D9 57 C8 32 F1 93 5F 54… 0,,11716,0:54.414.592,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,11717,0:54.428.597,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11721,0:54.429.594,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,11722,0:54.429.597,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C F5 85 5B 45 D4 30 C7 D9 57 C8 32 F1 93 5F 54… 0,,11726,0:54.430.594,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,11727,0:54.445.599,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FF E3 A6 07 EA 72 A4 7B 41 67 95 74 2E C9 F0 C0… 0,,11731,0:54.446.596,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,11732,0:54.460.601,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11736,0:54.461.598,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,11737,0:54.461.601,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FF E3 A6 07 EA 72 A4 7B 41 67 95 74 2E C9 F0 C0… 0,,11741,0:54.462.598,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,11742,0:54.477.604,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B DA ED FB D8 54 23 73 77 07 25 AA E4 F1 17 B6… 0,,11746,0:54.478.600,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,11747,0:54.492.606,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11751,0:54.493.602,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,11752,0:54.493.606,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B DA ED FB D8 54 23 73 77 07 25 AA E4 F1 17 B6… 0,,11756,0:54.494.603,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,11757,0:54.509.608,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A A7 CC 56 BC 72 B7 DF D9 A0 8B 65 DD 75 B8 0C… 0,,11761,0:54.510.605,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,11762,0:54.524.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11766,0:54.525.607,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,11767,0:54.525.610,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A A7 CC 56 BC 72 B7 DF D9 A0 8B 65 DD 75 B8 0C… 0,,11771,0:54.526.607,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,11772,0:54.541.612,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 92 E1 4C 02 2B B6 67 E5 EF 35 12 36 BE 68 07… 0,,11776,0:54.542.609,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,11777,0:54.556.614,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11781,0:54.557.611,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,11782,0:54.557.615,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 92 E1 4C 02 2B B6 67 E5 EF 35 12 36 BE 68 07… 0,,11786,0:54.558.611,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,11787,0:54.573.617,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F D8 35 A5 EE 86 8D C2 B2 99 1A 20 E0 F5 7C DF… 0,,11791,0:54.574.614,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,11792,0:54.588.619,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11796,0:54.589.616,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,11797,0:54.589.619,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F D8 35 A5 EE 86 8D C2 B2 99 1A 20 E0 F5 7C DF… 0,,11801,0:54.590.616,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,11802,0:54.605.621,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 4A E0 9F 93 A2 59 06 DF D0 34 A8 F0 B2 6B FA… 0,,11806,0:54.606.618,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,11807,0:54.620.623,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11811,0:54.621.620,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,11812,0:54.621.623,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 4A E0 9F 93 A2 59 06 DF D0 34 A8 F0 B2 6B FA… 0,,11816,0:54.622.620,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,11817,0:54.637.626,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 B6 EA 66 B2 63 66 3F BA 15 AF F7 B7 93 F3 39… 0,,11821,0:54.638.623,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,11822,0:54.652.628,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11826,0:54.653.625,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,11827,0:54.653.628,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 B6 EA 66 B2 63 66 3F BA 15 AF F7 B7 93 F3 39… 0,,11831,0:54.654.625,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,11832,0:54.669.630,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 8C C0 E3 EC AD 69 2F F1 EF 05 30 94 B2 83 E8… 0,,11836,0:54.670.627,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,11837,0:54.684.632,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11841,0:54.685.629,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,11842,0:54.685.632,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 8C C0 E3 EC AD 69 2F F1 EF 05 30 94 B2 83 E8… 0,,11846,0:54.686.629,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,11847,0:54.701.635,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E CF E0 4F 06 F7 C1 28 A6 C2 C4 48 B7 B6 90 CF… 0,,11851,0:54.702.631,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,11852,0:54.716.637,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11856,0:54.717.634,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,11857,0:54.717.637,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E CF E0 4F 06 F7 C1 28 A6 C2 C4 48 B7 B6 90 CF… 0,,11861,0:54.718.634,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,11862,0:54.733.639,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC A8 0C 95 76 CB 2B 5B 55 37 0C 72 14 EF 4E AB… 0,,11866,0:54.734.636,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,11867,0:54.748.641,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11871,0:54.749.638,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,11872,0:54.749.641,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC A8 0C 95 76 CB 2B 5B 55 37 0C 72 14 EF 4E AB… 0,,11876,0:54.750.638,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,11877,0:54.765.643,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 F7 CE 2A DD FE C1 79 5D 79 53 E9 6E 6A 37 8F… 0,,11881,0:54.766.640,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,11882,0:54.780.646,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11886,0:54.781.642,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,11887,0:54.781.646,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 F7 CE 2A DD FE C1 79 5D 79 53 E9 6E 6A 37 8F… 0,,11891,0:54.782.643,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,11892,0:54.797.648,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 6B 32 91 E2 C8 C1 A1 75 28 41 72 F6 D8 6B B3… 0,,11896,0:54.798.645,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,11897,0:54.812.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11901,0:54.813.647,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,11902,0:54.813.650,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 6B 32 91 E2 C8 C1 A1 75 28 41 72 F6 D8 6B B3… 0,,11906,0:54.814.647,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,11907,0:54.829.652,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 FA 2E E2 A9 97 68 37 04 F4 3E 60 E0 73 FA 15… 0,,11911,0:54.830.649,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,11912,0:54.844.654,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11916,0:54.845.651,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,11917,0:54.861.657,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 B7 10 D5 6B 7F FA 36 63 7F 68 80 F4 BB 76 01… 0,,11921,0:54.862.654,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,11922,0:54.876.659,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11926,0:54.877.656,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,11927,0:54.877.659,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 B7 10 D5 6B 7F FA 36 63 7F 68 80 F4 BB 76 01… 0,,11931,0:54.878.656,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,11932,0:54.893.661,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 11 47 A7 D4 3B 76 1A E1 A8 E1 3B AC BE 66 C3… 0,,11936,0:54.894.658,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,11937,0:54.908.663,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11941,0:54.909.660,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,11942,0:54.909.663,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 11 47 A7 D4 3B 76 1A E1 A8 E1 3B AC BE 66 C3… 0,,11946,0:54.910.660,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,11947,0:54.925.666,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 52 5F FA 13 80 94 91 01 58 30 53 AE 11 93 6F… 0,,11951,0:54.926.663,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,11952,0:54.940.668,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11956,0:54.941.665,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,11957,0:54.941.668,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 52 5F FA 13 80 94 91 01 58 30 53 AE 11 93 6F… 0,,11961,0:54.942.665,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,11962,0:54.957.670,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 12 47 71 77 FB 26 2E 25 F0 EC B6 67 DD 12 10… 0,,11966,0:54.958.667,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,11967,0:54.972.672,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11971,0:54.973.669,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,11972,0:54.973.672,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 12 47 71 77 FB 26 2E 25 F0 EC B6 67 DD 12 10… 0,,11976,0:54.974.669,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,11977,0:54.989.675,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 16 5F 4E 67 DF D9 AA 91 3B C2 89 2A E9 52 10… 0,,11981,0:54.990.671,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,11982,0:55.004.677,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,11986,0:55.005.674,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,11987,0:55.005.677,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 16 5F 4E 67 DF D9 AA 91 3B C2 89 2A E9 52 10… 0,,11991,0:55.006.674,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,11992,0:55.021.679,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D 53 49 DC EE 71 63 CC 24 5D 33 4D 7D 1A 9E 8D… 0,,11996,0:55.022.676,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,11997,0:55.036.681,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12001,0:55.037.678,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,12002,0:55.037.681,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D 53 49 DC EE 71 63 CC 24 5D 33 4D 7D 1A 9E 8D… 0,,12006,0:55.038.678,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,12007,0:55.053.683,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 11 6D A7 48 2D E2 F9 43 B4 6A 7A D9 D0 F6 51… 0,,12011,0:55.054.680,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,12012,0:55.068.686,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12016,0:55.069.682,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,12017,0:55.069.686,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 11 6D A7 48 2D E2 F9 43 B4 6A 7A D9 D0 F6 51… 0,,12021,0:55.070.683,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,12022,0:55.085.688,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 98 EF EC F0 E8 82 5C A7 A5 75 04 26 9E 6C DA… 0,,12026,0:55.086.685,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,12027,0:55.100.690,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12031,0:55.101.687,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,12032,0:55.101.690,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 98 EF EC F0 E8 82 5C A7 A5 75 04 26 9E 6C DA… 0,,12036,0:55.102.687,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,12037,0:55.117.692,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 2E 30 DC E1 E4 C9 63 97 9A 2B 21 E2 F9 4B 6D… 0,,12041,0:55.118.689,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,12042,0:55.132.694,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12046,0:55.133.691,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,12047,0:55.133.695,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 2E 30 DC E1 E4 C9 63 97 9A 2B 21 E2 F9 4B 6D… 0,,12051,0:55.134.691,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,12052,0:55.149.697,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A 8D 2E A3 E6 A6 7D 50 C0 14 88 06 80 BF 76 D7… 0,,12056,0:55.150.694,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,12057,0:55.164.699,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12061,0:55.165.696,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,12062,0:55.165.699,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A 8D 2E A3 E6 A6 7D 50 C0 14 88 06 80 BF 76 D7… 0,,12066,0:55.166.696,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,12067,0:55.181.701,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 03 A1 28 5F B4 36 A5 50 DD 8A 4F F3 81 D2 BE… 0,,12071,0:55.182.698,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,12072,0:55.196.703,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12076,0:55.197.700,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,12077,0:55.197.703,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 03 A1 28 5F B4 36 A5 50 DD 8A 4F F3 81 D2 BE… 0,,12081,0:55.198.700,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,12082,0:55.213.706,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 73 D1 F9 0E 53 16 69 FA 43 3A 13 EF 2D 56 9B… 0,,12086,0:55.214.703,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,12087,0:55.228.708,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12091,0:55.229.705,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,12092,0:55.229.708,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 73 D1 F9 0E 53 16 69 FA 43 3A 13 EF 2D 56 9B… 0,,12096,0:55.230.705,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,12097,0:55.245.710,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D B8 2C 45 27 5B 5C 22 AE 55 B3 09 9D C5 09 63… 0,,12101,0:55.246.707,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,12102,0:55.260.712,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12106,0:55.261.709,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,12107,0:55.261.712,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D B8 2C 45 27 5B 5C 22 AE 55 B3 09 9D C5 09 63… 0,,12111,0:55.262.709,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,12112,0:55.277.715,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 1D CB 55 B0 1C 71 77 AF 92 01 47 83 67 A3 C2… 0,,12116,0:55.278.711,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,12117,0:55.292.717,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12121,0:55.293.714,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,12122,0:55.293.717,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 1D CB 55 B0 1C 71 77 AF 92 01 47 83 67 A3 C2… 0,,12126,0:55.294.714,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,12127,0:55.309.719,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 A7 CA 82 80 7F 10 A1 02 7E 1F 9E 23 BA 6D 6C… 0,,12131,0:55.310.716,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,12132,0:55.324.721,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12136,0:55.325.718,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,12137,0:55.325.721,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 77 A7 CA 82 80 7F 10 A1 02 7E 1F 9E 23 BA 6D 6C… 0,,12141,0:55.326.718,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,12142,0:55.341.724,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 11 72 EB F7 73 8A 60 70 5A E9 B1 99 57 CE 90… 0,,12146,0:55.342.720,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,12147,0:55.356.726,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12151,0:55.357.722,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,12152,0:55.357.726,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 11 72 EB F7 73 8A 60 70 5A E9 B1 99 57 CE 90… 0,,12156,0:55.358.723,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,12157,0:55.373.728,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA C8 C1 72 5A 59 96 CB 80 54 D2 DB 8C 0A 5E 73… 0,,12161,0:55.374.725,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,12162,0:55.388.730,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12166,0:55.389.727,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,12167,0:55.389.730,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA C8 C1 72 5A 59 96 CB 80 54 D2 DB 8C 0A 5E 73… 0,,12171,0:55.390.727,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,12172,0:55.405.732,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 9C 2F 59 73 4D 78 55 01 D0 AE D7 FB 09 1E 4E… 0,,12176,0:55.406.729,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,12177,0:55.420.734,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12181,0:55.421.731,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,12182,0:55.421.735,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 9C 2F 59 73 4D 78 55 01 D0 AE D7 FB 09 1E 4E… 0,,12186,0:55.422.731,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,12187,0:55.437.737,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 88 A3 90 F4 8F 48 80 E2 26 73 75 61 DD 06 C8… 0,,12191,0:55.438.734,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,12192,0:55.452.739,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12196,0:55.453.736,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,12197,0:55.453.739,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 88 A3 90 F4 8F 48 80 E2 26 73 75 61 DD 06 C8… 0,,12201,0:55.454.736,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,12202,0:55.469.741,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC CA E3 65 0B 9F 7D 05 A6 16 A1 A6 CF 40 B2 65… 0,,12206,0:55.470.738,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,12207,0:55.484.743,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12211,0:55.485.740,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,12212,0:55.501.746,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC CA E3 65 0B 9F 7D 05 A6 16 A1 A6 CF 40 B2 65… 0,,12216,0:55.502.743,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,12217,0:55.516.748,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12221,0:55.517.745,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,12222,0:55.517.748,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E 6C 19 26 EB 30 0B 73 7C 7A DD 90 99 3E 42 85… 0,,12226,0:55.518.745,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,12227,0:55.533.750,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B C3 1C E2 4C B4 CB 63 47 12 E2 E4 1B FC EC D6… 0,,12231,0:55.534.747,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,12232,0:55.548.752,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12236,0:55.549.749,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,12237,0:55.549.752,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B C3 1C E2 4C B4 CB 63 47 12 E2 E4 1B FC EC D6… 0,,12241,0:55.550.749,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,12242,0:55.565.755,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 4A B4 79 2D DE 85 D6 27 C5 06 DB DC EF 77 74… 0,,12246,0:55.566.751,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,12247,0:55.580.757,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12251,0:55.581.754,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,12252,0:55.581.757,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 4A B4 79 2D DE 85 D6 27 C5 06 DB DC EF 77 74… 0,,12256,0:55.582.754,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,12257,0:55.597.759,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E5 0B 51 41 20 D6 5B 39 32 C8 B6 80 D9 8A E9 41… 0,,12261,0:55.598.756,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,12262,0:55.612.761,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12266,0:55.613.758,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,12267,0:55.613.761,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E5 0B 51 41 20 D6 5B 39 32 C8 B6 80 D9 8A E9 41… 0,,12271,0:55.614.758,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,12272,0:55.629.763,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B BC 67 12 5B 2B 99 59 44 D2 15 95 2E 0A BA 5D… 0,,12276,0:55.630.760,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,12277,0:55.644.766,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12281,0:55.645.762,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,12282,0:55.645.766,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B BC 67 12 5B 2B 99 59 44 D2 15 95 2E 0A BA 5D… 0,,12286,0:55.646.763,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,12287,0:55.661.768,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 C2 87 EF 15 B6 D6 9D E6 66 0D 29 A4 28 9A 3F… 0,,12291,0:55.662.765,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,12292,0:55.676.770,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12296,0:55.677.767,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,12297,0:55.677.770,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 C2 87 EF 15 B6 D6 9D E6 66 0D 29 A4 28 9A 3F… 0,,12301,0:55.678.767,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,12302,0:55.693.772,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 63 EB 94 62 6A 75 DA 03 0B C1 D2 A4 0B CD 44… 0,,12306,0:55.694.769,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,12307,0:55.708.774,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12311,0:55.709.771,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,12312,0:55.709.775,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 63 EB 94 62 6A 75 DA 03 0B C1 D2 A4 0B CD 44… 0,,12316,0:55.710.771,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,12317,0:55.725.777,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D 95 CD 2E A7 7B 61 DF BE 7E 92 E3 48 A5 82 6B… 0,,12321,0:55.726.774,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,12322,0:55.740.779,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12326,0:55.741.776,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,12327,0:55.741.779,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D 95 CD 2E A7 7B 61 DF BE 7E 92 E3 48 A5 82 6B… 0,,12331,0:55.742.776,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,12332,0:55.757.781,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 0F 05 71 5F 64 57 4C 80 0A 63 C0 A6 91 90 89… 0,,12336,0:55.758.778,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,12337,0:55.772.783,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12341,0:55.773.780,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,12342,0:55.773.783,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 0F 05 71 5F 64 57 4C 80 0A 63 C0 A6 91 90 89… 0,,12346,0:55.774.780,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,12347,0:55.789.786,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 70 FF 73 DE E3 40 F3 B0 81 F5 D3 34 3E 36 3F 8B… 0,,12351,0:55.790.783,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,12352,0:55.804.788,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12356,0:55.805.785,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,12357,0:55.805.788,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 70 FF 73 DE E3 40 F3 B0 81 F5 D3 34 3E 36 3F 8B… 0,,12361,0:55.806.785,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,12362,0:55.821.790,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 6E 8F B6 68 13 F8 51 13 76 31 33 E8 F0 85 33… 0,,12366,0:55.822.787,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,12367,0:55.836.792,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12371,0:55.837.789,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,12372,0:55.837.792,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 6E 8F B6 68 13 F8 51 13 76 31 33 E8 F0 85 33… 0,,12376,0:55.838.789,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,12377,0:55.853.795,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 21 E2 93 6D 49 A5 53 F7 59 74 01 2E CE FF AF… 0,,12381,0:55.854.791,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,12382,0:55.868.797,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12386,0:55.869.793,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,12387,0:55.869.797,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 21 E2 93 6D 49 A5 53 F7 59 74 01 2E CE FF AF… 0,,12391,0:55.870.794,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,12392,0:55.885.799,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 31 1C FF 17 D1 33 38 66 A0 1A 04 C3 FF F4 6E… 0,,12396,0:55.886.796,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,12397,0:55.900.801,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12401,0:55.901.798,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,12402,0:55.901.801,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 31 1C FF 17 D1 33 38 66 A0 1A 04 C3 FF F4 6E… 0,,12406,0:55.902.798,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,12407,0:55.917.803,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 D5 09 95 42 C1 CC 20 CD 2E 84 67 74 38 48 46… 0,,12411,0:55.918.800,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,12412,0:55.932.805,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12416,0:55.933.802,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,12417,0:55.933.806,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 D5 09 95 42 C1 CC 20 CD 2E 84 67 74 38 48 46… 0,,12421,0:55.934.803,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,12422,0:55.949.808,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 88 C4 94 5F EE 82 A0 AC A8 A6 2D 03 50 A8 DD… 0,,12426,0:55.950.805,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,12427,0:55.964.810,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12431,0:55.965.807,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,12432,0:55.965.810,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 88 C4 94 5F EE 82 A0 AC A8 A6 2D 03 50 A8 DD… 0,,12436,0:55.966.807,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,12437,0:55.981.812,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 55 D4 03 84 BE 79 51 D5 C1 C5 9D 50 EA BA CC… 0,,12441,0:55.982.809,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,12442,0:55.996.814,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12446,0:55.997.811,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,12447,0:55.997.815,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 55 D4 03 84 BE 79 51 D5 C1 C5 9D 50 EA BA CC… 0,,12451,0:55.998.811,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,12452,0:56.013.817,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 BA 9E 66 08 C8 53 3B B5 43 4F 10 DD D6 78 19… 0,,12456,0:56.014.814,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,12457,0:56.028.819,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12461,0:56.029.816,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,12462,0:56.029.819,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 BA 9E 66 08 C8 53 3B B5 43 4F 10 DD D6 78 19… 0,,12466,0:56.030.816,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,12467,0:56.045.821,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 28 B9 79 BE A2 B7 79 1F B8 42 E6 CA 96 44 E5… 0,,12471,0:56.046.818,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,12472,0:56.060.823,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12476,0:56.061.820,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,12477,0:56.061.823,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 28 B9 79 BE A2 B7 79 1F B8 42 E6 CA 96 44 E5… 0,,12481,0:56.062.820,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,12482,0:56.077.826,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 77 92 BC 20 4F 0E 17 4B C9 69 CC 85 50 89 74 BB… 0,,12486,0:56.078.823,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,12487,0:56.092.828,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12491,0:56.093.825,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,12492,0:56.109.830,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 7E 22 A0 2C 04 70 74 56 0D 4D FA 0B E8 E3 25… 0,,12496,0:56.110.827,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,12497,0:56.124.832,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12501,0:56.125.829,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,12502,0:56.125.832,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 7E 22 A0 2C 04 70 74 56 0D 4D FA 0B E8 E3 25… 0,,12506,0:56.126.829,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,12507,0:56.141.835,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 68 43 7B 3C 5B EC 32 A7 62 C4 6C 5F 2E 3F 7F… 0,,12511,0:56.142.831,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,12512,0:56.156.837,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12516,0:56.157.833,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,12517,0:56.157.837,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 68 43 7B 3C 5B EC 32 A7 62 C4 6C 5F 2E 3F 7F… 0,,12521,0:56.158.834,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,12522,0:56.173.839,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 94 D0 4B 76 10 94 64 50 07 DA 53 6A F9 18 CF… 0,,12526,0:56.174.836,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,12527,0:56.188.841,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12531,0:56.189.838,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,12532,0:56.189.841,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 94 D0 4B 76 10 94 64 50 07 DA 53 6A F9 18 CF… 0,,12536,0:56.190.838,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,12537,0:56.205.843,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 6D 0A 6C EC 3F 96 B8 31 B7 6D 17 94 34 6C 19… 0,,12541,0:56.206.840,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,12542,0:56.220.845,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12546,0:56.221.842,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,12547,0:56.221.846,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 6D 0A 6C EC 3F 96 B8 31 B7 6D 17 94 34 6C 19… 0,,12551,0:56.222.842,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,12552,0:56.237.848,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 48 7B 6E FA AF AA 11 82 DD E5 3A 5A 3B A0 DB… 0,,12556,0:56.238.845,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,12557,0:56.252.850,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12561,0:56.253.847,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,12562,0:56.253.850,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 48 7B 6E FA AF AA 11 82 DD E5 3A 5A 3B A0 DB… 0,,12566,0:56.254.847,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,12567,0:56.269.852,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 47 FD 2F F0 DD 50 5A 86 3B B9 81 43 96 96 65… 0,,12571,0:56.270.849,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,12572,0:56.284.854,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12576,0:56.285.851,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,12577,0:56.285.854,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 47 FD 2F F0 DD 50 5A 86 3B B9 81 43 96 96 65… 0,,12581,0:56.286.851,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,12582,0:56.301.857,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 2F 0E 5B F2 A8 F0 CB 06 B2 9D 8A 00 6B 32 70… 0,,12586,0:56.302.854,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,12587,0:56.316.859,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12591,0:56.317.856,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,12592,0:56.317.859,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 2F 0E 5B F2 A8 F0 CB 06 B2 9D 8A 00 6B 32 70… 0,,12596,0:56.318.856,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,12597,0:56.333.861,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 34 44 F1 2F D4 F0 79 2D 00 65 3E 30 C7 72 3B… 0,,12601,0:56.334.858,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,12602,0:56.348.863,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12606,0:56.349.860,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,12607,0:56.349.863,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 34 44 F1 2F D4 F0 79 2D 00 65 3E 30 C7 72 3B… 0,,12611,0:56.350.860,15.005.000 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,12612,0:56.365.866,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 FB 05 89 92 CD B2 FC AB 7B 3A 19 35 54 32 9D… 0,,12616,0:56.366.862,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,12617,0:56.380.868,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12621,0:56.381.865,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,12622,0:56.381.868,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 FB 05 89 92 CD B2 FC AB 7B 3A 19 35 54 32 9D… 0,,12626,0:56.382.865,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,12627,0:56.397.870,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 50 F5 84 C3 A9 60 F7 A8 08 BC E2 86 55 74 F2… 0,,12631,0:56.398.867,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,12632,0:56.412.872,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12636,0:56.413.869,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,12637,0:56.413.872,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B5 50 F5 84 C3 A9 60 F7 A8 08 BC E2 86 55 74 F2… 0,,12641,0:56.414.869,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,12642,0:56.429.874,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 92 71 EE 62 BF F1 66 36 B6 FD 72 B2 32 BC BC… 0,,12646,0:56.430.871,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,12647,0:56.444.877,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12651,0:56.445.873,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,12652,0:56.445.877,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 92 71 EE 62 BF F1 66 36 B6 FD 72 B2 32 BC BC… 0,,12656,0:56.446.874,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,12657,0:56.461.879,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 76 7A 51 7D 3B A6 81 37 56 18 36 81 3F FE 19… 0,,12661,0:56.462.876,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,12662,0:56.476.881,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12666,0:56.477.878,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,12667,0:56.477.881,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 76 7A 51 7D 3B A6 81 37 56 18 36 81 3F FE 19… 0,,12671,0:56.478.878,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,12672,0:56.493.883,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 1F 05 E9 46 8F E7 9C FC 88 CA FB 51 1B 61 C3… 0,,12676,0:56.494.880,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,12677,0:56.508.885,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12681,0:56.509.882,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,12682,0:56.509.886,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 1F 05 E9 46 8F E7 9C FC 88 CA FB 51 1B 61 C3… 0,,12686,0:56.510.882,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,12687,0:56.525.888,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 40 47 63 B6 F9 EC 7A 39 C3 D8 80 44 FF 35 47… 0,,12691,0:56.526.885,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,12692,0:56.540.890,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12696,0:56.541.887,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,12697,0:56.541.890,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 40 47 63 B6 F9 EC 7A 39 C3 D8 80 44 FF 35 47… 0,,12701,0:56.542.887,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,12702,0:56.557.892,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 B7 32 A3 BB D4 0A 72 5D 15 99 6F 12 C2 78 F2… 0,,12706,0:56.558.889,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,12707,0:56.572.894,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12711,0:56.573.891,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,12712,0:56.573.894,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 B7 32 A3 BB D4 0A 72 5D 15 99 6F 12 C2 78 F2… 0,,12716,0:56.574.891,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,12717,0:56.589.897,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E6 94 C1 A1 C3 B1 D0 FA 0B 63 69 99 E4 BC 05… 0,,12721,0:56.590.894,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,12722,0:56.604.899,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12726,0:56.605.896,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,12727,0:56.605.899,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E6 94 C1 A1 C3 B1 D0 FA 0B 63 69 99 E4 BC 05… 0,,12731,0:56.606.896,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,12732,0:56.621.901,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 BA 49 3C 19 43 23 86 FB 41 FA 31 B1 93 7F 2C… 0,,12736,0:56.622.898,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,12737,0:56.636.903,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12741,0:56.637.900,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,12742,0:56.637.903,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 BA 49 3C 19 43 23 86 FB 41 FA 31 B1 93 7F 2C… 0,,12746,0:56.638.900,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,12747,0:56.653.906,50.937 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 62 6C 7F 28 14 AE 95 F9 63 FE C7 6B 09 CA F7… 0,,12751,0:56.654.902,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,12752,0:56.668.908,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12756,0:56.669.905,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,12757,0:56.669.908,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 62 6C 7F 28 14 AE 95 F9 63 FE C7 6B 09 CA F7… 0,,12761,0:56.670.905,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,12762,0:56.685.910,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 8B 9C 31 FE 85 0F 96 9D E8 30 5B 76 B6 31 39… 0,,12766,0:56.686.907,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,12767,0:56.700.912,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12771,0:56.701.909,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,12772,0:56.701.912,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 8B 9C 31 FE 85 0F 96 9D E8 30 5B 76 B6 31 39… 0,,12776,0:56.702.909,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,12777,0:56.717.914,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 35 8F C3 1A 29 A7 43 D7 35 97 4F 61 E2 65 12… 0,,12781,0:56.718.911,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,12782,0:56.732.917,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12786,0:56.733.913,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,12787,0:56.733.917,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 35 8F C3 1A 29 A7 43 D7 35 97 4F 61 E2 65 12… 0,,12791,0:56.734.914,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,12792,0:56.749.919,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 01 04 43 62 19 97 AE F6 46 BB 11 5B E6 C3 AC… 0,,12796,0:56.750.916,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,12797,0:56.764.921,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12801,0:56.765.918,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,12802,0:56.765.921,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 01 04 43 62 19 97 AE F6 46 BB 11 5B E6 C3 AC… 0,,12806,0:56.766.918,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,12807,0:56.781.923,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 69 9F 51 D3 EA E8 BD EC 2B 2A CC C5 B7 E6 83… 0,,12811,0:56.782.920,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,12812,0:56.796.925,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12816,0:56.797.922,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,12817,0:56.797.926,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 69 9F 51 D3 EA E8 BD EC 2B 2A CC C5 B7 E6 83… 0,,12821,0:56.798.922,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,12822,0:56.813.928,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E BB 9B D6 C9 98 10 23 67 5D DE 5A 30 C7 CB E7… 0,,12826,0:56.814.925,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,12827,0:56.828.930,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12831,0:56.829.927,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,12832,0:56.829.930,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E BB 9B D6 C9 98 10 23 67 5D DE 5A 30 C7 CB E7… 0,,12836,0:56.830.927,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,12837,0:56.845.932,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 6B A1 A6 50 5B F3 46 92 24 7B FF FA 1A 6E F5… 0,,12841,0:56.846.929,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,12842,0:56.860.934,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12846,0:56.861.931,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,12847,0:56.861.934,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 6B A1 A6 50 5B F3 46 92 24 7B FF FA 1A 6E F5… 0,,12851,0:56.862.931,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,12852,0:56.877.937,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 BA B0 FC E6 E3 DD 7B D6 7E D3 F7 10 89 70 07… 0,,12856,0:56.878.934,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,12857,0:56.892.939,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12861,0:56.893.936,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,12862,0:56.893.939,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 BA B0 FC E6 E3 DD 7B D6 7E D3 F7 10 89 70 07… 0,,12866,0:56.894.936,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,12867,0:56.909.941,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 50 DD 00 5C CB FC 1C 3E 01 44 EC D2 F0 11 BF D2… 0,,12871,0:56.910.938,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,12872,0:56.924.943,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12876,0:56.925.940,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,12877,0:56.925.943,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 50 DD 00 5C CB FC 1C 3E 01 44 EC D2 F0 11 BF D2… 0,,12881,0:56.926.940,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,12882,0:56.941.946,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 45 27 C2 D8 AF 15 EA EE 8F 68 02 BB 27 6B 70 51… 0,,12886,0:56.942.942,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,12887,0:56.956.948,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12891,0:56.957.945,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,12892,0:56.957.948,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 45 27 C2 D8 AF 15 EA EE 8F 68 02 BB 27 6B 70 51… 0,,12896,0:56.958.945,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,12897,0:56.973.950,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4C 73 66 C1 B3 48 38 9F 4A 34 69 02 BD 0F 0C 08… 0,,12901,0:56.974.947,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,12902,0:56.988.952,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12906,0:56.989.949,2.833 us,,,,,[1 SOF],[Frame: 1633] 0,,12907,0:56.989.952,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4C 73 66 C1 B3 48 38 9F 4A 34 69 02 BD 0F 0C 08… 0,,12911,0:56.990.949,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,12912,0:57.005.954,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 5E 6F 7F AB C6 88 78 1F BF 48 AC 25 67 D7 3C… 0,,12916,0:57.006.951,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,12917,0:57.020.957,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12921,0:57.021.953,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,12922,0:57.021.957,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 5E 6F 7F AB C6 88 78 1F BF 48 AC 25 67 D7 3C… 0,,12926,0:57.022.954,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,12927,0:57.037.959,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0C BC DC 6C A5 42 1F 17 AB 15 D8 0F 15 6A 55 E9… 0,,12931,0:57.038.956,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,12932,0:57.052.961,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12936,0:57.053.958,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,12937,0:57.053.961,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0C BC DC 6C A5 42 1F 17 AB 15 D8 0F 15 6A 55 E9… 0,,12941,0:57.054.958,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,12942,0:57.069.963,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 C5 C7 F2 34 6C 7E 69 9D 66 C6 78 B1 4D 34 07… 0,,12946,0:57.070.960,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,12947,0:57.084.965,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12951,0:57.085.962,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,12952,0:57.085.966,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 C5 C7 F2 34 6C 7E 69 9D 66 C6 78 B1 4D 34 07… 0,,12956,0:57.086.962,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,12957,0:57.101.968,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 BE DE A6 A8 7F 2F C8 9D 98 DE F5 F2 40 DC 95… 0,,12961,0:57.102.965,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,12962,0:57.116.970,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12966,0:57.117.967,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,12967,0:57.117.970,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 BE DE A6 A8 7F 2F C8 9D 98 DE F5 F2 40 DC 95… 0,,12971,0:57.118.967,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,12972,0:57.133.972,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 A7 9E 6A D6 31 B3 60 19 37 68 02 91 0A ED 03… 0,,12976,0:57.134.969,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,12977,0:57.148.974,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12981,0:57.149.971,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,12982,0:57.149.974,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 A7 9E 6A D6 31 B3 60 19 37 68 02 91 0A ED 03… 0,,12986,0:57.150.971,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,12987,0:57.165.977,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B B1 6B F4 0B 8A 01 47 76 BF 8B 2A 2E 30 90 C3… 0,,12991,0:57.166.974,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,12992,0:57.180.979,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,12996,0:57.181.976,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,12997,0:57.181.979,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B B1 6B F4 0B 8A 01 47 76 BF 8B 2A 2E 30 90 C3… 0,,13001,0:57.182.976,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,13002,0:57.197.981,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 4E 82 40 74 7B D4 7E 54 FE D1 7C 0D 35 4A 7B… 0,,13006,0:57.198.978,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,13007,0:57.212.983,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13011,0:57.213.980,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,13012,0:57.213.983,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 4E 82 40 74 7B D4 7E 54 FE D1 7C 0D 35 4A 7B… 0,,13016,0:57.214.980,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,13017,0:57.229.986,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 BF 77 8C DE AF 68 29 9B BE 48 EB 93 18 76 8F… 0,,13021,0:57.230.982,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,13022,0:57.244.988,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13026,0:57.245.985,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,13027,0:57.245.988,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 BF 77 8C DE AF 68 29 9B BE 48 EB 93 18 76 8F… 0,,13031,0:57.246.985,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,13032,0:57.261.990,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7B E3 22 CD 98 7B AE 34 F1 B3 FD 3D FC D6 F4 F8… 0,,13036,0:57.262.987,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,13037,0:57.276.992,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13041,0:57.277.989,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,13042,0:57.277.992,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7B E3 22 CD 98 7B AE 34 F1 B3 FD 3D FC D6 F4 F8… 0,,13046,0:57.278.989,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,13047,0:57.293.994,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD CE B4 BE F0 04 36 4C 0E 74 AF 47 B5 E1 57 63… 0,,13051,0:57.294.991,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,13052,0:57.308.997,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13056,0:57.309.993,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,13057,0:57.309.997,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD CE B4 BE F0 04 36 4C 0E 74 AF 47 B5 E1 57 63… 0,,13061,0:57.310.994,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,13062,0:57.325.999,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B5 E8 F0 A1 E3 0E 00 DF 40 A5 DF 31 49 F4 A6 9C… 0,,13066,0:57.326.996,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,13067,0:57.341.001,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13071,0:57.341.998,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,13072,0:57.358.003,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 87 EC 76 25 F1 71 FA FD FD 75 34 E8 2A 08 E7… 0,,13076,0:57.359.000,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,13077,0:57.373.005,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13081,0:57.374.002,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,13082,0:57.374.006,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 87 EC 76 25 F1 71 FA FD FD 75 34 E8 2A 08 E7… 0,,13086,0:57.375.002,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,13087,0:57.390.008,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 26 6C 7C E3 C0 0B F5 64 89 A3 0E 60 D9 B1 67… 0,,13091,0:57.391.005,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,13092,0:57.405.010,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13096,0:57.406.007,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,13097,0:57.406.010,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 26 6C 7C E3 C0 0B F5 64 89 A3 0E 60 D9 B1 67… 0,,13101,0:57.407.007,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,13102,0:57.422.012,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 C7 CC 5B A5 D2 4F 09 04 04 79 FC 70 F1 B4 71… 0,,13106,0:57.423.009,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,13107,0:57.437.014,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13111,0:57.438.011,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,13112,0:57.438.014,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 C7 CC 5B A5 D2 4F 09 04 04 79 FC 70 F1 B4 71… 0,,13116,0:57.439.011,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,13117,0:57.454.017,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 BE E8 BE 41 62 CD 47 1B D2 87 2D 75 DA 7D 17… 0,,13121,0:57.455.014,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,13122,0:57.469.019,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13126,0:57.470.016,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,13127,0:57.470.019,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 BE E8 BE 41 62 CD 47 1B D2 87 2D 75 DA 7D 17… 0,,13131,0:57.471.016,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,13132,0:57.486.021,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 D4 82 85 A2 15 29 01 8F DA 72 F7 73 25 64 63… 0,,13136,0:57.487.018,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,13137,0:57.501.023,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13141,0:57.502.020,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,13142,0:57.502.023,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 D4 82 85 A2 15 29 01 8F DA 72 F7 73 25 64 63… 0,,13146,0:57.503.020,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,13147,0:57.518.026,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 39 F7 93 BC 66 06 1D 43 CB 57 65 48 2E EA 87… 0,,13151,0:57.519.022,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,13152,0:57.533.028,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13156,0:57.534.024,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,13157,0:57.534.028,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 39 F7 93 BC 66 06 1D 43 CB 57 65 48 2E EA 87… 0,,13161,0:57.535.025,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,13162,0:57.550.030,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF 82 9D A9 C8 7A CF 59 D9 A5 38 B9 F7 FD 19 90… 0,,13166,0:57.551.027,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,13167,0:57.565.032,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13171,0:57.566.029,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,13172,0:57.566.032,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF 82 9D A9 C8 7A CF 59 D9 A5 38 B9 F7 FD 19 90… 0,,13176,0:57.567.029,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,13177,0:57.582.034,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 02 02 09 66 F7 B1 3A 51 61 3D 22 EF 62 84 15… 0,,13181,0:57.583.031,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,13182,0:57.597.036,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13186,0:57.598.033,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,13187,0:57.598.037,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 02 02 09 66 F7 B1 3A 51 61 3D 22 EF 62 84 15… 0,,13191,0:57.599.034,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,13192,0:57.614.039,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 AB AF DE 7B 8C A2 C1 CE 9B 76 22 EE BB E5 15… 0,,13196,0:57.615.036,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,13197,0:57.629.041,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13201,0:57.630.038,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,13202,0:57.630.041,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 AB AF DE 7B 8C A2 C1 CE 9B 76 22 EE BB E5 15… 0,,13206,0:57.631.038,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,13207,0:57.646.043,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 59 A8 E4 03 6B 89 98 9E 0A 32 21 24 7D 6B 37… 0,,13211,0:57.647.040,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,13212,0:57.661.045,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13216,0:57.662.042,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,13217,0:57.662.046,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 59 A8 E4 03 6B 89 98 9E 0A 32 21 24 7D 6B 37… 0,,13221,0:57.663.042,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,13222,0:57.678.048,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B7 EE C3 09 35 73 48 31 95 67 D5 6A E4 E9 76 41… 0,,13226,0:57.679.045,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,13227,0:57.693.050,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13231,0:57.694.047,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,13232,0:57.694.050,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B7 EE C3 09 35 73 48 31 95 67 D5 6A E4 E9 76 41… 0,,13236,0:57.695.047,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,13237,0:57.710.052,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 A3 58 A4 44 90 54 81 53 B5 6A 93 C2 AC 49 0A… 0,,13241,0:57.711.049,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,13242,0:57.725.054,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13246,0:57.726.051,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,13247,0:57.726.054,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 A3 58 A4 44 90 54 81 53 B5 6A 93 C2 AC 49 0A… 0,,13251,0:57.727.051,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,13252,0:57.742.057,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF E6 65 D0 CC C5 60 4C 18 9A CC 2A D4 1F EF 81… 0,,13256,0:57.743.054,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,13257,0:57.757.059,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13261,0:57.758.056,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,13262,0:57.758.059,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF E6 65 D0 CC C5 60 4C 18 9A CC 2A D4 1F EF 81… 0,,13266,0:57.759.056,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,13267,0:57.774.061,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 ED CF 00 AE 03 E9 D0 B6 AC 24 19 66 F4 D9 6A 7E… 0,,13271,0:57.775.058,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,13272,0:57.789.063,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13276,0:57.790.060,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,13277,0:57.790.063,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 ED CF 00 AE 03 E9 D0 B6 AC 24 19 66 F4 D9 6A 7E… 0,,13281,0:57.791.060,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,13282,0:57.806.066,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AB C3 FB 17 AE 53 28 FF 78 23 FF 35 BD 4E B5 A3… 0,,13286,0:57.807.062,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,13287,0:57.821.068,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13291,0:57.822.064,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,13292,0:57.822.068,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AB C3 FB 17 AE 53 28 FF 78 23 FF 35 BD 4E B5 A3… 0,,13296,0:57.823.065,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,13297,0:57.838.070,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 82 52 19 CA EC 51 FA A7 31 4B 0F 44 51 D7 C5… 0,,13301,0:57.839.067,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,13302,0:57.853.072,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13306,0:57.854.069,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,13307,0:57.854.072,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 82 52 19 CA EC 51 FA A7 31 4B 0F 44 51 D7 C5… 0,,13311,0:57.855.069,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,13312,0:57.870.074,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 92 12 0B EA 92 90 9C 78 BA D8 7C 15 A3 CF 16 1D… 0,,13316,0:57.871.071,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,13317,0:57.885.076,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13321,0:57.886.073,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,13322,0:57.886.077,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 92 12 0B EA 92 90 9C 78 BA D8 7C 15 A3 CF 16 1D… 0,,13326,0:57.887.073,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,13327,0:57.902.079,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 C2 A5 FF 14 74 F4 C0 AA 67 CA AF 87 7C F9 FC… 0,,13331,0:57.903.076,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,13332,0:57.917.081,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13336,0:57.918.078,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,13337,0:57.918.081,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 C2 A5 FF 14 74 F4 C0 AA 67 CA AF 87 7C F9 FC… 0,,13341,0:57.919.078,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,13342,0:57.934.083,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 58 78 A4 49 F6 67 8C F5 AC 54 4C B4 C5 35 0B… 0,,13346,0:57.935.080,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,13347,0:57.949.085,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13351,0:57.950.082,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,13352,0:57.950.085,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 58 78 A4 49 F6 67 8C F5 AC 54 4C B4 C5 35 0B… 0,,13356,0:57.951.082,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,13357,0:57.966.088,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 81 8E 0A EB F4 AD 65 F4 9B 02 00 84 52 12 E2… 0,,13361,0:57.967.085,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,13362,0:57.981.090,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13366,0:57.982.087,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,13367,0:57.982.090,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 81 8E 0A EB F4 AD 65 F4 9B 02 00 84 52 12 E2… 0,,13371,0:57.983.087,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,13372,0:57.998.092,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 3E 68 F5 C5 84 49 DE 02 76 69 5D A4 09 3B 0A… 0,,13376,0:57.999.089,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,13377,0:58.013.094,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13381,0:58.014.091,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,13382,0:58.014.094,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 3E 68 F5 C5 84 49 DE 02 76 69 5D A4 09 3B 0A… 0,,13386,0:58.015.091,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,13387,0:58.030.097,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE C9 F5 A4 15 8C 6C 42 C5 B7 E9 94 43 38 8B 87… 0,,13391,0:58.031.093,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,13392,0:58.045.099,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13396,0:58.046.096,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,13397,0:58.046.099,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE C9 F5 A4 15 8C 6C 42 C5 B7 E9 94 43 38 8B 87… 0,,13401,0:58.047.096,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,13402,0:58.062.101,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 30 23 FB B0 B1 10 BC C0 AD 78 E6 AD 42 FD B7… 0,,13406,0:58.063.098,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,13407,0:58.077.103,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13411,0:58.078.100,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,13412,0:58.078.103,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 30 23 FB B0 B1 10 BC C0 AD 78 E6 AD 42 FD B7… 0,,13416,0:58.079.100,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,13417,0:58.094.105,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 3A AD 68 DC 67 3B 0D F3 82 93 CD 28 AF 2B 68… 0,,13421,0:58.095.102,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,13422,0:58.109.108,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13426,0:58.110.104,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,13427,0:58.110.108,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 3A AD 68 DC 67 3B 0D F3 82 93 CD 28 AF 2B 68… 0,,13431,0:58.111.105,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,13432,0:58.126.110,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3C 92 D6 A4 E3 04 AB 02 A0 59 5C FA 91 27 09 34… 0,,13436,0:58.127.107,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,13437,0:58.141.112,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13441,0:58.142.109,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,13442,0:58.142.112,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3C 92 D6 A4 E3 04 AB 02 A0 59 5C FA 91 27 09 34… 0,,13446,0:58.143.109,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,13447,0:58.158.114,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 04 90 D7 54 BB 34 B2 D2 A7 CF CF FE 07 C5 AE… 0,,13451,0:58.159.111,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,13452,0:58.173.116,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13456,0:58.174.113,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,13457,0:58.174.117,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 04 90 D7 54 BB 34 B2 D2 A7 CF CF FE 07 C5 AE… 0,,13461,0:58.175.113,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,13462,0:58.190.119,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 B1 F3 BB ED D1 EE 25 3E 42 10 69 12 E6 11 DD… 0,,13466,0:58.191.116,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,13467,0:58.205.121,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13471,0:58.206.118,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,13472,0:58.206.121,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 B1 F3 BB ED D1 EE 25 3E 42 10 69 12 E6 11 DD… 0,,13476,0:58.207.118,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,13477,0:58.222.123,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 02 5F 46 50 96 D5 F8 5D 7F ED 13 B6 16 2D F7… 0,,13481,0:58.223.120,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,13482,0:58.237.125,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13486,0:58.238.122,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,13487,0:58.238.125,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 02 5F 46 50 96 D5 F8 5D 7F ED 13 B6 16 2D F7… 0,,13491,0:58.239.122,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,13492,0:58.254.128,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 27 54 52 67 04 52 04 AC 87 A5 4D E3 5C 7B A3… 0,,13496,0:58.255.125,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,13497,0:58.269.130,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13501,0:58.270.127,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,13502,0:58.270.130,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 27 54 52 67 04 52 04 AC 87 A5 4D E3 5C 7B A3… 0,,13506,0:58.271.127,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,13507,0:58.286.132,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6C 61 05 89 02 D4 11 E7 9B B1 76 B0 F9 E6 F3 86… 0,,13511,0:58.287.129,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,13512,0:58.301.134,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13516,0:58.302.131,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,13517,0:58.302.134,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6C 61 05 89 02 D4 11 E7 9B B1 76 B0 F9 E6 F3 86… 0,,13521,0:58.303.131,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,13522,0:58.318.137,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 45 DD 4F 9F E9 78 63 B3 1B 2A 2A CC 17 33 7F… 0,,13526,0:58.319.133,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,13527,0:58.333.139,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13531,0:58.334.136,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,13532,0:58.334.139,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 45 DD 4F 9F E9 78 63 B3 1B 2A 2A CC 17 33 7F… 0,,13536,0:58.335.136,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,13537,0:58.350.141,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 48 8D F6 26 C7 A9 F8 B7 2E 76 31 F7 4E CB 2B… 0,,13541,0:58.351.138,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,13542,0:58.365.143,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13546,0:58.366.140,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,13547,0:58.366.143,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 48 8D F6 26 C7 A9 F8 B7 2E 76 31 F7 4E CB 2B… 0,,13551,0:58.367.140,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,13552,0:58.382.145,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C B3 94 99 5B D6 DC 61 D8 D4 B2 7E 31 55 7C 62… 0,,13556,0:58.383.142,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,13557,0:58.397.148,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13561,0:58.398.144,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,13562,0:58.398.148,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C B3 94 99 5B D6 DC 61 D8 D4 B2 7E 31 55 7C 62… 0,,13566,0:58.399.145,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,13567,0:58.414.150,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 32 A5 DA 10 60 62 FA 8C 51 AF CF 3E 2F 76 94… 0,,13571,0:58.415.147,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,13572,0:58.429.152,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13576,0:58.430.149,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,13577,0:58.430.152,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 32 A5 DA 10 60 62 FA 8C 51 AF CF 3E 2F 76 94… 0,,13581,0:58.431.149,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,13582,0:58.446.154,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 33 8E 69 C3 D4 86 9D 17 39 12 2C 8C DF FF 2D… 0,,13586,0:58.447.151,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,13587,0:58.461.156,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13591,0:58.462.153,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,13592,0:58.462.157,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 33 8E 69 C3 D4 86 9D 17 39 12 2C 8C DF FF 2D… 0,,13596,0:58.463.153,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,13597,0:58.478.159,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 4D E5 3E 1B 27 2E EA C6 E1 39 B3 A7 34 6F A9… 0,,13601,0:58.479.156,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,13602,0:58.493.161,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13606,0:58.494.158,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,13607,0:58.494.161,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 4D E5 3E 1B 27 2E EA C6 E1 39 B3 A7 34 6F A9… 0,,13611,0:58.495.158,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,13612,0:58.510.163,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 59 D8 DF AA FC 8F FE 17 37 9B 11 66 22 D2 15… 0,,13616,0:58.511.160,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,13617,0:58.525.165,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13621,0:58.526.162,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,13622,0:58.526.165,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 59 D8 DF AA FC 8F FE 17 37 9B 11 66 22 D2 15… 0,,13626,0:58.527.162,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,13627,0:58.542.168,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 25 4E 12 E6 27 E4 8B F1 0B 0C 19 B0 36 D7 46… 0,,13631,0:58.543.165,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,13632,0:58.557.170,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13636,0:58.558.167,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,13637,0:58.558.170,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 25 4E 12 E6 27 E4 8B F1 0B 0C 19 B0 36 D7 46… 0,,13641,0:58.559.167,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,13642,0:58.574.172,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 A8 D4 6A 58 9E A9 38 21 EE 00 02 38 B5 16 3D… 0,,13646,0:58.575.169,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,13647,0:58.589.174,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13651,0:58.590.171,16.005.041 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,13652,0:58.606.177,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 29 07 12 B0 03 62 1B 0B BA 83 50 AF A0 46 6F… 0,,13656,0:58.607.173,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,13657,0:58.621.179,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13661,0:58.622.176,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,13662,0:58.622.179,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 29 07 12 B0 03 62 1B 0B BA 83 50 AF A0 46 6F… 0,,13666,0:58.623.176,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,13667,0:58.638.181,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9D D8 48 83 BA 37 1E 1D 31 49 DE 6B 7C 07 30 47… 0,,13671,0:58.639.178,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,13672,0:58.653.183,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13676,0:58.654.180,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,13677,0:58.654.183,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9D D8 48 83 BA 37 1E 1D 31 49 DE 6B 7C 07 30 47… 0,,13681,0:58.655.180,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,13682,0:58.670.185,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 3E B3 BA 57 FE 81 18 FF 23 4C 8B F0 06 76 48… 0,,13686,0:58.671.182,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,13687,0:58.685.188,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13691,0:58.686.184,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,13692,0:58.686.188,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 3E B3 BA 57 FE 81 18 FF 23 4C 8B F0 06 76 48… 0,,13696,0:58.687.185,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,13697,0:58.702.190,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD 1C 2D 60 9E E2 FD 6A 30 61 4A 62 9B A1 95 C9… 0,,13701,0:58.703.187,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,13702,0:58.717.192,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13706,0:58.718.189,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,13707,0:58.718.192,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD 1C 2D 60 9E E2 FD 6A 30 61 4A 62 9B A1 95 C9… 0,,13711,0:58.719.189,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,13712,0:58.734.194,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EB 5C B3 F2 DD BA 0B 54 F6 C0 57 20 2C 5E 87 12… 0,,13716,0:58.735.191,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,13717,0:58.749.196,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13721,0:58.750.193,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,13722,0:58.750.197,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EB 5C B3 F2 DD BA 0B 54 F6 C0 57 20 2C 5E 87 12… 0,,13726,0:58.751.193,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,13727,0:58.766.199,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E EE D2 F6 BD 67 96 87 F7 00 6E A2 27 EB 04 BE… 0,,13731,0:58.767.196,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,13732,0:58.781.201,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13736,0:58.782.198,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,13737,0:58.782.201,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E EE D2 F6 BD 67 96 87 F7 00 6E A2 27 EB 04 BE… 0,,13741,0:58.783.198,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,13742,0:58.798.203,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 F0 73 77 31 6F 29 7B 48 7E 5E 07 7A 7D FD D2… 0,,13746,0:58.799.200,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,13747,0:58.813.205,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13751,0:58.814.202,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,13752,0:58.814.205,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 F0 73 77 31 6F 29 7B 48 7E 5E 07 7A 7D FD D2… 0,,13756,0:58.815.202,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,13757,0:58.830.208,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D 42 2F F4 9C 2E D2 B0 5C 10 96 8E 74 02 C0 3D… 0,,13761,0:58.831.205,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,13762,0:58.845.210,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13766,0:58.846.207,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,13767,0:58.846.210,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D 42 2F F4 9C 2E D2 B0 5C 10 96 8E 74 02 C0 3D… 0,,13771,0:58.847.207,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,13772,0:58.862.212,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 65 6A FA F4 E9 C5 C7 50 03 60 76 2D B6 B6 36… 0,,13776,0:58.863.209,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,13777,0:58.877.214,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13781,0:58.878.211,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,13782,0:58.878.214,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 65 6A FA F4 E9 C5 C7 50 03 60 76 2D B6 B6 36… 0,,13786,0:58.879.211,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,13787,0:58.894.217,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 AC 61 B3 38 A1 AD 0D 46 AE 56 C3 CA BA E0 2D… 0,,13791,0:58.895.213,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,13792,0:58.909.219,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13796,0:58.910.216,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,13797,0:58.910.219,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 AC 61 B3 38 A1 AD 0D 46 AE 56 C3 CA BA E0 2D… 0,,13801,0:58.911.216,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,13802,0:58.926.221,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 8F 31 EF 29 1E DC F6 A0 9F E7 38 86 E6 71 6F… 0,,13806,0:58.927.218,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,13807,0:58.941.223,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13811,0:58.942.220,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,13812,0:58.942.223,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 8F 31 EF 29 1E DC F6 A0 9F E7 38 86 E6 71 6F… 0,,13816,0:58.943.220,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,13817,0:58.958.226,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 96 55 0C EF EE 2D B6 D5 1C 30 F8 17 27 20 65… 0,,13821,0:58.959.222,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,13822,0:58.973.228,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13826,0:58.974.224,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,13827,0:58.974.228,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 96 55 0C EF EE 2D B6 D5 1C 30 F8 17 27 20 65… 0,,13831,0:58.975.225,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,13832,0:58.990.230,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FA 2A FE 82 FB D3 30 86 5A 95 3B FD 7A E6 FC 48… 0,,13836,0:58.991.227,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,13837,0:59.005.232,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13841,0:59.006.229,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,13842,0:59.006.232,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FA 2A FE 82 FB D3 30 86 5A 95 3B FD 7A E6 FC 48… 0,,13846,0:59.007.229,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,13847,0:59.022.234,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 3D E4 AC A5 8F BE 64 B4 8E 1C 8F 0D B9 CC EB… 0,,13851,0:59.023.231,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,13852,0:59.037.236,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13856,0:59.038.233,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,13857,0:59.038.237,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 3D E4 AC A5 8F BE 64 B4 8E 1C 8F 0D B9 CC EB… 0,,13861,0:59.039.233,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,13862,0:59.054.239,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B 88 B0 5B 4B B7 62 93 88 3B 9E 9A 68 C5 B8 51… 0,,13866,0:59.055.236,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,13867,0:59.069.241,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13871,0:59.070.238,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,13872,0:59.070.241,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B 88 B0 5B 4B B7 62 93 88 3B 9E 9A 68 C5 B8 51… 0,,13876,0:59.071.238,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,13877,0:59.086.243,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 E9 4F 96 CE 41 56 96 64 2C EE 3F 1F A2 C2 B4… 0,,13881,0:59.087.240,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,13882,0:59.101.245,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13886,0:59.102.242,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,13887,0:59.102.245,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 E9 4F 96 CE 41 56 96 64 2C EE 3F 1F A2 C2 B4… 0,,13891,0:59.103.242,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,13892,0:59.118.248,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F B3 21 90 58 D7 97 59 2E 9B B8 A3 FB 83 2A 14… 0,,13896,0:59.119.245,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,13897,0:59.133.250,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13901,0:59.134.247,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,13902,0:59.134.250,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F B3 21 90 58 D7 97 59 2E 9B B8 A3 FB 83 2A 14… 0,,13906,0:59.135.247,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,13907,0:59.150.252,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AF FC 6D 0D 80 1C 4B 33 F5 E8 FF 90 93 6A 9B 56… 0,,13911,0:59.151.249,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,13912,0:59.165.254,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13916,0:59.166.251,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,13917,0:59.166.254,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AF FC 6D 0D 80 1C 4B 33 F5 E8 FF 90 93 6A 9B 56… 0,,13921,0:59.167.251,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,13922,0:59.182.257,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 78 5A 3B 72 26 04 F2 56 5D B8 8B 43 05 88 0F… 0,,13926,0:59.183.253,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,13927,0:59.197.259,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13931,0:59.198.255,16.005.041 ms,,,,,[17 SOF],[Frames: 1793 - 1809] 0,,13932,0:59.214.261,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC C1 CD 25 8B 42 D1 54 51 5B 31 20 57 C1 2F AC… 0,,13936,0:59.215.258,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,13937,0:59.229.263,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13941,0:59.230.260,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,13942,0:59.230.263,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC C1 CD 25 8B 42 D1 54 51 5B 31 20 57 C1 2F AC… 0,,13946,0:59.231.260,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,13947,0:59.246.265,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 F8 9D 3B 32 9C 97 C2 84 B0 28 CE 0B F1 F9 31… 0,,13951,0:59.247.262,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,13952,0:59.261.267,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13956,0:59.262.264,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,13957,0:59.262.268,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 F8 9D 3B 32 9C 97 C2 84 B0 28 CE 0B F1 F9 31… 0,,13961,0:59.263.265,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,13962,0:59.278.270,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E E5 BB 92 B1 3D D8 C2 17 FD 99 5A 93 AA 8C 79… 0,,13966,0:59.279.267,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,13967,0:59.293.272,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13971,0:59.294.269,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,13972,0:59.294.272,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E E5 BB 92 B1 3D D8 C2 17 FD 99 5A 93 AA 8C 79… 0,,13976,0:59.295.269,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,13977,0:59.310.274,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 F0 9E 99 40 C8 B6 E6 91 2E 49 73 6A 56 DC 0C… 0,,13981,0:59.311.271,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,13982,0:59.325.276,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,13986,0:59.326.273,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,13987,0:59.326.277,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 F0 9E 99 40 C8 B6 E6 91 2E 49 73 6A 56 DC 0C… 0,,13991,0:59.327.273,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,13992,0:59.342.279,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 FC FB AD EF 9D EA C2 A9 98 AE E5 F9 01 55 9B… 0,,13996,0:59.343.276,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,13997,0:59.357.281,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14001,0:59.358.278,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,14002,0:59.358.281,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 FC FB AD EF 9D EA C2 A9 98 AE E5 F9 01 55 9B… 0,,14006,0:59.359.278,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,14007,0:59.374.283,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 FF F0 06 56 33 E4 D2 2E 41 74 94 CE 15 A5 E0… 0,,14011,0:59.375.280,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,14012,0:59.389.285,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14016,0:59.390.282,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,14017,0:59.390.285,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 FF F0 06 56 33 E4 D2 2E 41 74 94 CE 15 A5 E0… 0,,14021,0:59.391.282,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,14022,0:59.406.288,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 4D 33 94 8F 10 68 AF EA 2D DE 3B EF E2 BA F5… 0,,14026,0:59.407.285,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,14027,0:59.421.290,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14031,0:59.422.287,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,14032,0:59.422.290,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 4D 33 94 8F 10 68 AF EA 2D DE 3B EF E2 BA F5… 0,,14036,0:59.423.287,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,14037,0:59.438.292,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 00 3E D6 04 E6 DD E1 A6 51 75 3F 1B 33 85 19… 0,,14041,0:59.439.289,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,14042,0:59.453.294,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14046,0:59.454.291,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,14047,0:59.454.294,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 00 3E D6 04 E6 DD E1 A6 51 75 3F 1B 33 85 19… 0,,14051,0:59.455.291,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,14052,0:59.470.296,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 F4 1B 2E 9F 51 EB CD 16 25 19 82 65 DD 49 20… 0,,14056,0:59.471.293,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,14057,0:59.485.299,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14061,0:59.486.295,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,14062,0:59.486.299,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 F4 1B 2E 9F 51 EB CD 16 25 19 82 65 DD 49 20… 0,,14066,0:59.487.296,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,14067,0:59.502.301,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 AA 10 C9 DD E6 C5 DF 1B B5 8B 6E 62 E5 F9 91… 0,,14071,0:59.503.298,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,14072,0:59.517.303,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14076,0:59.518.300,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,14077,0:59.518.303,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 AA 10 C9 DD E6 C5 DF 1B B5 8B 6E 62 E5 F9 91… 0,,14081,0:59.519.300,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,14082,0:59.534.305,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 8B 25 E1 1F DF 7F 0F 4B FA AA 7E E9 DF 05 4F… 0,,14086,0:59.535.302,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,14087,0:59.549.307,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14091,0:59.550.304,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,14092,0:59.550.308,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 8B 25 E1 1F DF 7F 0F 4B FA AA 7E E9 DF 05 4F… 0,,14096,0:59.551.304,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,14097,0:59.566.310,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 40 7E A4 49 6E B2 75 FA 3F 2F 69 1E 36 6D B4… 0,,14101,0:59.567.307,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,14102,0:59.581.312,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14106,0:59.582.309,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,14107,0:59.582.312,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 40 7E A4 49 6E B2 75 FA 3F 2F 69 1E 36 6D B4… 0,,14111,0:59.583.309,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,14112,0:59.598.314,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 60 AA 33 F6 B2 45 81 76 60 FA D1 73 7F 16 17… 0,,14116,0:59.599.311,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,14117,0:59.613.316,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14121,0:59.614.313,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,14122,0:59.614.316,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 60 AA 33 F6 B2 45 81 76 60 FA D1 73 7F 16 17… 0,,14126,0:59.615.313,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,14127,0:59.630.319,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB 0F 98 87 94 E2 61 6B D6 7C 4B A5 5B 91 AD 93… 0,,14131,0:59.631.316,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,14132,0:59.645.321,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14136,0:59.646.318,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,14137,0:59.646.321,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB 0F 98 87 94 E2 61 6B D6 7C 4B A5 5B 91 AD 93… 0,,14141,0:59.647.318,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,14142,0:59.662.323,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6A 89 B2 10 92 FA C0 8A C5 47 1C 2E BF D3 A7… 0,,14146,0:59.663.320,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,14147,0:59.677.325,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14151,0:59.678.322,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,14152,0:59.678.325,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6A 89 B2 10 92 FA C0 8A C5 47 1C 2E BF D3 A7… 0,,14156,0:59.679.322,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,14157,0:59.694.328,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 1A E3 E5 5A B0 EA 93 EE A4 4E 8F C4 1B 10 DC… 0,,14161,0:59.695.324,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,14162,0:59.709.330,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14166,0:59.710.327,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,14167,0:59.710.330,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 1A E3 E5 5A B0 EA 93 EE A4 4E 8F C4 1B 10 DC… 0,,14171,0:59.711.327,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,14172,0:59.726.332,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 C5 C6 83 04 27 B5 55 05 EA 73 BE 1D 9C BD A1… 0,,14176,0:59.727.329,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,14177,0:59.741.334,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14181,0:59.742.331,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,14182,0:59.742.334,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 C5 C6 83 04 27 B5 55 05 EA 73 BE 1D 9C BD A1… 0,,14186,0:59.743.331,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,14187,0:59.758.336,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A D5 35 BF E1 A4 27 4B F8 E7 79 97 BE 42 DB 40… 0,,14191,0:59.759.333,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,14192,0:59.773.339,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14196,0:59.774.335,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,14197,0:59.774.339,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A D5 35 BF E1 A4 27 4B F8 E7 79 97 BE 42 DB 40… 0,,14201,0:59.775.336,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,14202,0:59.790.341,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 DA 4C D8 9C 5E 5D 41 16 B5 70 81 01 B0 A8 90… 0,,14206,0:59.791.338,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,14207,0:59.805.343,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14211,0:59.806.340,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,14212,0:59.806.343,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 DA 4C D8 9C 5E 5D 41 16 B5 70 81 01 B0 A8 90… 0,,14216,0:59.807.340,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,14217,0:59.822.345,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 6E C6 DF 08 51 55 9F 46 F1 C6 2B CE 33 F7 8D… 0,,14221,0:59.823.342,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,14222,0:59.837.347,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14226,0:59.838.344,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,14227,0:59.854.350,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 CC A5 F7 CD A5 BC EF 7E D3 2F B7 95 C9 FD CB… 0,,14231,0:59.855.347,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,14232,0:59.869.352,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14236,0:59.870.349,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,14237,0:59.870.352,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 CC A5 F7 CD A5 BC EF 7E D3 2F B7 95 C9 FD CB… 0,,14241,0:59.871.349,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,14242,0:59.886.354,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 95 34 65 17 8B BE DF A1 4F 64 5A BD A4 55 5E… 0,,14246,0:59.887.351,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,14247,0:59.901.356,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14251,0:59.902.353,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,14252,0:59.902.356,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 95 34 65 17 8B BE DF A1 4F 64 5A BD A4 55 5E… 0,,14256,0:59.903.353,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,14257,0:59.918.359,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 A5 C8 AD FD AA 19 A0 77 B7 91 C9 B7 0C 44 AF… 0,,14261,0:59.919.356,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,14262,0:59.933.361,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14266,0:59.934.358,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,14267,0:59.934.361,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 A5 C8 AD FD AA 19 A0 77 B7 91 C9 B7 0C 44 AF… 0,,14271,0:59.935.358,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,14272,0:59.950.363,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 8F 13 AC C2 39 B2 0C F5 D5 9E E4 7D C8 B5 8C… 0,,14276,0:59.951.360,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,14277,0:59.965.365,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14281,0:59.966.362,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,14282,0:59.966.365,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 8F 13 AC C2 39 B2 0C F5 D5 9E E4 7D C8 B5 8C… 0,,14286,0:59.967.362,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,14287,0:59.982.368,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 5B 36 4B 1F 41 DB 0A 21 3D 47 DF 7C 3D 0D 8E… 0,,14291,0:59.983.364,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,14292,0:59.997.370,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14296,0:59.998.367,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,14297,0:59.998.370,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 5B 36 4B 1F 41 DB 0A 21 3D 47 DF 7C 3D 0D 8E… 0,,14301,0:59.999.367,15.004.916 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,14302,1:00.014.372,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 CB 3C D5 38 B7 0A 34 87 45 B7 E2 77 06 38 88… 0,,14306,1:00.015.369,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,14307,1:00.029.374,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14311,1:00.030.371,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,14312,1:00.030.374,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 CB 3C D5 38 B7 0A 34 87 45 B7 E2 77 06 38 88… 0,,14316,1:00.031.371,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,14317,1:00.046.376,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 7C A7 C1 75 66 AB FE 8E 8A 35 3C E5 72 16 2D… 0,,14321,1:00.047.373,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,14322,1:00.061.379,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14326,1:00.062.375,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,14327,1:00.062.379,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 7C A7 C1 75 66 AB FE 8E 8A 35 3C E5 72 16 2D… 0,,14331,1:00.063.376,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,14332,1:00.078.381,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C0 94 D3 29 9D 93 91 41 1A 87 87 86 ED FF 0C 47… 0,,14336,1:00.079.378,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,14337,1:00.093.383,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14341,1:00.094.380,2.833 us,,,,,[1 SOF],[Frame: 641] 0,,14342,1:00.094.383,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C0 94 D3 29 9D 93 91 41 1A 87 87 86 ED FF 0C 47… 0,,14346,1:00.095.380,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,14347,1:00.110.385,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD 04 75 3B CE 1B 91 C5 8A 4D C1 9D 20 B1 A7 82… 0,,14351,1:00.111.382,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,14352,1:00.125.387,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14356,1:00.126.384,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,14357,1:00.126.388,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD 04 75 3B CE 1B 91 C5 8A 4D C1 9D 20 B1 A7 82… 0,,14361,1:00.127.384,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,14362,1:00.142.390,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 9B CE 5C BB CF 54 58 B5 05 2A E3 88 8D 33 05… 0,,14366,1:00.143.387,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,14367,1:00.157.392,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14371,1:00.158.389,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,14372,1:00.158.392,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 9B CE 5C BB CF 54 58 B5 05 2A E3 88 8D 33 05… 0,,14376,1:00.159.389,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,14377,1:00.174.394,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE A9 0C 64 CB 65 C8 5E 5E 0C 00 28 6C F9 8E D4… 0,,14381,1:00.175.391,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,14382,1:00.189.396,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14386,1:00.190.393,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,14387,1:00.190.396,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE A9 0C 64 CB 65 C8 5E 5E 0C 00 28 6C F9 8E D4… 0,,14391,1:00.191.393,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,14392,1:00.206.399,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 7B B5 84 19 C8 52 42 BF 8F 97 B5 C9 73 95 DC… 0,,14396,1:00.207.396,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,14397,1:00.221.401,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14401,1:00.222.398,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,14402,1:00.222.401,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 7B B5 84 19 C8 52 42 BF 8F 97 B5 C9 73 95 DC… 0,,14406,1:00.223.398,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,14407,1:00.238.403,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 E8 30 29 0B 41 D2 6D B0 10 00 33 87 6A 9C CD… 0,,14411,1:00.239.400,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,14412,1:00.253.405,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14416,1:00.254.402,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,14417,1:00.254.405,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 E8 30 29 0B 41 D2 6D B0 10 00 33 87 6A 9C CD… 0,,14421,1:00.255.402,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,14422,1:00.270.408,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 86 33 8A CE E2 E7 F9 0B E4 97 B7 B2 70 93 73… 0,,14426,1:00.271.404,14.004.750 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,14427,1:00.285.410,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14431,1:00.286.407,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,14432,1:00.286.410,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 86 33 8A CE E2 E7 F9 0B E4 97 B7 B2 70 93 73… 0,,14436,1:00.287.407,15.004.916 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,14437,1:00.302.412,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 A9 1C AC 75 DB 1E D4 81 77 FF 29 65 A3 F8 9B… 0,,14441,1:00.303.409,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,14442,1:00.317.414,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14446,1:00.318.411,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,14447,1:00.318.414,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 A9 1C AC 75 DB 1E D4 81 77 FF 29 65 A3 F8 9B… 0,,14451,1:00.319.411,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,14452,1:00.334.416,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 7E 1A 71 94 AC FF 8B 9A A4 FA 2A 31 19 BA B2… 0,,14456,1:00.335.413,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,14457,1:00.349.419,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14461,1:00.350.415,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,14462,1:00.350.419,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 7E 1A 71 94 AC FF 8B 9A A4 FA 2A 31 19 BA B2… 0,,14466,1:00.351.416,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,14467,1:00.366.421,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 57 E7 8A 23 7D 7B 9A 4D 03 99 8A 5D 48 08 D8… 0,,14471,1:00.367.418,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,14472,1:00.381.423,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14476,1:00.382.420,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,14477,1:00.382.423,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 57 E7 8A 23 7D 7B 9A 4D 03 99 8A 5D 48 08 D8… 0,,14481,1:00.383.420,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,14482,1:00.398.425,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 AD CD F1 93 1C 5D 6F AF 81 97 5E 3E BC 93 09… 0,,14486,1:00.399.422,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,14487,1:00.413.427,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14491,1:00.414.424,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,14492,1:00.414.428,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 AD CD F1 93 1C 5D 6F AF 81 97 5E 3E BC 93 09… 0,,14496,1:00.415.424,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,14497,1:00.430.430,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 3A 4A 65 5B 4B 00 47 78 01 F2 74 2F 9E 70 7A… 0,,14501,1:00.431.427,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,14502,1:00.445.432,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14506,1:00.446.429,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,14507,1:00.462.434,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 A8 5B 71 D7 90 78 2B 52 B5 A6 51 26 D7 50 33… 0,,14511,1:00.463.431,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,14512,1:00.477.436,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14516,1:00.478.433,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,14517,1:00.478.436,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 A8 5B 71 D7 90 78 2B 52 B5 A6 51 26 D7 50 33… 0,,14521,1:00.479.433,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,14522,1:00.494.439,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC DB E5 83 0B BE 60 B4 F2 AD 1D 48 06 48 F8 C0… 0,,14526,1:00.495.436,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,14527,1:00.509.441,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14531,1:00.510.438,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,14532,1:00.510.441,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC DB E5 83 0B BE 60 B4 F2 AD 1D 48 06 48 F8 C0… 0,,14536,1:00.511.438,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,14537,1:00.526.443,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 FF 94 46 AE B5 9B B7 49 0F D9 AE C4 C1 38 B6… 0,,14541,1:00.527.440,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,14542,1:00.541.445,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14546,1:00.542.442,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,14547,1:00.542.445,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 FF 94 46 AE B5 9B B7 49 0F D9 AE C4 C1 38 B6… 0,,14551,1:00.543.442,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,14552,1:00.558.448,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 49 E6 39 E3 26 AD 68 9B 47 5D CE 67 17 A0 7A… 0,,14556,1:00.559.444,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,14557,1:00.573.450,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14561,1:00.574.447,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,14562,1:00.574.450,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 49 E6 39 E3 26 AD 68 9B 47 5D CE 67 17 A0 7A… 0,,14566,1:00.575.447,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,14567,1:00.590.452,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 E1 FB 10 18 9C EC 4A A6 C6 6F 00 18 AA 61 0E… 0,,14571,1:00.591.449,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,14572,1:00.605.454,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14576,1:00.606.451,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,14577,1:00.606.454,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 E1 FB 10 18 9C EC 4A A6 C6 6F 00 18 AA 61 0E… 0,,14581,1:00.607.451,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,14582,1:00.622.456,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 77 EC B5 99 22 BE 0D 3F D7 4E 04 9F 8F 4E 4C… 0,,14586,1:00.623.453,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,14587,1:00.637.458,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14591,1:00.638.455,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,14592,1:00.638.459,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 77 EC B5 99 22 BE 0D 3F D7 4E 04 9F 8F 4E 4C… 0,,14596,1:00.639.456,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,14597,1:00.654.461,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 85 B0 96 B0 19 C8 6A B0 97 98 AB 92 79 A4 1D… 0,,14601,1:00.655.458,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,14602,1:00.669.463,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14606,1:00.670.460,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,14607,1:00.670.463,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 47 85 B0 96 B0 19 C8 6A B0 97 98 AB 92 79 A4 1D… 0,,14611,1:00.671.460,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,14612,1:00.686.465,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F D1 91 A5 A9 AF C5 91 F6 47 A9 2D 68 B4 CE 9F… 0,,14616,1:00.687.462,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,14617,1:00.701.467,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14621,1:00.702.464,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,14622,1:00.702.468,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F D1 91 A5 A9 AF C5 91 F6 47 A9 2D 68 B4 CE 9F… 0,,14626,1:00.703.464,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,14627,1:00.718.470,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 E1 03 91 F8 25 C3 B6 FC 79 C2 74 38 9C CE 45… 0,,14631,1:00.719.467,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,14632,1:00.733.472,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14636,1:00.734.469,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,14637,1:00.734.472,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 E1 03 91 F8 25 C3 B6 FC 79 C2 74 38 9C CE 45… 0,,14641,1:00.735.469,15.004.916 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,14642,1:00.750.474,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B B9 99 12 66 4A E0 4D 65 B3 10 11 7C AC 0D 92… 0,,14646,1:00.751.471,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,14647,1:00.765.476,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14651,1:00.766.473,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,14652,1:00.766.476,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B B9 99 12 66 4A E0 4D 65 B3 10 11 7C AC 0D 92… 0,,14656,1:00.767.473,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,14657,1:00.782.479,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 22 60 42 B2 B5 44 B1 D9 D2 43 01 E1 02 01 3D… 0,,14661,1:00.783.476,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,14662,1:00.797.481,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14666,1:00.798.478,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,14667,1:00.798.481,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 22 60 42 B2 B5 44 B1 D9 D2 43 01 E1 02 01 3D… 0,,14671,1:00.799.478,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,14672,1:00.814.483,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D C2 29 56 03 85 A6 C4 50 5A 83 75 E6 27 96 D8… 0,,14676,1:00.815.480,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,14677,1:00.829.485,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14681,1:00.830.482,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,14682,1:00.830.485,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D C2 29 56 03 85 A6 C4 50 5A 83 75 E6 27 96 D8… 0,,14686,1:00.831.482,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,14687,1:00.846.488,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 C0 8A 17 B0 97 51 9D 7A FB E1 84 22 98 19 DF… 0,,14691,1:00.847.484,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,14692,1:00.861.490,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14696,1:00.862.486,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,14697,1:00.862.490,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 97 C0 8A 17 B0 97 51 9D 7A FB E1 84 22 98 19 DF… 0,,14701,1:00.863.487,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,14702,1:00.878.492,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 26 00 57 A6 06 25 C8 83 6C D5 C9 4F 0E 31 2B… 0,,14706,1:00.879.489,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,14707,1:00.893.494,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14711,1:00.894.491,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,14712,1:00.894.494,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 26 00 57 A6 06 25 C8 83 6C D5 C9 4F 0E 31 2B… 0,,14716,1:00.895.491,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,14717,1:00.910.496,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 2B D1 35 C3 23 73 5A 9D 0E 91 E0 C6 D1 DE 29… 0,,14721,1:00.911.493,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,14722,1:00.925.498,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14726,1:00.926.495,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,14727,1:00.926.499,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 2B D1 35 C3 23 73 5A 9D 0E 91 E0 C6 D1 DE 29… 0,,14731,1:00.927.496,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,14732,1:00.942.501,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5D DF 3E 35 FE A1 A7 A9 12 54 04 9D 80 72 20 53… 0,,14736,1:00.943.498,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,14737,1:00.957.503,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14741,1:00.958.500,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,14742,1:00.958.503,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5D DF 3E 35 FE A1 A7 A9 12 54 04 9D 80 72 20 53… 0,,14746,1:00.959.500,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,14747,1:00.974.505,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 23 26 D9 4D E3 89 38 D6 6C 8F 1B C3 C3 46 17… 0,,14751,1:00.975.502,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,14752,1:00.989.507,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14756,1:00.990.504,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,14757,1:00.990.508,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 23 26 D9 4D E3 89 38 D6 6C 8F 1B C3 C3 46 17… 0,,14761,1:00.991.504,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,14762,1:01.006.510,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 B1 A7 B8 8F C6 A8 5B D4 91 92 76 74 4C 03 CF… 0,,14766,1:01.007.507,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,14767,1:01.021.512,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14771,1:01.022.509,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,14772,1:01.022.512,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 B1 A7 B8 8F C6 A8 5B D4 91 92 76 74 4C 03 CF… 0,,14776,1:01.023.509,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,14777,1:01.038.514,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C CB BD A7 42 6A EC 1A 21 58 4A C6 C8 3E D7 86… 0,,14781,1:01.039.511,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,14782,1:01.053.516,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14786,1:01.054.513,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,14787,1:01.054.516,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C CB BD A7 42 6A EC 1A 21 58 4A C6 C8 3E D7 86… 0,,14791,1:01.055.513,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,14792,1:01.070.519,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 47 6B 3D 7E F8 88 75 34 22 5E A4 AF EF 55 22 FA… 0,,14796,1:01.071.515,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,14797,1:01.085.521,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14801,1:01.086.518,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,14802,1:01.102.523,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 00 69 F6 C1 E2 5D 97 AB 26 85 37 F9 95 9F 61… 0,,14806,1:01.103.520,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,14807,1:01.117.525,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14811,1:01.118.522,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,14812,1:01.118.525,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 00 69 F6 C1 E2 5D 97 AB 26 85 37 F9 95 9F 61… 0,,14816,1:01.119.522,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,14817,1:01.134.527,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 3F 7D 76 7B CC 31 5D 0B 16 A3 AD 9D F4 B8 A7… 0,,14821,1:01.135.524,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,14822,1:01.149.530,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14826,1:01.150.526,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,14827,1:01.150.530,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 3F 7D 76 7B CC 31 5D 0B 16 A3 AD 9D F4 B8 A7… 0,,14831,1:01.151.527,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,14832,1:01.166.532,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 EB B8 2D CD 5E BC 97 56 59 F8 9A 22 24 9A 3A… 0,,14836,1:01.167.529,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,14837,1:01.181.534,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14841,1:01.182.531,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,14842,1:01.182.534,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 EB B8 2D CD 5E BC 97 56 59 F8 9A 22 24 9A 3A… 0,,14846,1:01.183.531,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,14847,1:01.198.536,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 ED C3 61 39 92 E3 F7 43 24 77 93 85 B1 5F 6E… 0,,14851,1:01.199.533,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,14852,1:01.213.538,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14856,1:01.214.535,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,14857,1:01.214.539,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 ED C3 61 39 92 E3 F7 43 24 77 93 85 B1 5F 6E… 0,,14861,1:01.215.535,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,14862,1:01.230.541,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E AE 84 53 38 06 8B 31 28 3B 03 6B 89 60 33 5F… 0,,14866,1:01.231.538,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,14867,1:01.245.543,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14871,1:01.246.540,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,14872,1:01.246.543,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E AE 84 53 38 06 8B 31 28 3B 03 6B 89 60 33 5F… 0,,14876,1:01.247.540,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,14877,1:01.262.545,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 2B 86 F9 F9 29 42 DF FB E5 DB A0 63 6C 01 1D… 0,,14881,1:01.263.542,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,14882,1:01.277.547,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14886,1:01.278.544,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,14887,1:01.278.548,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 2B 86 F9 F9 29 42 DF FB E5 DB A0 63 6C 01 1D… 0,,14891,1:01.279.544,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,14892,1:01.294.550,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 B8 89 67 00 AA D2 77 3F 0E 8E 37 72 61 4F B8… 0,,14896,1:01.295.547,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,14897,1:01.309.552,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14901,1:01.310.549,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,14902,1:01.310.552,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 B8 89 67 00 AA D2 77 3F 0E 8E 37 72 61 4F B8… 0,,14906,1:01.311.549,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,14907,1:01.326.554,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 E7 AB C9 E7 8B CA 69 61 00 AB A2 81 B6 05 5C… 0,,14911,1:01.327.551,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,14912,1:01.341.556,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14916,1:01.342.553,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,14917,1:01.342.556,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 E7 AB C9 E7 8B CA 69 61 00 AB A2 81 B6 05 5C… 0,,14921,1:01.343.553,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,14922,1:01.358.559,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 F2 8F 71 00 4B 5E DC D7 EC D3 A4 88 95 26 8B… 0,,14926,1:01.359.555,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,14927,1:01.373.561,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14931,1:01.374.558,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,14932,1:01.374.561,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 F2 8F 71 00 4B 5E DC D7 EC D3 A4 88 95 26 8B… 0,,14936,1:01.375.558,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,14937,1:01.390.563,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D 51 F7 D2 25 8A 93 B8 20 AF C2 4C 36 4A 35 43… 0,,14941,1:01.391.560,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,14942,1:01.405.565,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14946,1:01.406.562,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,14947,1:01.406.565,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D 51 F7 D2 25 8A 93 B8 20 AF C2 4C 36 4A 35 43… 0,,14951,1:01.407.562,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,14952,1:01.422.567,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B4 76 DF 35 FB 35 3F 49 3C 48 42 64 2C 86 2C… 0,,14956,1:01.423.564,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,14957,1:01.437.570,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14961,1:01.438.566,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,14962,1:01.438.570,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B4 76 DF 35 FB 35 3F 49 3C 48 42 64 2C 86 2C… 0,,14966,1:01.439.567,15.005.000 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,14967,1:01.454.572,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 17 0B 91 D2 11 79 4A 15 08 FC 30 DA A6 5C 6B 6A… 0,,14971,1:01.455.569,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,14972,1:01.469.574,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14976,1:01.470.571,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,14977,1:01.470.574,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 17 0B 91 D2 11 79 4A 15 08 FC 30 DA A6 5C 6B 6A… 0,,14981,1:01.471.571,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,14982,1:01.486.576,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 6C 49 70 38 03 C2 A9 AB 61 19 C8 0A 6C 0E C0… 0,,14986,1:01.487.573,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,14987,1:01.501.578,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,14991,1:01.502.575,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,14992,1:01.502.579,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 6C 49 70 38 03 C2 A9 AB 61 19 C8 0A 6C 0E C0… 0,,14996,1:01.503.575,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,14997,1:01.518.581,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C 7A 36 91 19 50 72 A5 A5 B4 45 70 C6 15 41 5C… 0,,15001,1:01.519.578,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,15002,1:01.533.583,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15006,1:01.534.580,2.833 us,,,,,[1 SOF],[Frame: 33] 0,,15007,1:01.534.583,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C 7A 36 91 19 50 72 A5 A5 B4 45 70 C6 15 41 5C… 0,,15011,1:01.535.580,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,15012,1:01.550.585,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B3 8B 4C 44 7B 44 D3 D4 1A 5D 2F 47 38 74 B1 C8… 0,,15016,1:01.551.582,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,15017,1:01.565.587,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15021,1:01.566.584,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,15022,1:01.566.587,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B3 8B 4C 44 7B 44 D3 D4 1A 5D 2F 47 38 74 B1 C8… 0,,15026,1:01.567.584,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,15027,1:01.582.590,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 98 4E AA D4 EE B6 4E 77 64 6D 33 B6 AD 90 6B 85… 0,,15031,1:01.583.587,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,15032,1:01.597.592,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15036,1:01.598.589,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,15037,1:01.598.592,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 98 4E AA D4 EE B6 4E 77 64 6D 33 B6 AD 90 6B 85… 0,,15041,1:01.599.589,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,15042,1:01.614.594,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 87 51 44 BA 92 ED 5D 20 CB A9 0B 1E E6 AC 56… 0,,15046,1:01.615.591,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,15047,1:01.629.596,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15051,1:01.630.593,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,15052,1:01.630.596,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 87 51 44 BA 92 ED 5D 20 CB A9 0B 1E E6 AC 56… 0,,15056,1:01.631.593,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,15057,1:01.646.599,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 E9 D6 59 C9 4F A5 83 BE F8 F8 9E 4C 8D 20 FB… 0,,15061,1:01.647.595,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,15062,1:01.661.601,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15066,1:01.662.598,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,15067,1:01.662.601,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 E9 D6 59 C9 4F A5 83 BE F8 F8 9E 4C 8D 20 FB… 0,,15071,1:01.663.598,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,15072,1:01.678.603,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 0A 23 44 E2 5F 80 44 2F 4E 5A 1A 66 90 A9 D2… 0,,15076,1:01.679.600,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,15077,1:01.693.605,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15081,1:01.694.602,16.005.020 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,15082,1:01.710.607,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 AA 21 83 3F BF 8F D7 AD 30 FD CA 18 F6 CF 2F… 0,,15086,1:01.711.604,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,15087,1:01.725.610,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15091,1:01.726.606,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,15092,1:01.726.610,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 AA 21 83 3F BF 8F D7 AD 30 FD CA 18 F6 CF 2F… 0,,15096,1:01.727.607,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,15097,1:01.742.612,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 89 2E 9D 28 EA 47 FD A3 D5 B2 57 F0 13 84 60… 0,,15101,1:01.743.609,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,15102,1:01.757.614,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15106,1:01.758.611,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,15107,1:01.758.614,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 89 2E 9D 28 EA 47 FD A3 D5 B2 57 F0 13 84 60… 0,,15111,1:01.759.611,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,15112,1:01.774.616,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 56 DF DC 1F 04 01 78 81 25 F3 20 45 50 44 0C… 0,,15116,1:01.775.613,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,15117,1:01.789.618,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15121,1:01.790.615,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,15122,1:01.790.619,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 56 DF DC 1F 04 01 78 81 25 F3 20 45 50 44 0C… 0,,15126,1:01.791.615,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,15127,1:01.806.621,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 6B CD 07 97 CC 80 03 B4 F1 AC D1 2D 00 A4 4E… 0,,15131,1:01.807.618,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,15132,1:01.821.623,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15136,1:01.822.620,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,15137,1:01.822.623,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 6B CD 07 97 CC 80 03 B4 F1 AC D1 2D 00 A4 4E… 0,,15141,1:01.823.620,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,15142,1:01.838.625,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 45 8B BA A6 D5 57 16 89 66 78 D1 43 8A 23 05 4A… 0,,15146,1:01.839.622,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,15147,1:01.853.627,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15151,1:01.854.624,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,15152,1:01.854.627,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 45 8B BA A6 D5 57 16 89 66 78 D1 43 8A 23 05 4A… 0,,15156,1:01.855.624,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,15157,1:01.870.630,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 EE 3E 24 12 7D 8B 6A EF C4 74 6B 1E 00 EF 5B… 0,,15161,1:01.871.627,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,15162,1:01.885.632,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15166,1:01.886.629,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,15167,1:01.886.632,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 EE 3E 24 12 7D 8B 6A EF C4 74 6B 1E 00 EF 5B… 0,,15171,1:01.887.629,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,15172,1:01.902.634,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 81 40 7C 15 08 CD C6 35 FA A7 17 62 DF BB B3 9A… 0,,15176,1:01.903.631,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,15177,1:01.917.636,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15181,1:01.918.633,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,15182,1:01.918.636,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 81 40 7C 15 08 CD C6 35 FA A7 17 62 DF BB B3 9A… 0,,15186,1:01.919.633,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,15187,1:01.934.639,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA D6 FD 6F 15 55 FA 4C FD 39 03 64 9C AD 94 27… 0,,15191,1:01.935.635,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,15192,1:01.949.641,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15196,1:01.950.638,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,15197,1:01.950.641,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA D6 FD 6F 15 55 FA 4C FD 39 03 64 9C AD 94 27… 0,,15201,1:01.951.638,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,15202,1:01.966.643,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D C2 32 30 B9 31 F9 E3 8A EF CA 24 23 AB 65 89… 0,,15206,1:01.967.640,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,15207,1:01.981.645,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15211,1:01.982.642,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,15212,1:01.982.645,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D C2 32 30 B9 31 F9 E3 8A EF CA 24 23 AB 65 89… 0,,15216,1:01.983.642,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,15217,1:01.998.647,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 68 68 B8 96 0A 4B C3 A4 62 CB CA 86 66 52 C9… 0,,15221,1:01.999.644,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,15222,1:02.013.650,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15226,1:02.014.646,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,15227,1:02.014.650,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 68 68 B8 96 0A 4B C3 A4 62 CB CA 86 66 52 C9… 0,,15231,1:02.015.647,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,15232,1:02.030.652,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 EB 57 27 C3 9B 53 59 8D 85 07 70 FA BB 61 B6… 0,,15236,1:02.031.649,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,15237,1:02.045.654,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15241,1:02.046.651,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,15242,1:02.046.654,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 EB 57 27 C3 9B 53 59 8D 85 07 70 FA BB 61 B6… 0,,15246,1:02.047.651,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,15247,1:02.062.656,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 5C 55 18 A4 B5 88 05 F1 D2 27 D8 A4 2E 11 C3… 0,,15251,1:02.063.653,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,15252,1:02.077.658,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15256,1:02.078.655,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,15257,1:02.078.659,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 5C 55 18 A4 B5 88 05 F1 D2 27 D8 A4 2E 11 C3… 0,,15261,1:02.079.655,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,15262,1:02.094.661,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 15 02 5E 98 30 CD 42 33 13 5A B8 F8 FE 84 F9 81… 0,,15266,1:02.095.658,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,15267,1:02.109.663,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15271,1:02.110.660,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,15272,1:02.110.663,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 15 02 5E 98 30 CD 42 33 13 5A B8 F8 FE 84 F9 81… 0,,15276,1:02.111.660,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,15277,1:02.126.665,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7F F0 23 4F 3D 03 3C 3E D2 2A 67 B7 26 94 DE E8… 0,,15281,1:02.127.662,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,15282,1:02.141.667,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15286,1:02.142.664,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,15287,1:02.142.667,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7F F0 23 4F 3D 03 3C 3E D2 2A 67 B7 26 94 DE E8… 0,,15291,1:02.143.664,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,15292,1:02.158.670,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 5E 34 D9 4B BA 76 12 3C E6 08 B2 A4 66 17 35… 0,,15296,1:02.159.667,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,15297,1:02.173.672,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15301,1:02.174.669,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,15302,1:02.174.672,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 5E 34 D9 4B BA 76 12 3C E6 08 B2 A4 66 17 35… 0,,15306,1:02.175.669,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,15307,1:02.190.674,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 7E 31 9C 71 1D 60 A7 F8 56 87 A7 D0 78 3F F9… 0,,15311,1:02.191.671,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,15312,1:02.205.676,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15316,1:02.206.673,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,15317,1:02.206.676,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 7E 31 9C 71 1D 60 A7 F8 56 87 A7 D0 78 3F F9… 0,,15321,1:02.207.673,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,15322,1:02.222.679,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 EB 41 21 13 A1 FE DD 3C 16 16 3E 18 EE F8 CB… 0,,15326,1:02.223.675,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,15327,1:02.237.681,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15331,1:02.238.677,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,15332,1:02.238.681,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 EB 41 21 13 A1 FE DD 3C 16 16 3E 18 EE F8 CB… 0,,15336,1:02.239.678,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,15337,1:02.254.683,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 87 F0 9C 48 E5 60 AB 95 07 79 2C 8C 17 98 0C 79… 0,,15341,1:02.255.680,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,15342,1:02.269.685,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15346,1:02.270.682,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,15347,1:02.270.685,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 87 F0 9C 48 E5 60 AB 95 07 79 2C 8C 17 98 0C 79… 0,,15351,1:02.271.682,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,15352,1:02.286.687,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E BC 64 B6 A0 F3 DA 64 B1 8B AB 9F D8 EC 44 92… 0,,15356,1:02.287.684,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,15357,1:02.301.689,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15361,1:02.302.686,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,15362,1:02.302.690,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E BC 64 B6 A0 F3 DA 64 B1 8B AB 9F D8 EC 44 92… 0,,15366,1:02.303.687,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,15367,1:02.318.692,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 D9 CD 17 D3 82 72 08 C9 E9 0D 22 8F 06 06 1D… 0,,15371,1:02.319.689,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,15372,1:02.333.694,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15376,1:02.334.691,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,15377,1:02.350.696,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A FE BD 5A 72 35 1F 8B A8 59 47 70 25 6B B7 10… 0,,15381,1:02.351.693,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,15382,1:02.365.698,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15386,1:02.366.695,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,15387,1:02.366.699,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A FE BD 5A 72 35 1F 8B A8 59 47 70 25 6B B7 10… 0,,15391,1:02.367.695,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,15392,1:02.382.701,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 A9 12 61 72 D7 E1 28 C4 BE 7B 66 10 F3 F5 1D… 0,,15396,1:02.383.698,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,15397,1:02.397.703,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15401,1:02.398.700,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,15402,1:02.398.703,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 A9 12 61 72 D7 E1 28 C4 BE 7B 66 10 F3 F5 1D… 0,,15406,1:02.399.700,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,15407,1:02.414.705,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 AC DA 91 A2 42 EA 19 91 F5 66 77 7A 0F 2B 65… 0,,15411,1:02.415.702,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,15412,1:02.429.707,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15416,1:02.430.704,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,15417,1:02.430.707,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 AC DA 91 A2 42 EA 19 91 F5 66 77 7A 0F 2B 65… 0,,15421,1:02.431.704,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,15422,1:02.446.710,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 C0 EE E5 20 A5 0F 8E 0B 44 55 32 E7 32 D7 57… 0,,15426,1:02.447.707,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,15427,1:02.461.712,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15431,1:02.462.709,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,15432,1:02.462.712,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 C0 EE E5 20 A5 0F 8E 0B 44 55 32 E7 32 D7 57… 0,,15436,1:02.463.709,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,15437,1:02.478.714,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 74 03 73 C4 7D C3 21 03 39 51 D3 E3 4C 64 B0… 0,,15441,1:02.479.711,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,15442,1:02.493.716,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15446,1:02.494.713,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,15447,1:02.494.716,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 74 03 73 C4 7D C3 21 03 39 51 D3 E3 4C 64 B0… 0,,15451,1:02.495.713,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,15452,1:02.510.719,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 4D 4A CD 19 40 D8 68 94 27 E2 E6 11 C7 26 7A… 0,,15456,1:02.511.715,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,15457,1:02.525.721,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15461,1:02.526.717,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,15462,1:02.526.721,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 4D 4A CD 19 40 D8 68 94 27 E2 E6 11 C7 26 7A… 0,,15466,1:02.527.718,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,15467,1:02.542.723,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 4B CB 9B 06 FE E2 49 12 B4 57 63 A2 FB F8 CA… 0,,15471,1:02.543.720,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,15472,1:02.557.725,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15476,1:02.558.722,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,15477,1:02.558.725,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 4B CB 9B 06 FE E2 49 12 B4 57 63 A2 FB F8 CA… 0,,15481,1:02.559.722,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,15482,1:02.574.727,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E C6 DC BD 80 6A 7C 39 C5 2C 30 D0 C7 80 69 C4… 0,,15486,1:02.575.724,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,15487,1:02.589.729,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15491,1:02.590.726,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,15492,1:02.590.730,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E C6 DC BD 80 6A 7C 39 C5 2C 30 D0 C7 80 69 C4… 0,,15496,1:02.591.726,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,15497,1:02.606.732,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 30 A8 92 05 AE E3 E2 B1 37 65 40 94 FC E8 DF… 0,,15501,1:02.607.729,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,15502,1:02.621.734,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15506,1:02.622.731,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,15507,1:02.622.734,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 30 A8 92 05 AE E3 E2 B1 37 65 40 94 FC E8 DF… 0,,15511,1:02.623.731,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,15512,1:02.638.736,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 96 69 4B 73 CC 00 69 63 B5 94 D3 AE 3D 81 83… 0,,15516,1:02.639.733,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,15517,1:02.653.738,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15521,1:02.654.735,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,15522,1:02.654.739,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 96 69 4B 73 CC 00 69 63 B5 94 D3 AE 3D 81 83… 0,,15526,1:02.655.735,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,15527,1:02.670.741,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C4 6E 8F 0E FF 4C 55 5C 0E 51 46 E8 23 E7 1B 0E… 0,,15531,1:02.671.738,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,15532,1:02.685.743,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15536,1:02.686.740,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,15537,1:02.686.743,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C4 6E 8F 0E FF 4C 55 5C 0E 51 46 E8 23 E7 1B 0E… 0,,15541,1:02.687.740,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,15542,1:02.702.745,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E1 07 D9 53 79 72 51 EB 6A B8 01 CC 2E 47 D9 9D… 0,,15546,1:02.703.742,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,15547,1:02.717.747,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15551,1:02.718.744,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,15552,1:02.718.747,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E1 07 D9 53 79 72 51 EB 6A B8 01 CC 2E 47 D9 9D… 0,,15556,1:02.719.744,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,15557,1:02.734.750,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 14 0A 66 1C 91 2A 0F ED 0E F5 39 DF 18 82 B2… 0,,15561,1:02.735.746,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,15562,1:02.749.752,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15566,1:02.750.749,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,15567,1:02.750.752,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 14 0A 66 1C 91 2A 0F ED 0E F5 39 DF 18 82 B2… 0,,15571,1:02.751.749,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,15572,1:02.766.754,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CE A8 58 E1 23 5C 1E 41 3E 46 D0 EE AC B0 14 27… 0,,15576,1:02.767.751,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,15577,1:02.781.756,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15581,1:02.782.753,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,15582,1:02.782.756,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CE A8 58 E1 23 5C 1E 41 3E 46 D0 EE AC B0 14 27… 0,,15586,1:02.783.753,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,15587,1:02.798.758,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 7B 95 D9 83 9F 40 93 63 60 D0 0D AE 31 04 C0… 0,,15591,1:02.799.755,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,15592,1:02.813.761,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15596,1:02.814.757,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,15597,1:02.814.761,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 7B 95 D9 83 9F 40 93 63 60 D0 0D AE 31 04 C0… 0,,15601,1:02.815.758,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,15602,1:02.830.763,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 D7 94 02 18 69 EA 9C C7 83 A3 7E 38 C1 4C 36… 0,,15606,1:02.831.760,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,15607,1:02.845.765,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15611,1:02.846.762,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,15612,1:02.846.765,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 D7 94 02 18 69 EA 9C C7 83 A3 7E 38 C1 4C 36… 0,,15616,1:02.847.762,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,15617,1:02.862.767,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 BB BA 54 F9 64 FF AC 6A 0F A6 CF A2 24 B2 D3… 0,,15621,1:02.863.764,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,15622,1:02.877.769,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15626,1:02.878.766,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,15627,1:02.878.770,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 BB BA 54 F9 64 FF AC 6A 0F A6 CF A2 24 B2 D3… 0,,15631,1:02.879.766,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,15632,1:02.894.772,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3D 5F 9E 1B 85 0D 43 F9 E4 C1 08 3D 17 E5 15 1D… 0,,15636,1:02.895.769,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,15637,1:02.909.774,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15641,1:02.910.771,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,15642,1:02.910.774,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3D 5F 9E 1B 85 0D 43 F9 E4 C1 08 3D 17 E5 15 1D… 0,,15646,1:02.911.771,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,15647,1:02.926.776,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 19 E0 85 16 84 E2 B4 12 80 39 8F 90 D7 3E 9E… 0,,15651,1:02.927.773,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,15652,1:02.941.778,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15656,1:02.942.775,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,15657,1:02.958.781,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 9A 1D FA 02 6E 49 4A 6A 05 84 E0 A3 67 AF 33… 0,,15661,1:02.959.778,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,15662,1:02.973.783,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15666,1:02.974.780,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,15667,1:02.974.783,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 9A 1D FA 02 6E 49 4A 6A 05 84 E0 A3 67 AF 33… 0,,15671,1:02.975.780,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,15672,1:02.990.785,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A4 CE 09 8A 58 A7 13 17 AA 5D 55 7E 74 F2 7A 29… 0,,15676,1:02.991.782,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,15677,1:03.005.787,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15681,1:03.006.784,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,15682,1:03.006.787,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A4 CE 09 8A 58 A7 13 17 AA 5D 55 7E 74 F2 7A 29… 0,,15686,1:03.007.784,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,15687,1:03.022.790,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 8C 28 96 5F 15 5B C5 7F E1 0E 21 83 5B 9A 70… 0,,15691,1:03.023.786,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,15692,1:03.037.792,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15696,1:03.038.789,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,15697,1:03.038.792,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 8C 28 96 5F 15 5B C5 7F E1 0E 21 83 5B 9A 70… 0,,15701,1:03.039.789,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,15702,1:03.054.794,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 67 62 B2 F8 6D 8A E5 B3 34 7D 0D DA 8A D8 0A… 0,,15706,1:03.055.791,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,15707,1:03.069.796,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15711,1:03.070.793,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,15712,1:03.070.796,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 67 62 B2 F8 6D 8A E5 B3 34 7D 0D DA 8A D8 0A… 0,,15716,1:03.071.793,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,15717,1:03.086.798,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F5 EF 58 F8 2C 03 56 2D AF CF 4B 02 06 38 29 49… 0,,15721,1:03.087.795,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,15722,1:03.101.801,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15726,1:03.102.797,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,15727,1:03.102.801,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F5 EF 58 F8 2C 03 56 2D AF CF 4B 02 06 38 29 49… 0,,15731,1:03.103.798,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,15732,1:03.118.803,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 43 AA DD CC F6 DD 98 60 07 8A 02 E1 DE 4F E7… 0,,15736,1:03.119.800,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,15737,1:03.133.805,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15741,1:03.134.802,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,15742,1:03.134.805,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 43 AA DD CC F6 DD 98 60 07 8A 02 E1 DE 4F E7… 0,,15746,1:03.135.802,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,15747,1:03.150.807,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 32 0D 46 52 22 B6 1B 28 2A BE 93 EB DC 14 15… 0,,15751,1:03.151.804,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,15752,1:03.165.809,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15756,1:03.166.806,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,15757,1:03.166.810,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 32 0D 46 52 22 B6 1B 28 2A BE 93 EB DC 14 15… 0,,15761,1:03.167.806,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,15762,1:03.182.812,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 3E 3D 93 18 65 92 3C AE 1D 34 91 0C 8C 9F 36… 0,,15766,1:03.183.809,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,15767,1:03.197.814,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15771,1:03.198.811,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,15772,1:03.198.814,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 3E 3D 93 18 65 92 3C AE 1D 34 91 0C 8C 9F 36… 0,,15776,1:03.199.811,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,15777,1:03.214.816,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 A4 F4 CA E6 71 1E B4 3E 02 55 58 3D 2C E4 03… 0,,15781,1:03.215.813,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,15782,1:03.229.818,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15786,1:03.230.815,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,15787,1:03.230.818,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 A4 F4 CA E6 71 1E B4 3E 02 55 58 3D 2C E4 03… 0,,15791,1:03.231.815,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,15792,1:03.246.821,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1E D2 F4 CB 98 28 F0 F5 FE 17 9A EB C3 14 F7 6F… 0,,15796,1:03.247.818,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,15797,1:03.261.823,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15801,1:03.262.820,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,15802,1:03.262.823,50.729 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1E D2 F4 CB 98 28 F0 F5 FE 17 9A EB C3 14 F7 6F… 0,,15806,1:03.263.820,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,15807,1:03.278.825,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 28 C5 A5 B3 FD 53 C7 07 D2 A3 90 C9 B8 BA B4 AE… 0,,15811,1:03.279.822,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,15812,1:03.293.827,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15816,1:03.294.824,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,15817,1:03.294.827,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 28 C5 A5 B3 FD 53 C7 07 D2 A3 90 C9 B8 BA B4 AE… 0,,15821,1:03.295.824,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,15822,1:03.310.830,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D8 95 37 7A 21 A7 F3 95 56 FD 07 97 46 5E 43 0E… 0,,15826,1:03.311.826,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,15827,1:03.325.832,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15831,1:03.326.829,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,15832,1:03.326.832,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D8 95 37 7A 21 A7 F3 95 56 FD 07 97 46 5E 43 0E… 0,,15836,1:03.327.829,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,15837,1:03.342.834,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 BB 1A C4 51 D4 AD CC 45 9B 7E B2 96 95 B6 D1… 0,,15841,1:03.343.831,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,15842,1:03.357.836,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15846,1:03.358.833,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,15847,1:03.358.836,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 BB 1A C4 51 D4 AD CC 45 9B 7E B2 96 95 B6 D1… 0,,15851,1:03.359.833,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,15852,1:03.374.838,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 B5 14 25 71 89 2D B1 BF 0F 2F 18 44 50 1D 7E… 0,,15856,1:03.375.835,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,15857,1:03.389.841,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15861,1:03.390.837,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,15862,1:03.390.841,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 B5 14 25 71 89 2D B1 BF 0F 2F 18 44 50 1D 7E… 0,,15866,1:03.391.838,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,15867,1:03.406.843,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 81 BE 30 80 EB 56 D1 91 75 59 77 33 D3 B3 05… 0,,15871,1:03.407.840,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,15872,1:03.421.845,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15876,1:03.422.842,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,15877,1:03.422.845,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 81 BE 30 80 EB 56 D1 91 75 59 77 33 D3 B3 05… 0,,15881,1:03.423.842,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,15882,1:03.438.847,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F 64 DD E4 37 00 F2 A3 8B CB 42 51 50 86 F6 F0… 0,,15886,1:03.439.844,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,15887,1:03.453.849,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15891,1:03.454.846,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,15892,1:03.454.850,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F 64 DD E4 37 00 F2 A3 8B CB 42 51 50 86 F6 F0… 0,,15896,1:03.455.846,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,15897,1:03.470.852,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 04 8D 4C F5 7F 85 F8 72 27 93 51 76 8A F4 5C… 0,,15901,1:03.471.849,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,15902,1:03.485.854,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15906,1:03.486.851,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,15907,1:03.486.854,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 04 8D 4C F5 7F 85 F8 72 27 93 51 76 8A F4 5C… 0,,15911,1:03.487.851,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,15912,1:03.502.856,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 27 DE 89 32 91 FC 59 2F 73 52 72 3B 89 77 3E… 0,,15916,1:03.503.853,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,15917,1:03.517.858,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15921,1:03.518.855,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,15922,1:03.518.859,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 27 DE 89 32 91 FC 59 2F 73 52 72 3B 89 77 3E… 0,,15926,1:03.519.855,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,15927,1:03.534.861,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 07 11 66 78 97 08 3B 8A 7C B4 C6 51 2A D7 28… 0,,15931,1:03.535.858,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,15932,1:03.549.863,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15936,1:03.550.860,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,15937,1:03.550.863,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 07 11 66 78 97 08 3B 8A 7C B4 C6 51 2A D7 28… 0,,15941,1:03.551.860,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,15942,1:03.566.865,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C CA A0 F2 21 8B B4 C0 4D 70 4F 6C 18 93 7D 46… 0,,15946,1:03.567.862,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,15947,1:03.581.867,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15951,1:03.582.864,16.005.041 ms,,,,,[17 SOF],[Frames: 33 - 49] 0,,15952,1:03.598.870,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 A8 BB 58 27 50 E8 1C A2 D1 3F 86 54 98 51 1A… 0,,15956,1:03.599.866,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,15957,1:03.613.872,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15961,1:03.614.869,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,15962,1:03.614.872,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 A8 BB 58 27 50 E8 1C A2 D1 3F 86 54 98 51 1A… 0,,15966,1:03.615.869,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,15967,1:03.630.874,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 02 9D 34 D6 7A FD 42 6E AF 0F 76 93 64 FD 30… 0,,15971,1:03.631.871,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,15972,1:03.645.876,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15976,1:03.646.873,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,15977,1:03.646.876,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 02 9D 34 D6 7A FD 42 6E AF 0F 76 93 64 FD 30… 0,,15981,1:03.647.873,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,15982,1:03.662.878,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D CA 16 BD D8 19 A6 C6 39 80 84 C6 44 41 9B 69… 0,,15986,1:03.663.875,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,15987,1:03.677.881,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,15991,1:03.678.877,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,15992,1:03.678.881,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D CA 16 BD D8 19 A6 C6 39 80 84 C6 44 41 9B 69… 0,,15996,1:03.679.878,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,15997,1:03.694.883,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 DF FF C6 42 B9 CF 7B 00 A9 B7 B7 3E B3 22 49… 0,,16001,1:03.695.880,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,16002,1:03.709.885,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16006,1:03.710.882,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,16007,1:03.710.885,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 DF FF C6 42 B9 CF 7B 00 A9 B7 B7 3E B3 22 49… 0,,16011,1:03.711.882,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,16012,1:03.726.887,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 67 2B 25 A2 FB 68 DA 16 E4 51 4A 45 76 CF 56… 0,,16016,1:03.727.884,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,16017,1:03.741.889,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16021,1:03.742.886,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,16022,1:03.742.890,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 67 2B 25 A2 FB 68 DA 16 E4 51 4A 45 76 CF 56… 0,,16026,1:03.743.886,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,16027,1:03.758.892,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 43 51 E7 62 43 A9 76 DA 40 B6 17 8D 3E C3 D6 90… 0,,16031,1:03.759.889,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,16032,1:03.773.894,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16036,1:03.774.891,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,16037,1:03.774.894,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 43 51 E7 62 43 A9 76 DA 40 B6 17 8D 3E C3 D6 90… 0,,16041,1:03.775.891,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,16042,1:03.790.896,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 E2 CD C7 09 5D C4 13 E4 63 D7 B7 D5 3A 69 E2… 0,,16046,1:03.791.893,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,16047,1:03.805.898,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16051,1:03.806.895,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,16052,1:03.806.898,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 E2 CD C7 09 5D C4 13 E4 63 D7 B7 D5 3A 69 E2… 0,,16056,1:03.807.895,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,16057,1:03.822.901,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 36 56 7F DD 07 EF 1A 17 BD B0 56 D5 93 A3 33… 0,,16061,1:03.823.898,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,16062,1:03.837.903,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16066,1:03.838.900,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,16067,1:03.838.903,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 36 56 7F DD 07 EF 1A 17 BD B0 56 D5 93 A3 33… 0,,16071,1:03.839.900,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,16072,1:03.854.905,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 6D FF 35 11 FC DA EF A3 20 F2 47 95 0E DD C6… 0,,16076,1:03.855.902,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,16077,1:03.869.907,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16081,1:03.870.904,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,16082,1:03.870.907,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 6D FF 35 11 FC DA EF A3 20 F2 47 95 0E DD C6… 0,,16086,1:03.871.904,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,16087,1:03.886.910,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 68 42 B0 9E 4E 78 96 1A A9 1E F2 CB DB 35 9F… 0,,16091,1:03.887.906,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,16092,1:03.901.912,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16096,1:03.902.908,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,16097,1:03.902.912,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 68 42 B0 9E 4E 78 96 1A A9 1E F2 CB DB 35 9F… 0,,16101,1:03.903.909,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,16102,1:03.918.914,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 19 26 82 87 70 8F FE 95 37 4D 00 AE 17 11 DF… 0,,16106,1:03.919.911,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,16107,1:03.933.916,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16111,1:03.934.913,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,16112,1:03.934.916,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 19 26 82 87 70 8F FE 95 37 4D 00 AE 17 11 DF… 0,,16116,1:03.935.913,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,16117,1:03.950.918,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A1 53 5D B6 4A E2 67 5F 84 21 01 29 67 5F 1C A2… 0,,16121,1:03.951.915,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,16122,1:03.965.920,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16126,1:03.966.917,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,16127,1:03.966.921,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A1 53 5D B6 4A E2 67 5F 84 21 01 29 67 5F 1C A2… 0,,16131,1:03.967.918,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,16132,1:03.982.923,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 C1 46 F9 09 A8 F5 D5 66 9D D5 9F 24 98 84 48… 0,,16136,1:03.983.920,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,16137,1:03.997.925,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16141,1:03.998.922,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,16142,1:03.998.925,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 C1 46 F9 09 A8 F5 D5 66 9D D5 9F 24 98 84 48… 0,,16146,1:03.999.922,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,16147,1:04.014.927,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 91 98 AD E2 C7 B3 4B 09 D9 80 39 F8 E9 D3 B6… 0,,16151,1:04.015.924,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,16152,1:04.029.929,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16156,1:04.030.926,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,16157,1:04.030.930,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 91 98 AD E2 C7 B3 4B 09 D9 80 39 F8 E9 D3 B6… 0,,16161,1:04.031.926,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,16162,1:04.046.932,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 78 DC 02 19 2B FB 86 AE D0 BC 47 9D DD A1 D0 22… 0,,16166,1:04.047.929,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,16167,1:04.061.934,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16171,1:04.062.931,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,16172,1:04.062.934,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 78 DC 02 19 2B FB 86 AE D0 BC 47 9D DD A1 D0 22… 0,,16176,1:04.063.931,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,16177,1:04.078.936,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5B C5 D2 08 35 9D 82 66 F0 EB CE 9D 2E 00 7B EF… 0,,16181,1:04.079.933,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,16182,1:04.093.938,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16186,1:04.094.935,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,16187,1:04.094.938,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5B C5 D2 08 35 9D 82 66 F0 EB CE 9D 2E 00 7B EF… 0,,16191,1:04.095.935,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,16192,1:04.110.941,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 9A F0 24 13 DF CC 45 FF 9D 3B D1 06 D6 8A EE… 0,,16196,1:04.111.938,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,16197,1:04.125.943,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16201,1:04.126.940,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,16202,1:04.126.943,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 9A F0 24 13 DF CC 45 FF 9D 3B D1 06 D6 8A EE… 0,,16206,1:04.127.940,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,16207,1:04.142.945,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 2E 67 21 35 E1 3F 58 1F 0D 26 0D 27 1C FD 63… 0,,16211,1:04.143.942,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,16212,1:04.157.947,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16216,1:04.158.944,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,16217,1:04.158.947,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 2E 67 21 35 E1 3F 58 1F 0D 26 0D 27 1C FD 63… 0,,16221,1:04.159.944,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,16222,1:04.174.949,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 35 E5 7A 44 C7 F5 C8 6C A9 4B 0D 57 1D 04 B8… 0,,16226,1:04.175.946,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,16227,1:04.189.952,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16231,1:04.190.948,16.005.041 ms,,,,,[17 SOF],[Frames: 641 - 657] 0,,16232,1:04.206.954,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6B D3 53 DD 36 C7 29 20 19 6E 73 EF 42 5E B0 83… 0,,16236,1:04.207.951,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,16237,1:04.221.956,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16241,1:04.222.953,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,16242,1:04.222.956,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6B D3 53 DD 36 C7 29 20 19 6E 73 EF 42 5E B0 83… 0,,16246,1:04.223.953,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,16247,1:04.238.958,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 40 A0 72 11 6C 73 82 D3 71 05 A3 4C 95 4A C2… 0,,16251,1:04.239.955,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,16252,1:04.253.960,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16256,1:04.254.957,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,16257,1:04.254.961,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 40 A0 72 11 6C 73 82 D3 71 05 A3 4C 95 4A C2… 0,,16261,1:04.255.957,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,16262,1:04.270.963,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 AB 3E B4 33 02 76 C1 73 9B 5E E1 1C 65 EA 5B… 0,,16266,1:04.271.960,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,16267,1:04.285.965,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16271,1:04.286.962,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,16272,1:04.286.965,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 AB 3E B4 33 02 76 C1 73 9B 5E E1 1C 65 EA 5B… 0,,16276,1:04.287.962,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,16277,1:04.302.967,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 12 1A 6B 35 2D 54 7E 80 FA 3D F0 4F E2 52 4A 15… 0,,16281,1:04.303.964,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,16282,1:04.317.969,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16286,1:04.318.966,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,16287,1:04.318.969,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 12 1A 6B 35 2D 54 7E 80 FA 3D F0 4F E2 52 4A 15… 0,,16291,1:04.319.966,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,16292,1:04.334.972,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 2F 2A A9 6C 53 56 59 99 31 67 2D E8 10 4D 56… 0,,16296,1:04.335.969,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,16297,1:04.349.974,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16301,1:04.350.971,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,16302,1:04.350.974,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 2F 2A A9 6C 53 56 59 99 31 67 2D E8 10 4D 56… 0,,16306,1:04.351.971,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,16307,1:04.366.976,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 C9 02 C9 F3 BF 6D 76 26 B8 B9 76 97 34 11 47… 0,,16311,1:04.367.973,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,16312,1:04.381.978,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16316,1:04.382.975,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,16317,1:04.382.978,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 C9 02 C9 F3 BF 6D 76 26 B8 B9 76 97 34 11 47… 0,,16321,1:04.383.975,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,16322,1:04.398.981,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 63 B5 C3 84 7A 91 C3 E7 DC 28 EA 24 2D 5C CD… 0,,16326,1:04.399.977,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,16327,1:04.413.983,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16331,1:04.414.980,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,16332,1:04.414.983,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 63 B5 C3 84 7A 91 C3 E7 DC 28 EA 24 2D 5C CD… 0,,16336,1:04.415.980,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,16337,1:04.430.985,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 28 D9 F5 6E BD DD 44 A9 AC 72 CB 22 C5 28 D9… 0,,16341,1:04.431.982,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,16342,1:04.445.987,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16346,1:04.446.984,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,16347,1:04.446.987,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 28 D9 F5 6E BD DD 44 A9 AC 72 CB 22 C5 28 D9… 0,,16351,1:04.447.984,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,16352,1:04.462.989,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 90 F6 36 CC 16 61 DB A0 3E 04 46 1D 89 67 2A C8… 0,,16356,1:04.463.986,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,16357,1:04.477.992,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16361,1:04.478.988,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,16362,1:04.478.992,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 90 F6 36 CC 16 61 DB A0 3E 04 46 1D 89 67 2A C8… 0,,16366,1:04.479.989,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,16367,1:04.494.994,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 58 BB 2B 60 9D 1E A1 9E 89 8D 47 12 62 E3 DC B7… 0,,16371,1:04.495.991,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,16372,1:04.509.996,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16376,1:04.510.993,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,16377,1:04.510.996,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 58 BB 2B 60 9D 1E A1 9E 89 8D 47 12 62 E3 DC B7… 0,,16381,1:04.511.993,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,16382,1:04.526.998,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 33 4F C2 7D 86 98 10 76 35 7F 94 76 97 C6 7B… 0,,16386,1:04.527.995,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,16387,1:04.542.000,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16391,1:04.542.997,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,16392,1:04.543.001,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 33 4F C2 7D 86 98 10 76 35 7F 94 76 97 C6 7B… 0,,16396,1:04.543.997,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,16397,1:04.559.003,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 05 E1 17 18 00 FD C0 C0 F2 0B 7C DA 3F 39 F5… 0,,16401,1:04.560.000,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,16402,1:04.574.005,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16406,1:04.575.002,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,16407,1:04.575.005,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 05 E1 17 18 00 FD C0 C0 F2 0B 7C DA 3F 39 F5… 0,,16411,1:04.576.002,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,16412,1:04.591.007,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3A A8 19 0F 41 52 78 F1 66 A6 EB 0E 9C A4 E1 EB… 0,,16416,1:04.592.004,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,16417,1:04.606.009,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16421,1:04.607.006,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,16422,1:04.607.009,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3A A8 19 0F 41 52 78 F1 66 A6 EB 0E 9C A4 E1 EB… 0,,16426,1:04.608.006,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,16427,1:04.623.012,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8D BC 57 07 DA 03 0E 75 77 34 C0 54 F4 84 B7 2A… 0,,16431,1:04.624.009,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,16432,1:04.638.014,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16436,1:04.639.011,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,16437,1:04.639.014,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8D BC 57 07 DA 03 0E 75 77 34 C0 54 F4 84 B7 2A… 0,,16441,1:04.640.011,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,16442,1:04.655.016,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 B0 08 B4 2B 1E 96 0A C8 96 4F 3D C1 02 9E 1C… 0,,16446,1:04.656.013,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,16447,1:04.670.018,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16451,1:04.671.015,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,16452,1:04.671.018,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 B0 08 B4 2B 1E 96 0A C8 96 4F 3D C1 02 9E 1C… 0,,16456,1:04.672.015,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,16457,1:04.687.021,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 25 7A 6D 21 03 BD 40 40 42 D8 8A DF 33 72 05 96… 0,,16461,1:04.688.017,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,16462,1:04.702.023,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16466,1:04.703.020,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,16467,1:04.703.023,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 25 7A 6D 21 03 BD 40 40 42 D8 8A DF 33 72 05 96… 0,,16471,1:04.704.020,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,16472,1:04.719.025,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 CE AA B6 3D 29 1F 46 87 66 FC AA 65 EA 4F E1… 0,,16476,1:04.720.022,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,16477,1:04.734.027,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16481,1:04.735.024,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,16482,1:04.735.027,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 CE AA B6 3D 29 1F 46 87 66 FC AA 65 EA 4F E1… 0,,16486,1:04.736.024,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,16487,1:04.751.029,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 BA 7C C6 3C 94 98 7F B2 01 C4 BA 66 AE F0 8E… 0,,16491,1:04.752.026,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,16492,1:04.766.032,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16496,1:04.767.028,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,16497,1:04.767.032,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 BA 7C C6 3C 94 98 7F B2 01 C4 BA 66 AE F0 8E… 0,,16501,1:04.768.029,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,16502,1:04.783.034,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C DD C6 96 D1 CA 68 AC 02 8B 0D BB 88 64 2E E9… 0,,16506,1:04.784.031,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,16507,1:04.798.036,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16511,1:04.799.033,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,16512,1:04.799.036,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C DD C6 96 D1 CA 68 AC 02 8B 0D BB 88 64 2E E9… 0,,16516,1:04.800.033,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,16517,1:04.815.038,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 36 1D 5C 09 1D 8F 9B 25 99 A0 78 DD BB DB 30… 0,,16521,1:04.816.035,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,16522,1:04.830.040,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16526,1:04.831.037,16.005.041 ms,,,,,[17 SOF],[Frames: 1281 - 1297] 0,,16527,1:04.847.043,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 6F AC 62 10 7D DB 19 4F ED 81 8D 53 7A 56 62… 0,,16531,1:04.848.040,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,16532,1:04.862.045,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16536,1:04.863.042,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,16537,1:04.863.045,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C5 6F AC 62 10 7D DB 19 4F ED 81 8D 53 7A 56 62… 0,,16541,1:04.864.042,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,16542,1:04.879.047,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C7 6E 59 BF 13 D8 D0 8C 61 21 E0 D1 EB E3 47 84… 0,,16546,1:04.880.044,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,16547,1:04.894.049,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16551,1:04.895.046,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,16552,1:04.895.049,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C7 6E 59 BF 13 D8 D0 8C 61 21 E0 D1 EB E3 47 84… 0,,16556,1:04.896.046,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,16557,1:04.911.052,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 C3 54 80 E1 3C DD E7 46 9F C8 96 76 18 C7 71… 0,,16561,1:04.912.049,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,16562,1:04.926.054,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16566,1:04.927.051,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,16567,1:04.927.054,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 C3 54 80 E1 3C DD E7 46 9F C8 96 76 18 C7 71… 0,,16571,1:04.928.051,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,16572,1:04.943.056,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 46 F8 AF BC 93 D2 A5 F0 94 46 CC E8 94 49 E1 35… 0,,16576,1:04.944.053,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,16577,1:04.958.058,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16581,1:04.959.055,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,16582,1:04.959.058,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 46 F8 AF BC 93 D2 A5 F0 94 46 CC E8 94 49 E1 35… 0,,16586,1:04.960.055,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,16587,1:04.975.061,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 FF CA D0 DD 10 DD 07 F1 5E 5A 60 E6 83 97 4F… 0,,16591,1:04.976.057,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,16592,1:04.990.063,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16596,1:04.991.060,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,16597,1:04.991.063,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 FF CA D0 DD 10 DD 07 F1 5E 5A 60 E6 83 97 4F… 0,,16601,1:04.992.060,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,16602,1:05.007.065,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C 09 CB A1 D1 9D BE F5 70 00 68 D4 BD DC 62 0B… 0,,16606,1:05.008.062,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,16607,1:05.022.067,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16611,1:05.023.064,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,16612,1:05.023.067,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C 09 CB A1 D1 9D BE F5 70 00 68 D4 BD DC 62 0B… 0,,16616,1:05.024.064,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,16617,1:05.039.069,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 F7 D8 44 05 D2 E0 FA ED 57 3E E2 04 4C 9C 83… 0,,16621,1:05.040.066,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,16622,1:05.054.072,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16626,1:05.055.068,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,16627,1:05.055.072,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 F7 D8 44 05 D2 E0 FA ED 57 3E E2 04 4C 9C 83… 0,,16631,1:05.056.069,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,16632,1:05.071.074,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 43 B7 E4 91 64 E3 7D 97 55 52 28 BD 18 8D F6… 0,,16636,1:05.072.071,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,16637,1:05.086.076,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16641,1:05.087.073,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,16642,1:05.087.076,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 43 B7 E4 91 64 E3 7D 97 55 52 28 BD 18 8D F6… 0,,16646,1:05.088.073,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,16647,1:05.103.078,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DF FE 78 73 92 02 A1 33 AF ED 9C 7C 46 7E F5 DC… 0,,16651,1:05.104.075,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,16652,1:05.118.080,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16656,1:05.119.077,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,16657,1:05.119.081,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DF FE 78 73 92 02 A1 33 AF ED 9C 7C 46 7E F5 DC… 0,,16661,1:05.120.077,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,16662,1:05.135.083,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 D5 30 FD C1 4B 46 2D B0 A8 93 C4 F8 EE B7 9F… 0,,16666,1:05.136.080,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,16667,1:05.150.085,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16671,1:05.151.082,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,16672,1:05.151.085,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 D5 30 FD C1 4B 46 2D B0 A8 93 C4 F8 EE B7 9F… 0,,16676,1:05.152.082,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,16677,1:05.167.087,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 E7 47 D8 24 F7 B7 52 4C 2A 58 EE F1 31 67 26… 0,,16681,1:05.168.084,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,16682,1:05.182.089,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16686,1:05.183.086,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,16687,1:05.183.089,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 E7 47 D8 24 F7 B7 52 4C 2A 58 EE F1 31 67 26… 0,,16691,1:05.184.086,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,16692,1:05.199.092,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 F9 A1 C2 93 3D 0D 74 CC A9 E3 63 17 A6 44 DE… 0,,16696,1:05.200.089,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,16697,1:05.214.094,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16701,1:05.215.091,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,16702,1:05.215.094,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 F9 A1 C2 93 3D 0D 74 CC A9 E3 63 17 A6 44 DE… 0,,16706,1:05.216.091,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,16707,1:05.231.096,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F6 43 62 4C F0 DD B4 9A 93 17 5D E7 CF C0 D1 C2… 0,,16711,1:05.232.093,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,16712,1:05.246.098,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16716,1:05.247.095,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,16717,1:05.247.098,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F6 43 62 4C F0 DD B4 9A 93 17 5D E7 CF C0 D1 C2… 0,,16721,1:05.248.095,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,16722,1:05.263.101,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 D6 8A 38 3B 70 C5 06 9F 4E F5 5C 23 0B 88 2A… 0,,16726,1:05.264.097,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,16727,1:05.278.103,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16731,1:05.279.100,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,16732,1:05.279.103,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 D6 8A 38 3B 70 C5 06 9F 4E F5 5C 23 0B 88 2A… 0,,16736,1:05.280.100,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,16737,1:05.295.105,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 8E 51 74 85 A3 D4 23 9B 79 65 75 E7 1C 62 B8… 0,,16741,1:05.296.102,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,16742,1:05.310.107,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16746,1:05.311.104,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,16747,1:05.311.107,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 8E 51 74 85 A3 D4 23 9B 79 65 75 E7 1C 62 B8… 0,,16751,1:05.312.104,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,16752,1:05.327.109,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 54 DE 2E 46 B4 52 CD F0 D3 5D 8C 49 50 65 5B 48… 0,,16756,1:05.328.106,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,16757,1:05.342.111,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16761,1:05.343.108,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,16762,1:05.343.112,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 54 DE 2E 46 B4 52 CD F0 D3 5D 8C 49 50 65 5B 48… 0,,16766,1:05.344.109,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,16767,1:05.359.114,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D F7 45 A4 E2 87 A9 62 D9 7D E0 E5 FA 8F FE 09… 0,,16771,1:05.360.111,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,16772,1:05.374.116,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16776,1:05.375.113,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,16777,1:05.375.116,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D F7 45 A4 E2 87 A9 62 D9 7D E0 E5 FA 8F FE 09… 0,,16781,1:05.376.113,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,16782,1:05.391.118,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 E8 4A D4 D8 49 15 8A 46 0A 83 B3 07 9A 08 91… 0,,16786,1:05.392.115,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,16787,1:05.406.120,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16791,1:05.407.117,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,16792,1:05.407.121,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 E8 4A D4 D8 49 15 8A 46 0A 83 B3 07 9A 08 91… 0,,16796,1:05.408.117,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,16797,1:05.423.123,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 B7 C5 E8 41 51 AC 7C 6C F0 B7 5B 08 3E 4F B0… 0,,16801,1:05.424.120,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,16802,1:05.438.125,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16806,1:05.439.122,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,16807,1:05.455.127,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 76 53 16 70 52 72 DE BA 90 BA 26 5A 8E BC A3 4B… 0,,16811,1:05.456.124,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,16812,1:05.470.129,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16816,1:05.471.126,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,16817,1:05.471.129,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 76 53 16 70 52 72 DE BA 90 BA 26 5A 8E BC A3 4B… 0,,16821,1:05.472.126,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,16822,1:05.487.132,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EA BD A1 09 F4 93 1B 18 92 E2 AA B3 FB 59 13 58… 0,,16826,1:05.488.129,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,16827,1:05.502.134,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16831,1:05.503.131,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,16832,1:05.503.134,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EA BD A1 09 F4 93 1B 18 92 E2 AA B3 FB 59 13 58… 0,,16836,1:05.504.131,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,16837,1:05.519.136,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF F4 A6 18 88 5E 35 53 BD 0F EC 8E 97 F0 B0 81… 0,,16841,1:05.520.133,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,16842,1:05.534.138,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16846,1:05.535.135,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,16847,1:05.535.138,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF F4 A6 18 88 5E 35 53 BD 0F EC 8E 97 F0 B0 81… 0,,16851,1:05.536.135,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,16852,1:05.551.141,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 DE E0 EB 28 99 6E 7F 82 60 E3 3D BA 21 E2 27… 0,,16856,1:05.552.137,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,16857,1:05.566.143,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16861,1:05.567.139,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,16862,1:05.567.143,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 DE E0 EB 28 99 6E 7F 82 60 E3 3D BA 21 E2 27… 0,,16866,1:05.568.140,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,16867,1:05.583.145,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 4E 32 0D FB B1 87 D4 E4 F2 18 8E B7 17 DD AE… 0,,16871,1:05.584.142,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,16872,1:05.598.147,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16876,1:05.599.144,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,16877,1:05.599.147,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 4E 32 0D FB B1 87 D4 E4 F2 18 8E B7 17 DD AE… 0,,16881,1:05.600.144,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,16882,1:05.615.149,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C C7 76 8B B0 4A B1 C7 6A CC 88 93 AE 16 3D 67… 0,,16886,1:05.616.146,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,16887,1:05.630.151,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16891,1:05.631.148,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,16892,1:05.631.152,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C C7 76 8B B0 4A B1 C7 6A CC 88 93 AE 16 3D 67… 0,,16896,1:05.632.149,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,16897,1:05.647.154,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 FC E7 D0 33 01 36 18 BD 52 C9 2B 1A 1D F1 7B… 0,,16901,1:05.648.151,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,16902,1:05.662.156,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16906,1:05.663.153,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,16907,1:05.663.156,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 FC E7 D0 33 01 36 18 BD 52 C9 2B 1A 1D F1 7B… 0,,16911,1:05.664.153,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,16912,1:05.679.158,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 95 F1 A9 EA 73 92 80 5C 9A 57 16 FB 78 87 F3 4A… 0,,16916,1:05.680.155,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,16917,1:05.694.160,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16921,1:05.695.157,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,16922,1:05.695.160,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 95 F1 A9 EA 73 92 80 5C 9A 57 16 FB 78 87 F3 4A… 0,,16926,1:05.696.157,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,16927,1:05.711.163,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 50 F0 8B 82 AA 4A AF C4 3A 9C 3C A0 44 25 21… 0,,16931,1:05.712.160,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,16932,1:05.726.165,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16936,1:05.727.162,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,16937,1:05.727.165,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 50 F0 8B 82 AA 4A AF C4 3A 9C 3C A0 44 25 21… 0,,16941,1:05.728.162,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,16942,1:05.743.167,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE DA 18 41 D9 64 DC 35 05 06 B6 DF C7 8A A2 61… 0,,16946,1:05.744.164,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,16947,1:05.758.169,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16951,1:05.759.166,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,16952,1:05.759.169,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE DA 18 41 D9 64 DC 35 05 06 B6 DF C7 8A A2 61… 0,,16956,1:05.760.166,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,16957,1:05.775.172,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C3 AB D2 9F 6E A0 88 E4 CA 05 86 54 AE 89 32 43… 0,,16961,1:05.776.168,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,16962,1:05.790.174,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16966,1:05.791.171,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,16967,1:05.791.174,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C3 AB D2 9F 6E A0 88 E4 CA 05 86 54 AE 89 32 43… 0,,16971,1:05.792.171,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,16972,1:05.807.176,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 97 6B 0D EC 05 54 26 67 95 DD 53 5D 3C 31 F1… 0,,16976,1:05.808.173,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,16977,1:05.822.178,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16981,1:05.823.175,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,16982,1:05.823.178,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 97 6B 0D EC 05 54 26 67 95 DD 53 5D 3C 31 F1… 0,,16986,1:05.824.175,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,16987,1:05.839.180,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 04 F3 DE DD 31 E6 97 11 89 92 25 82 DA 1E 1B… 0,,16991,1:05.840.177,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,16992,1:05.854.183,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,16996,1:05.855.179,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,16997,1:05.855.183,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 04 F3 DE DD 31 E6 97 11 89 92 25 82 DA 1E 1B… 0,,17001,1:05.856.180,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,17002,1:05.871.185,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FD 03 90 EE 8B 24 2C F3 EA 53 CF 9F 2A A4 B5 B6… 0,,17006,1:05.872.182,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,17007,1:05.886.187,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17011,1:05.887.184,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,17012,1:05.887.187,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FD 03 90 EE 8B 24 2C F3 EA 53 CF 9F 2A A4 B5 B6… 0,,17016,1:05.888.184,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,17017,1:05.903.189,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C A8 60 EB 4C D1 74 0A DD 6A 16 B1 8F CA BF 5D… 0,,17021,1:05.904.186,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,17022,1:05.918.191,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17026,1:05.919.188,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,17027,1:05.919.192,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C A8 60 EB 4C D1 74 0A DD 6A 16 B1 8F CA BF 5D… 0,,17031,1:05.920.188,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,17032,1:05.935.194,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 76 9F 28 0E B4 FE 34 96 42 2F 20 E4 2E 3E C3… 0,,17036,1:05.936.191,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,17037,1:05.950.196,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17041,1:05.951.193,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,17042,1:05.951.196,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 76 9F 28 0E B4 FE 34 96 42 2F 20 E4 2E 3E C3… 0,,17046,1:05.952.193,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,17047,1:05.967.198,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D0 BA A2 13 AF DF 3E 80 AB 88 C2 F7 78 45 AA B5… 0,,17051,1:05.968.195,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,17052,1:05.982.200,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17056,1:05.983.197,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,17057,1:05.983.200,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D0 BA A2 13 AF DF 3E 80 AB 88 C2 F7 78 45 AA B5… 0,,17061,1:05.984.197,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,17062,1:05.999.203,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 10 02 58 A6 E5 D0 28 0F 48 CF 49 65 E3 B0 66… 0,,17066,1:06.000.200,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,17067,1:06.014.205,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17071,1:06.015.202,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,17072,1:06.015.205,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 10 02 58 A6 E5 D0 28 0F 48 CF 49 65 E3 B0 66… 0,,17076,1:06.016.202,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,17077,1:06.031.207,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 57 A0 4F F8 97 12 9A DA 47 70 4E F6 FC 84 63… 0,,17081,1:06.032.204,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,17082,1:06.046.209,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17086,1:06.047.206,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,17087,1:06.047.209,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 57 A0 4F F8 97 12 9A DA 47 70 4E F6 FC 84 63… 0,,17091,1:06.048.206,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,17092,1:06.063.212,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D BC 07 94 71 65 5C BA 46 73 97 98 20 41 C5 FA… 0,,17096,1:06.064.208,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,17097,1:06.078.214,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17101,1:06.079.211,16.005.041 ms,,,,,[17 SOF],[Frames: 481 - 497] 0,,17102,1:06.095.216,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 67 B1 8F 38 2F DB 76 8C 52 26 3E 5B AC 8A F4… 0,,17106,1:06.096.213,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,17107,1:06.110.218,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17111,1:06.111.215,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,17112,1:06.111.218,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 67 B1 8F 38 2F DB 76 8C 52 26 3E 5B AC 8A F4… 0,,17116,1:06.112.215,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,17117,1:06.127.220,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 53 B6 58 13 27 F2 14 6F 48 1A FF 78 6A 26 03… 0,,17121,1:06.128.217,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,17122,1:06.142.223,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17126,1:06.143.219,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,17127,1:06.143.223,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 53 B6 58 13 27 F2 14 6F 48 1A FF 78 6A 26 03… 0,,17131,1:06.144.220,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,17132,1:06.159.225,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B BD CD 61 06 AF 73 47 01 17 A9 5D 0A 56 99 B8… 0,,17136,1:06.160.222,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,17137,1:06.174.227,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17141,1:06.175.224,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,17142,1:06.175.227,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B BD CD 61 06 AF 73 47 01 17 A9 5D 0A 56 99 B8… 0,,17146,1:06.176.224,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,17147,1:06.191.229,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 CB 27 64 A0 A0 EE C1 2D 2F 44 B2 E0 C1 2B EC… 0,,17151,1:06.192.226,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,17152,1:06.206.231,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17156,1:06.207.228,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,17157,1:06.207.232,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 CB 27 64 A0 A0 EE C1 2D 2F 44 B2 E0 C1 2B EC… 0,,17161,1:06.208.228,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,17162,1:06.223.234,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE C2 4A C2 FA 11 7E AE 12 60 05 1A D8 2E 0B F5… 0,,17166,1:06.224.231,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,17167,1:06.238.236,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17171,1:06.239.233,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,17172,1:06.239.236,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE C2 4A C2 FA 11 7E AE 12 60 05 1A D8 2E 0B F5… 0,,17176,1:06.240.233,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,17177,1:06.255.238,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 2E F6 D8 80 A8 C2 8B A5 C2 96 3F 3B C6 8A CE… 0,,17181,1:06.256.235,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,17182,1:06.270.240,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17186,1:06.271.237,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,17187,1:06.271.240,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 2E F6 D8 80 A8 C2 8B A5 C2 96 3F 3B C6 8A CE… 0,,17191,1:06.272.237,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,17192,1:06.287.243,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 85 59 4A FA 12 1F DD 24 5F F3 1C CD 17 FF 15… 0,,17196,1:06.288.240,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,17197,1:06.302.245,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17201,1:06.303.242,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,17202,1:06.303.245,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 85 59 4A FA 12 1F DD 24 5F F3 1C CD 17 FF 15… 0,,17206,1:06.304.242,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,17207,1:06.319.247,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 47 D7 4A 0E D1 E6 51 B8 E0 70 08 56 0D 46 8C… 0,,17211,1:06.320.244,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,17212,1:06.334.249,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17216,1:06.335.246,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,17217,1:06.335.249,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 47 D7 4A 0E D1 E6 51 B8 E0 70 08 56 0D 46 8C… 0,,17221,1:06.336.246,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,17222,1:06.351.252,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C6 E5 46 FF F1 DE 45 01 CB 2B 51 83 F1 40 FF BA… 0,,17226,1:06.352.248,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,17227,1:06.366.254,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17231,1:06.367.251,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,17232,1:06.367.254,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C6 E5 46 FF F1 DE 45 01 CB 2B 51 83 F1 40 FF BA… 0,,17236,1:06.368.251,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,17237,1:06.383.256,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E 71 F2 21 96 C6 35 E0 27 00 D9 9A 8D DD 55 CD… 0,,17241,1:06.384.253,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,17242,1:06.398.258,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17246,1:06.399.255,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,17247,1:06.399.258,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E 71 F2 21 96 C6 35 E0 27 00 D9 9A 8D DD 55 CD… 0,,17251,1:06.400.255,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,17252,1:06.415.260,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 27 5E 81 10 BC CA 3F 77 40 6B 81 74 A2 C2 7B 1B… 0,,17256,1:06.416.257,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,17257,1:06.430.263,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17261,1:06.431.259,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,17262,1:06.431.263,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 27 5E 81 10 BC CA 3F 77 40 6B 81 74 A2 C2 7B 1B… 0,,17266,1:06.432.260,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,17267,1:06.447.265,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 9F EF C6 D6 BA 2B 8D 03 CD E0 FA 22 EF C5 46… 0,,17271,1:06.448.262,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,17272,1:06.462.267,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17276,1:06.463.264,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,17277,1:06.463.267,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 9F EF C6 D6 BA 2B 8D 03 CD E0 FA 22 EF C5 46… 0,,17281,1:06.464.264,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,17282,1:06.479.269,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 1E 3F 5F CB D3 EA 1C D2 AC 00 A4 52 9A 1F F4… 0,,17286,1:06.480.266,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,17287,1:06.494.271,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17291,1:06.495.268,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,17292,1:06.495.272,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 1E 3F 5F CB D3 EA 1C D2 AC 00 A4 52 9A 1F F4… 0,,17296,1:06.496.268,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,17297,1:06.511.274,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 8F 1D C2 3A E2 66 AA 46 80 C1 F5 E4 96 BF B8… 0,,17301,1:06.512.271,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,17302,1:06.526.276,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17306,1:06.527.273,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,17307,1:06.527.276,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 8F 1D C2 3A E2 66 AA 46 80 C1 F5 E4 96 BF B8… 0,,17311,1:06.528.273,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,17312,1:06.543.278,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 BB 1F 5C 0A 7E 97 A3 0E 33 66 3E 8F A8 A6 EF… 0,,17316,1:06.544.275,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,17317,1:06.558.280,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17321,1:06.559.277,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,17322,1:06.559.280,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 BB 1F 5C 0A 7E 97 A3 0E 33 66 3E 8F A8 A6 EF… 0,,17326,1:06.560.277,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,17327,1:06.575.283,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 D8 18 51 38 D4 05 B2 C6 B5 D4 5B 2F BD 3C CC… 0,,17331,1:06.576.280,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,17332,1:06.590.285,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17336,1:06.591.282,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,17337,1:06.591.285,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 D8 18 51 38 D4 05 B2 C6 B5 D4 5B 2F BD 3C CC… 0,,17341,1:06.592.282,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,17342,1:06.607.287,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E8 6C 70 32 30 7D 5E 85 99 42 63 50 8C DC 98 71… 0,,17346,1:06.608.284,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,17347,1:06.622.289,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17351,1:06.623.286,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,17352,1:06.623.289,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E8 6C 70 32 30 7D 5E 85 99 42 63 50 8C DC 98 71… 0,,17356,1:06.624.286,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,17357,1:06.639.292,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 B2 3B 82 0F 4C DA C0 0F 88 5E D9 D2 B1 9E 00… 0,,17361,1:06.640.288,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,17362,1:06.654.294,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17366,1:06.655.291,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,17367,1:06.655.294,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 B2 3B 82 0F 4C DA C0 0F 88 5E D9 D2 B1 9E 00… 0,,17371,1:06.656.291,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,17372,1:06.671.296,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 BD 54 2C E8 99 16 2D 1D EA C6 9D BC A2 FA 65… 0,,17376,1:06.672.293,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,17377,1:06.686.298,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17381,1:06.687.295,16.005.041 ms,,,,,[17 SOF],[Frames: 1089 - 1105] 0,,17382,1:06.703.300,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 20 9F 6D 57 E5 AE 21 90 1D 25 4F 1E FC 1B 9A… 0,,17386,1:06.704.297,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,17387,1:06.718.303,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17391,1:06.719.299,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,17392,1:06.719.303,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 20 9F 6D 57 E5 AE 21 90 1D 25 4F 1E FC 1B 9A… 0,,17396,1:06.720.300,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,17397,1:06.735.305,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 1A 3A 59 EF 4B 52 5C D8 B9 EB 7B D4 BC 5E BF… 0,,17401,1:06.736.302,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,17402,1:06.750.307,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17406,1:06.751.304,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,17407,1:06.751.307,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 1A 3A 59 EF 4B 52 5C D8 B9 EB 7B D4 BC 5E BF… 0,,17411,1:06.752.304,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,17412,1:06.767.309,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 89 84 26 D2 DF 13 C3 E4 DD 5A D9 03 82 CD 27… 0,,17416,1:06.768.306,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,17417,1:06.782.311,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17421,1:06.783.308,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,17422,1:06.783.312,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 89 84 26 D2 DF 13 C3 E4 DD 5A D9 03 82 CD 27… 0,,17426,1:06.784.308,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,17427,1:06.799.314,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A9 7B 27 66 6F D3 28 CA F7 16 44 9A 61 EE 15 22… 0,,17431,1:06.800.311,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,17432,1:06.814.316,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17436,1:06.815.313,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,17437,1:06.815.316,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A9 7B 27 66 6F D3 28 CA F7 16 44 9A 61 EE 15 22… 0,,17441,1:06.816.313,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,17442,1:06.831.318,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 44 EC 00 46 AD D7 1C F9 C9 43 F7 FE 62 39 AD 0E… 0,,17446,1:06.832.315,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,17447,1:06.846.320,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17451,1:06.847.317,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,17452,1:06.847.320,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 44 EC 00 46 AD D7 1C F9 C9 43 F7 FE 62 39 AD 0E… 0,,17456,1:06.848.317,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,17457,1:06.863.323,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 C7 1A AF 5F 25 11 39 12 E3 60 F6 EA EE 34 0A… 0,,17461,1:06.864.320,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,17462,1:06.878.325,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17466,1:06.879.322,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,17467,1:06.879.325,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 C7 1A AF 5F 25 11 39 12 E3 60 F6 EA EE 34 0A… 0,,17471,1:06.880.322,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,17472,1:06.895.327,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 0E 9B 14 35 B0 1F A0 9C 8A 3B 22 E1 98 39 D8… 0,,17476,1:06.896.324,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,17477,1:06.910.329,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17481,1:06.911.326,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,17482,1:06.911.329,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC 0E 9B 14 35 B0 1F A0 9C 8A 3B 22 E1 98 39 D8… 0,,17486,1:06.912.326,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,17487,1:06.927.332,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 C5 AA 25 6E 47 1E F9 FE BD 17 43 BC CC 2B F1… 0,,17491,1:06.928.328,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,17492,1:06.942.334,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17496,1:06.943.330,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,17497,1:06.943.334,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 C5 AA 25 6E 47 1E F9 FE BD 17 43 BC CC 2B F1… 0,,17501,1:06.944.331,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,17502,1:06.959.336,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 4D 6C 3A CB D1 1D 1F C7 12 F3 96 8A B3 D4 0D… 0,,17506,1:06.960.333,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,17507,1:06.974.338,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17511,1:06.975.335,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,17512,1:06.975.338,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 4D 6C 3A CB D1 1D 1F C7 12 F3 96 8A B3 D4 0D… 0,,17516,1:06.976.335,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,17517,1:06.991.340,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C DE 8F 01 AB 23 8F 10 8C E4 82 88 CE F1 56 77… 0,,17521,1:06.992.337,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,17522,1:07.006.342,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17526,1:07.007.339,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,17527,1:07.007.343,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C DE 8F 01 AB 23 8F 10 8C E4 82 88 CE F1 56 77… 0,,17531,1:07.008.340,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,17532,1:07.023.345,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 2C 8A 61 81 4B 0E 14 AD 3A E8 0C B5 94 C3 D8… 0,,17536,1:07.024.342,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,17537,1:07.038.347,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17541,1:07.039.344,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,17542,1:07.039.347,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 2C 8A 61 81 4B 0E 14 AD 3A E8 0C B5 94 C3 D8… 0,,17546,1:07.040.344,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,17547,1:07.055.349,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 B1 D0 5A C7 69 B1 1B EC 29 34 78 82 37 D2 00… 0,,17551,1:07.056.346,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,17552,1:07.070.351,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17556,1:07.071.348,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,17557,1:07.071.352,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 B1 D0 5A C7 69 B1 1B EC 29 34 78 82 37 D2 00… 0,,17561,1:07.072.348,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,17562,1:07.087.354,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2E 18 C8 AA F2 A0 14 A3 09 56 62 51 CD 88 73 7D… 0,,17566,1:07.088.351,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,17567,1:07.102.356,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17571,1:07.103.353,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,17572,1:07.103.356,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2E 18 C8 AA F2 A0 14 A3 09 56 62 51 CD 88 73 7D… 0,,17576,1:07.104.353,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,17577,1:07.119.358,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 C4 C4 21 3A 83 97 7C 45 48 E1 AC 4B CC DD BF… 0,,17581,1:07.120.355,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,17582,1:07.134.360,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17586,1:07.135.357,2.833 us,,,,,[1 SOF],[Frame: 1537] 0,,17587,1:07.135.360,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 C4 C4 21 3A 83 97 7C 45 48 E1 AC 4B CC DD BF… 0,,17591,1:07.136.357,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,17592,1:07.151.363,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 28 E0 C0 43 9B DC 36 22 67 D3 0E E8 D3 ED ED… 0,,17596,1:07.152.360,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,17597,1:07.166.365,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17601,1:07.167.362,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,17602,1:07.167.365,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 28 E0 C0 43 9B DC 36 22 67 D3 0E E8 D3 ED ED… 0,,17606,1:07.168.362,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,17607,1:07.183.367,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 97 F6 92 F5 DE FB 31 80 32 75 61 BD DD 65 54… 0,,17611,1:07.184.364,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,17612,1:07.198.369,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17616,1:07.199.366,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,17617,1:07.199.369,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 97 F6 92 F5 DE FB 31 80 32 75 61 BD DD 65 54… 0,,17621,1:07.200.366,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,17622,1:07.215.371,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CE 1C B9 AE 4D B4 B0 D4 21 E4 80 EA 5B E6 51 9B… 0,,17626,1:07.216.368,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,17627,1:07.230.374,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17631,1:07.231.370,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,17632,1:07.231.374,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CE 1C B9 AE 4D B4 B0 D4 21 E4 80 EA 5B E6 51 9B… 0,,17636,1:07.232.371,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,17637,1:07.247.376,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 9E A6 BB 81 4E AE 01 72 F2 74 1E 5E 0D 92 7E… 0,,17641,1:07.248.373,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,17642,1:07.262.378,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17646,1:07.263.375,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,17647,1:07.263.378,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 9E A6 BB 81 4E AE 01 72 F2 74 1E 5E 0D 92 7E… 0,,17651,1:07.264.375,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,17652,1:07.279.380,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 2A 13 44 49 11 59 D8 26 E4 97 5D 70 79 8B 72… 0,,17656,1:07.280.377,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,17657,1:07.294.382,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17661,1:07.295.379,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,17662,1:07.295.383,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 2A 13 44 49 11 59 D8 26 E4 97 5D 70 79 8B 72… 0,,17666,1:07.296.379,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,17667,1:07.311.385,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17671,1:07.312.382,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,17672,1:07.326.387,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17676,1:07.327.384,16.005.041 ms,,,,,[17 SOF],[Frames: 1729 - 1745] 0,,17677,1:07.343.389,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17681,1:07.344.386,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,17682,1:07.358.391,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17686,1:07.359.388,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,17687,1:07.359.392,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4D 43 F9 BE AD 1A 48 54 FE 9E 63 E9 F2 60 C8 C6… 0,,17691,1:07.360.388,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,17692,1:07.375.394,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4A 2F 48 F7 B8 48 C5 82 7D 43 2B B0 7B B6 F8 CD… 0,,17696,1:07.376.391,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,17697,1:07.390.396,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17701,1:07.391.393,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,17702,1:07.391.396,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4A 2F 48 F7 B8 48 C5 82 7D 43 2B B0 7B B6 F8 CD… 0,,17706,1:07.392.393,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,17707,1:07.407.398,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 30 E5 CF 47 05 27 B9 C1 EE 30 AC CA 5D 50 09… 0,,17711,1:07.408.395,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,17712,1:07.422.400,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17716,1:07.423.397,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,17717,1:07.423.400,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 30 E5 CF 47 05 27 B9 C1 EE 30 AC CA 5D 50 09… 0,,17721,1:07.424.397,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,17722,1:07.439.403,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 73 1C F0 5A 69 AE E7 E7 8E 9F 3E 6D 41 4F B6… 0,,17726,1:07.440.399,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,17727,1:07.454.405,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17731,1:07.455.402,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,17732,1:07.455.405,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 73 1C F0 5A 69 AE E7 E7 8E 9F 3E 6D 41 4F B6… 0,,17736,1:07.456.402,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,17737,1:07.471.407,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 FF 6F FB 95 C4 A0 A6 0E 3E 47 15 5B 7D A4 7C… 0,,17741,1:07.472.404,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,17742,1:07.486.409,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17746,1:07.487.406,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,17747,1:07.487.409,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 FF 6F FB 95 C4 A0 A6 0E 3E 47 15 5B 7D A4 7C… 0,,17751,1:07.488.406,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,17752,1:07.503.411,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C B0 B0 D0 6E 80 14 51 A7 23 F1 75 5F 86 45 17… 0,,17756,1:07.504.408,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,17757,1:07.518.414,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17761,1:07.519.410,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,17762,1:07.519.414,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C B0 B0 D0 6E 80 14 51 A7 23 F1 75 5F 86 45 17… 0,,17766,1:07.520.411,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,17767,1:07.535.416,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 07 06 70 1E 70 9F 21 13 5B B2 95 35 04 CC BF BC… 0,,17771,1:07.536.413,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,17772,1:07.550.418,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17776,1:07.551.415,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,17777,1:07.551.418,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 07 06 70 1E 70 9F 21 13 5B B2 95 35 04 CC BF BC… 0,,17781,1:07.552.415,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,17782,1:07.567.420,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 34 03 10 11 8F 0B D2 EA D2 A6 51 90 03 A2 03… 0,,17786,1:07.568.417,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,17787,1:07.582.422,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17791,1:07.583.419,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,17792,1:07.583.423,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 34 03 10 11 8F 0B D2 EA D2 A6 51 90 03 A2 03… 0,,17796,1:07.584.419,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,17797,1:07.599.425,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9F E9 85 F6 72 F2 8C 4F 2B 63 FA D1 38 D8 2C 42… 0,,17801,1:07.600.422,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,17802,1:07.614.427,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17806,1:07.615.424,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,17807,1:07.615.427,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9F E9 85 F6 72 F2 8C 4F 2B 63 FA D1 38 D8 2C 42… 0,,17811,1:07.616.424,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,17812,1:07.631.429,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD E1 68 01 65 83 25 59 78 5B 6D 73 D7 32 2D BA… 0,,17816,1:07.632.426,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,17817,1:07.646.431,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17821,1:07.647.428,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,17822,1:07.647.431,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD E1 68 01 65 83 25 59 78 5B 6D 73 D7 32 2D BA… 0,,17826,1:07.648.428,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,17827,1:07.663.434,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5E BD CF 63 62 71 AE 5D B8 87 FE C3 DB 5D FB 42… 0,,17831,1:07.664.431,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,17832,1:07.678.436,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17836,1:07.679.433,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,17837,1:07.679.436,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5E BD CF 63 62 71 AE 5D B8 87 FE C3 DB 5D FB 42… 0,,17841,1:07.680.433,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,17842,1:07.695.438,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 27 70 D2 A0 C8 47 8F 99 51 DF 86 0D 0F B8 85… 0,,17846,1:07.696.435,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,17847,1:07.710.440,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17851,1:07.711.437,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,17852,1:07.711.440,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 27 70 D2 A0 C8 47 8F 99 51 DF 86 0D 0F B8 85… 0,,17856,1:07.712.437,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,17857,1:07.727.443,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 2D 06 DA 2A 83 D6 C9 BD 81 6F DB 56 92 F6 30… 0,,17861,1:07.728.439,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,17862,1:07.742.445,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17866,1:07.743.442,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,17867,1:07.743.445,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 2D 06 DA 2A 83 D6 C9 BD 81 6F DB 56 92 F6 30… 0,,17871,1:07.744.442,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,17872,1:07.759.447,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 D3 79 8A C8 0D 6D 4C 8D 54 B9 7C D8 C7 92 1C… 0,,17876,1:07.760.444,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,17877,1:07.774.449,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17881,1:07.775.446,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,17882,1:07.775.449,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 D3 79 8A C8 0D 6D 4C 8D 54 B9 7C D8 C7 92 1C… 0,,17886,1:07.776.446,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,17887,1:07.791.451,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 C4 D8 48 BE 1E 6D 51 7B 23 F4 E3 96 18 A6 F5… 0,,17891,1:07.792.448,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,17892,1:07.806.454,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17896,1:07.807.450,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,17897,1:07.807.454,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 C4 D8 48 BE 1E 6D 51 7B 23 F4 E3 96 18 A6 F5… 0,,17901,1:07.808.451,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,17902,1:07.823.456,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 1E 73 E6 DF 74 79 15 89 C8 C3 D5 85 B3 44 4E… 0,,17906,1:07.824.453,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,17907,1:07.838.458,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17911,1:07.839.455,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,17912,1:07.839.458,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EC 1E 73 E6 DF 74 79 15 89 C8 C3 D5 85 B3 44 4E… 0,,17916,1:07.840.455,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,17917,1:07.855.460,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1A FC 3B 71 24 4E 77 2D 9E A3 E3 D4 2F FB D0 E3… 0,,17921,1:07.856.457,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,17922,1:07.870.462,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17926,1:07.871.459,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,17927,1:07.871.463,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1A FC 3B 71 24 4E 77 2D 9E A3 E3 D4 2F FB D0 E3… 0,,17931,1:07.872.459,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,17932,1:07.887.465,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C B1 3F 6F E5 4E 26 DB 93 B8 75 1D F4 99 CA B4… 0,,17936,1:07.888.462,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,17937,1:07.902.467,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17941,1:07.903.464,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,17942,1:07.903.467,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C B1 3F 6F E5 4E 26 DB 93 B8 75 1D F4 99 CA B4… 0,,17946,1:07.904.464,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,17947,1:07.919.469,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B F8 23 4A 15 77 C0 2C 5B EF 4B A9 15 B3 E6 77… 0,,17951,1:07.920.466,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,17952,1:07.934.471,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17956,1:07.935.468,16.005.041 ms,,,,,[17 SOF],[Frames: 289 - 305] 0,,17957,1:07.951.474,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 0C 3A A1 27 6B 96 4F 48 AD 5C C6 AD 64 DD 5E… 0,,17961,1:07.952.471,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,17962,1:07.966.476,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17966,1:07.967.473,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,17967,1:07.967.476,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 0C 3A A1 27 6B 96 4F 48 AD 5C C6 AD 64 DD 5E… 0,,17971,1:07.968.473,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,17972,1:07.983.478,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A7 78 B1 68 00 4E AF D5 6D 21 22 5D BE 95 11 51… 0,,17976,1:07.984.475,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,17977,1:07.998.480,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17981,1:07.999.477,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,17982,1:07.999.480,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A7 78 B1 68 00 4E AF D5 6D 21 22 5D BE 95 11 51… 0,,17986,1:08.000.477,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,17987,1:08.015.483,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 22 0F 74 29 F9 DF 04 99 D4 B1 FC DF C2 7E EF 47… 0,,17991,1:08.016.479,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,17992,1:08.030.485,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,17996,1:08.031.482,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,17997,1:08.031.485,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 22 0F 74 29 F9 DF 04 99 D4 B1 FC DF C2 7E EF 47… 0,,18001,1:08.032.482,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,18002,1:08.047.487,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 0D B8 3B 7A B0 30 8C D9 24 AC 21 0A 3C 06 44… 0,,18006,1:08.048.484,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,18007,1:08.062.489,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18011,1:08.063.486,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,18012,1:08.063.489,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 0D B8 3B 7A B0 30 8C D9 24 AC 21 0A 3C 06 44… 0,,18016,1:08.064.486,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,18017,1:08.079.491,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 66 C8 A9 EB 09 54 1C 4D 27 69 44 90 2F 0D 23… 0,,18021,1:08.080.488,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,18022,1:08.094.494,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18026,1:08.095.490,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,18027,1:08.095.494,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 66 C8 A9 EB 09 54 1C 4D 27 69 44 90 2F 0D 23… 0,,18031,1:08.096.491,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,18032,1:08.111.496,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D D7 31 5D C3 99 84 66 A4 F1 A8 E7 17 2D 48 A7… 0,,18036,1:08.112.493,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,18037,1:08.126.498,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18041,1:08.127.495,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,18042,1:08.127.498,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D D7 31 5D C3 99 84 66 A4 F1 A8 E7 17 2D 48 A7… 0,,18046,1:08.128.495,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,18047,1:08.143.500,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 3E 0E E4 C8 D6 4E BF 31 37 E6 4C 00 8F 8C 44… 0,,18051,1:08.144.497,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,18052,1:08.158.502,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18056,1:08.159.499,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,18057,1:08.159.503,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 3E 0E E4 C8 D6 4E BF 31 37 E6 4C 00 8F 8C 44… 0,,18061,1:08.160.499,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,18062,1:08.175.505,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F7 72 86 17 F1 47 C9 C0 9F 64 35 99 23 3C 82 0F… 0,,18066,1:08.176.502,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,18067,1:08.190.507,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18071,1:08.191.504,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,18072,1:08.191.507,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F7 72 86 17 F1 47 C9 C0 9F 64 35 99 23 3C 82 0F… 0,,18076,1:08.192.504,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,18077,1:08.207.509,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F0 61 02 18 18 28 9D EF 9F E3 77 89 F2 2F 54 A9… 0,,18081,1:08.208.506,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,18082,1:08.222.511,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18086,1:08.223.508,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,18087,1:08.223.511,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F0 61 02 18 18 28 9D EF 9F E3 77 89 F2 2F 54 A9… 0,,18091,1:08.224.508,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,18092,1:08.239.514,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 4C 67 95 7F 74 F5 F8 5E 54 47 00 2B BB 3E 60… 0,,18096,1:08.240.511,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,18097,1:08.254.516,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18101,1:08.255.513,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,18102,1:08.255.516,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 4C 67 95 7F 74 F5 F8 5E 54 47 00 2B BB 3E 60… 0,,18106,1:08.256.513,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,18107,1:08.271.518,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 5B C0 7A E8 9A 4D 91 5A 2A AD CD 48 37 62 A6… 0,,18111,1:08.272.515,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,18112,1:08.286.520,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18116,1:08.287.517,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,18117,1:08.287.520,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 5B C0 7A E8 9A 4D 91 5A 2A AD CD 48 37 62 A6… 0,,18121,1:08.288.517,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,18122,1:08.303.523,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 10 0B 80 06 FA BC 06 59 AF 89 36 EE 59 7A 6D C0… 0,,18126,1:08.304.519,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,18127,1:08.318.525,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18131,1:08.319.521,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,18132,1:08.319.525,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 10 0B 80 06 FA BC 06 59 AF 89 36 EE 59 7A 6D C0… 0,,18136,1:08.320.522,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,18137,1:08.335.527,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AE E1 38 0E 2A 74 D7 FB 1F 72 94 84 C8 07 2C AB… 0,,18141,1:08.336.524,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,18142,1:08.350.529,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18146,1:08.351.526,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,18147,1:08.351.529,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AE E1 38 0E 2A 74 D7 FB 1F 72 94 84 C8 07 2C AB… 0,,18151,1:08.352.526,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,18152,1:08.367.531,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 58 53 C0 65 E1 48 CF 67 F1 58 48 30 19 08 02… 0,,18156,1:08.368.528,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,18157,1:08.382.533,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18161,1:08.383.530,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,18162,1:08.383.534,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 58 53 C0 65 E1 48 CF 67 F1 58 48 30 19 08 02… 0,,18166,1:08.384.531,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,18167,1:08.399.536,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 B1 B2 66 62 FA CD 71 BE E8 C4 57 D6 7D 22 E8… 0,,18171,1:08.400.533,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,18172,1:08.414.538,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18176,1:08.415.535,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,18177,1:08.415.538,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 B1 B2 66 62 FA CD 71 BE E8 C4 57 D6 7D 22 E8… 0,,18181,1:08.416.535,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,18182,1:08.431.540,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B1 D2 B0 A2 86 FC F3 94 E2 C2 9A 8B E0 16 B4 31… 0,,18186,1:08.432.537,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,18187,1:08.446.542,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18191,1:08.447.539,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,18192,1:08.447.543,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B1 D2 B0 A2 86 FC F3 94 E2 C2 9A 8B E0 16 B4 31… 0,,18196,1:08.448.539,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,18197,1:08.463.545,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 37 0B 9B 51 FB 95 A7 FB 0F 19 51 2F DB BA AD 02… 0,,18201,1:08.464.542,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,18202,1:08.478.547,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18206,1:08.479.544,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,18207,1:08.479.547,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 37 0B 9B 51 FB 95 A7 FB 0F 19 51 2F DB BA AD 02… 0,,18211,1:08.480.544,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,18212,1:08.495.549,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 AE 5B 45 E0 78 8D 6D 0C 30 C6 88 71 D9 E3 65… 0,,18216,1:08.496.546,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,18217,1:08.510.551,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18221,1:08.511.548,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,18222,1:08.511.551,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 AE 5B 45 E0 78 8D 6D 0C 30 C6 88 71 D9 E3 65… 0,,18226,1:08.512.548,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,18227,1:08.527.554,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 A5 BB FD 97 80 4E F8 4B D5 B9 72 3F E7 02 A4… 0,,18231,1:08.528.551,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,18232,1:08.542.556,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18236,1:08.543.553,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,18237,1:08.543.556,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 A5 BB FD 97 80 4E F8 4B D5 B9 72 3F E7 02 A4… 0,,18241,1:08.544.553,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,18242,1:08.559.558,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA D4 0F 83 00 D7 AB C9 71 6C DA BB 7E 5D 0B 3A… 0,,18246,1:08.560.555,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,18247,1:08.574.560,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18251,1:08.575.557,16.005.041 ms,,,,,[17 SOF],[Frames: 929 - 945] 0,,18252,1:08.591.563,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA D4 0F 83 00 D7 AB C9 71 6C DA BB 7E 5D 0B 3A… 0,,18256,1:08.592.559,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,18257,1:08.606.565,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18261,1:08.607.561,16.005.041 ms,,,,,[17 SOF],[Frames: 961 - 977] 0,,18262,1:08.623.567,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9A 33 11 6B D5 3B D9 EF ED DB 1F 25 56 30 92 0E… 0,,18266,1:08.624.564,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,18267,1:08.638.569,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18271,1:08.639.566,2.812 us,,,,,[1 SOF],[Frame: 993] 0,,18272,1:08.639.569,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9A 33 11 6B D5 3B D9 EF ED DB 1F 25 56 30 92 0E… 0,,18276,1:08.640.566,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,18277,1:08.655.571,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EE 12 5E A3 AF B6 80 8B 15 97 87 43 AF DF 86 09… 0,,18281,1:08.656.568,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,18282,1:08.670.573,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18286,1:08.671.570,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,18287,1:08.671.574,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EE 12 5E A3 AF B6 80 8B 15 97 87 43 AF DF 86 09… 0,,18291,1:08.672.570,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,18292,1:08.687.576,50.937 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 62 0F 75 FB CF BB E0 2F 90 BE DE 5F DE E3 EC… 0,,18296,1:08.688.573,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,18297,1:08.702.578,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18301,1:08.703.575,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,18302,1:08.703.578,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 62 0F 75 FB CF BB E0 2F 90 BE DE 5F DE E3 EC… 0,,18306,1:08.704.575,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,18307,1:08.719.580,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8C 8F C2 0F 91 9A 21 07 6A 65 B0 5E E8 10 19 D7… 0,,18311,1:08.720.577,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,18312,1:08.734.582,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18316,1:08.735.579,2.833 us,,,,,[1 SOF],[Frame: 1089] 0,,18317,1:08.735.582,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8C 8F C2 0F 91 9A 21 07 6A 65 B0 5E E8 10 19 D7… 0,,18321,1:08.736.579,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,18322,1:08.751.585,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 2D 07 2B 76 C1 DF 74 84 B4 A7 00 98 2E 90 8C… 0,,18326,1:08.752.582,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,18327,1:08.766.587,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18331,1:08.767.584,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,18332,1:08.767.587,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 2D 07 2B 76 C1 DF 74 84 B4 A7 00 98 2E 90 8C… 0,,18336,1:08.768.584,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,18337,1:08.783.589,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 66 6B A6 C6 E4 E1 64 D3 3F 38 86 ED D0 21 13 5C… 0,,18341,1:08.784.586,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,18342,1:08.798.591,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18346,1:08.799.588,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,18347,1:08.799.591,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 66 6B A6 C6 E4 E1 64 D3 3F 38 86 ED D0 21 13 5C… 0,,18351,1:08.800.588,15.004.916 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,18352,1:08.815.594,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 BB 43 CE 14 FD FC 7F A1 10 1F 77 71 71 AC 3D… 0,,18356,1:08.816.590,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,18357,1:08.830.596,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18361,1:08.831.593,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,18362,1:08.831.596,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 BB 43 CE 14 FD FC 7F A1 10 1F 77 71 71 AC 3D… 0,,18366,1:08.832.593,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,18367,1:08.847.598,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B 92 EB EE 8F 64 E3 42 B8 12 8A C8 90 B9 36 28… 0,,18371,1:08.848.595,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,18372,1:08.862.600,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18376,1:08.863.597,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,18377,1:08.863.600,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B 92 EB EE 8F 64 E3 42 B8 12 8A C8 90 B9 36 28… 0,,18381,1:08.864.597,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,18382,1:08.879.602,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 4D 73 22 25 86 EB 68 3E B7 DF 76 03 5E 27 FD… 0,,18386,1:08.880.599,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,18387,1:08.894.605,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18391,1:08.895.601,2.833 us,,,,,[1 SOF],[Frame: 1249] 0,,18392,1:08.895.605,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 4D 73 22 25 86 EB 68 3E B7 DF 76 03 5E 27 FD… 0,,18396,1:08.896.602,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,18397,1:08.911.607,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 08 38 32 3F 86 D8 2E 25 82 9D A6 A3 67 40 A1… 0,,18401,1:08.912.604,14.004.750 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,18402,1:08.926.609,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18406,1:08.927.606,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,18407,1:08.927.609,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 08 38 32 3F 86 D8 2E 25 82 9D A6 A3 67 40 A1… 0,,18411,1:08.928.606,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,18412,1:08.943.611,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 65 FB 75 33 D9 68 93 64 18 D2 DB 9C 46 93 BB 9C… 0,,18416,1:08.944.608,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,18417,1:08.958.613,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18421,1:08.959.610,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,18422,1:08.959.614,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 65 FB 75 33 D9 68 93 64 18 D2 DB 9C 46 93 BB 9C… 0,,18426,1:08.960.610,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,18427,1:08.975.616,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 35 F1 4E 7D 3F E0 42 99 57 FF 42 14 8D 29 3A 74… 0,,18431,1:08.976.613,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,18432,1:08.990.618,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18436,1:08.991.615,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,18437,1:08.991.618,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 35 F1 4E 7D 3F E0 42 99 57 FF 42 14 8D 29 3A 74… 0,,18441,1:08.992.615,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,18442,1:09.007.620,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C2 D4 27 C6 CE 85 DC 90 1F D7 71 AC FF FB 31 67… 0,,18446,1:09.008.617,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,18447,1:09.022.622,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18451,1:09.023.619,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,18452,1:09.023.622,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C2 D4 27 C6 CE 85 DC 90 1F D7 71 AC FF FB 31 67… 0,,18456,1:09.024.619,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,18457,1:09.039.625,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FE 34 08 49 7B E8 8D 30 31 A6 4D 83 29 52 63 C8… 0,,18461,1:09.040.622,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,18462,1:09.054.627,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18466,1:09.055.624,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,18467,1:09.055.627,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FE 34 08 49 7B E8 8D 30 31 A6 4D 83 29 52 63 C8… 0,,18471,1:09.056.624,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,18472,1:09.071.629,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 9B 3F 15 93 AC D0 05 84 92 64 D3 B7 C9 70 28… 0,,18476,1:09.072.626,14.004.750 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,18477,1:09.086.631,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18481,1:09.087.628,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,18482,1:09.087.631,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 9B 3F 15 93 AC D0 05 84 92 64 D3 B7 C9 70 28… 0,,18486,1:09.088.628,15.004.916 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,18487,1:09.103.634,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 21 0B 29 4C 5B 9C 02 A2 8F 5B 5E 42 19 8D 09 C8… 0,,18491,1:09.104.630,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,18492,1:09.118.636,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18496,1:09.119.633,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,18497,1:09.119.636,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 21 0B 29 4C 5B 9C 02 A2 8F 5B 5E 42 19 8D 09 C8… 0,,18501,1:09.120.633,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,18502,1:09.135.638,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 C4 56 C3 81 59 15 A6 5F B2 ED A1 55 40 6F 16… 0,,18506,1:09.136.635,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,18507,1:09.150.640,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18511,1:09.151.637,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,18512,1:09.151.640,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 C4 56 C3 81 59 15 A6 5F B2 ED A1 55 40 6F 16… 0,,18516,1:09.152.637,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,18517,1:09.167.642,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 99 CC F3 EA 8B 83 34 3A 49 A9 9A 3F 5C B3 44… 0,,18521,1:09.168.639,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,18522,1:09.182.645,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18526,1:09.183.641,16.005.125 ms,,,,,[17 SOF],[Frames: 1537 - 1553] 0,,18527,1:09.199.647,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7C ED 54 00 F2 EF BA C9 73 8F D7 B7 2F 33 83 00… 0,,18531,1:09.200.644,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,18532,1:09.214.649,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18536,1:09.215.646,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,18537,1:09.215.649,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7C ED 54 00 F2 EF BA C9 73 8F D7 B7 2F 33 83 00… 0,,18541,1:09.216.646,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,18542,1:09.231.651,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 99 4D 00 99 00 C7 FD 83 9A D0 46 F2 33 77 A6… 0,,18546,1:09.232.648,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,18547,1:09.246.653,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18551,1:09.247.650,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,18552,1:09.247.654,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 99 4D 00 99 00 C7 FD 83 9A D0 46 F2 33 77 A6… 0,,18556,1:09.248.650,15.004.916 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,18557,1:09.263.656,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 45 99 05 8B 27 86 9E 4B 72 BF F7 0C ED 34 5D… 0,,18561,1:09.264.653,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,18562,1:09.278.658,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18566,1:09.279.655,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,18567,1:09.279.658,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 45 99 05 8B 27 86 9E 4B 72 BF F7 0C ED 34 5D… 0,,18571,1:09.280.655,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,18572,1:09.295.660,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 37 70 FA 41 7D C2 AB CB 50 66 12 00 2B 25 92… 0,,18576,1:09.296.657,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,18577,1:09.310.662,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18581,1:09.311.659,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,18582,1:09.311.662,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 37 70 FA 41 7D C2 AB CB 50 66 12 00 2B 25 92… 0,,18586,1:09.312.659,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,18587,1:09.327.665,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 25 C9 B1 D5 DE 8B 77 44 0D 67 1F F2 A2 D1 4A… 0,,18591,1:09.328.662,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,18592,1:09.342.667,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18596,1:09.343.664,2.833 us,,,,,[1 SOF],[Frame: 1697] 0,,18597,1:09.343.667,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 25 C9 B1 D5 DE 8B 77 44 0D 67 1F F2 A2 D1 4A… 0,,18601,1:09.344.664,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,18602,1:09.359.669,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 55 F0 13 AF 30 41 98 95 F8 9B 3F 9D DE 24 82… 0,,18606,1:09.360.666,14.004.750 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,18607,1:09.374.671,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18611,1:09.375.668,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,18612,1:09.375.671,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 55 F0 13 AF 30 41 98 95 F8 9B 3F 9D DE 24 82… 0,,18616,1:09.376.668,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,18617,1:09.391.674,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 56 39 BB 99 B8 DC 0C 7F BD 25 C6 24 90 EC 06 EB… 0,,18621,1:09.392.670,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,18622,1:09.406.676,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18626,1:09.407.673,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,18627,1:09.407.676,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 56 39 BB 99 B8 DC 0C 7F BD 25 C6 24 90 EC 06 EB… 0,,18631,1:09.408.673,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,18632,1:09.423.678,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 0C 2C C4 C4 0E C7 C1 D0 07 1C 06 27 47 B3 FD… 0,,18636,1:09.424.675,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,18637,1:09.438.680,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18641,1:09.439.677,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,18642,1:09.439.680,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 0C 2C C4 C4 0E C7 C1 D0 07 1C 06 27 47 B3 FD… 0,,18646,1:09.440.677,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,18647,1:09.455.682,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 64 14 7D 9D 6F AB A5 6C 57 60 79 B1 3E 9D EF C5… 0,,18651,1:09.456.679,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,18652,1:09.470.685,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18656,1:09.471.681,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,18657,1:09.471.685,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 64 14 7D 9D 6F AB A5 6C 57 60 79 B1 3E 9D EF C5… 0,,18661,1:09.472.682,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,18662,1:09.487.687,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 7B B4 45 E4 62 08 8F 91 D3 7E 8B 29 81 81 4D… 0,,18666,1:09.488.684,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,18667,1:09.502.689,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18671,1:09.503.686,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,18672,1:09.503.689,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 7B B4 45 E4 62 08 8F 91 D3 7E 8B 29 81 81 4D… 0,,18676,1:09.504.686,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,18677,1:09.519.691,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 FD 5A FB 23 33 AF 8E D4 D1 31 BF D4 66 54 BA… 0,,18681,1:09.520.688,14.004.750 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,18682,1:09.534.693,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18686,1:09.535.690,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,18687,1:09.535.694,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 FD 5A FB 23 33 AF 8E D4 D1 31 BF D4 66 54 BA… 0,,18691,1:09.536.690,15.004.916 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,18692,1:09.551.696,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D2 B3 08 77 1C 13 C6 07 BB A7 EF 99 5B 5C A6 82… 0,,18696,1:09.552.693,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,18697,1:09.566.698,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18701,1:09.567.695,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,18702,1:09.567.698,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D2 B3 08 77 1C 13 C6 07 BB A7 EF 99 5B 5C A6 82… 0,,18706,1:09.568.695,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,18707,1:09.583.700,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 20 B9 B9 39 BC 48 F1 CF 16 A6 95 27 DA F1 C5… 0,,18711,1:09.584.697,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,18712,1:09.598.702,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18716,1:09.599.699,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,18717,1:09.599.702,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 20 B9 B9 39 BC 48 F1 CF 16 A6 95 27 DA F1 C5… 0,,18721,1:09.600.699,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,18722,1:09.615.705,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 79 56 6D AD C1 3B 25 61 98 6A 9A 11 A8 5D BB… 0,,18726,1:09.616.702,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,18727,1:09.630.707,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18731,1:09.631.704,2.916 us,,,,,[1 SOF],[Frame: 1985] 0,,18732,1:09.631.707,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 79 56 6D AD C1 3B 25 61 98 6A 9A 11 A8 5D BB… 0,,18736,1:09.632.704,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,18737,1:09.647.709,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E B9 02 08 A7 61 67 FA 7E 62 B0 81 76 B4 7D 74… 0,,18741,1:09.648.706,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,18742,1:09.662.711,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18746,1:09.663.708,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,18747,1:09.663.711,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E B9 02 08 A7 61 67 FA 7E 62 B0 81 76 B4 7D 74… 0,,18751,1:09.664.708,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,18752,1:09.679.714,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 76 CF 8B 96 8E 79 07 89 EC 4A 18 59 45 11 59… 0,,18756,1:09.680.710,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,18757,1:09.694.716,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18761,1:09.695.713,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,18762,1:09.695.716,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 76 CF 8B 96 8E 79 07 89 EC 4A 18 59 45 11 59… 0,,18766,1:09.696.713,15.004.916 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,18767,1:09.711.718,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 6B 0F BC 19 ED CB 29 29 D0 37 2B 3D 90 C9 E0… 0,,18771,1:09.712.715,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,18772,1:09.726.720,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18776,1:09.727.717,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,18777,1:09.727.720,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 6B 0F BC 19 ED CB 29 29 D0 37 2B 3D 90 C9 E0… 0,,18781,1:09.728.717,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,18782,1:09.743.722,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F CB D7 4A E9 15 A2 30 02 BB A2 BD F2 A6 C4 4F… 0,,18786,1:09.744.719,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,18787,1:09.758.724,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18791,1:09.759.721,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,18792,1:09.759.725,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3F CB D7 4A E9 15 A2 30 02 BB A2 BD F2 A6 C4 4F… 0,,18796,1:09.760.722,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,18797,1:09.775.727,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 2C 0D 49 8E 0D DE 77 B0 32 9B 49 0F 10 E2 96… 0,,18801,1:09.776.724,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,18802,1:09.790.729,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18806,1:09.791.726,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,18807,1:09.791.729,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 2C 0D 49 8E 0D DE 77 B0 32 9B 49 0F 10 E2 96… 0,,18811,1:09.792.726,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,18812,1:09.807.731,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18816,1:09.808.728,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,18817,1:09.822.733,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18821,1:09.823.730,16.005.041 ms,,,,,[17 SOF],[Frames: 129 - 145] 0,,18822,1:09.839.736,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18826,1:09.840.733,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,18827,1:09.854.738,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18831,1:09.855.735,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,18832,1:09.855.738,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F 6E 71 5B E1 87 D8 60 73 46 EC 05 55 49 AC AD… 0,,18836,1:09.856.735,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,18837,1:09.871.740,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 7D 6E 49 70 CB 2D 33 08 D2 8C 28 67 24 C4 E4… 0,,18841,1:09.872.737,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,18842,1:09.886.742,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18846,1:09.887.739,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,18847,1:09.887.742,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 7D 6E 49 70 CB 2D 33 08 D2 8C 28 67 24 C4 E4… 0,,18851,1:09.888.739,15.004.895 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,18852,1:09.903.745,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 77 BD 62 FC 3B 04 DC 1B 6D 7D 8F F4 BE 3C 05… 0,,18856,1:09.904.742,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,18857,1:09.918.747,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18861,1:09.919.744,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,18862,1:09.919.747,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 77 BD 62 FC 3B 04 DC 1B 6D 7D 8F F4 BE 3C 05… 0,,18866,1:09.920.744,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,18867,1:09.935.749,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 89 D1 3B E7 8B 10 15 A1 EA 2A 55 E8 04 F4 5A… 0,,18871,1:09.936.746,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,18872,1:09.950.751,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18876,1:09.951.748,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,18877,1:09.951.751,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 89 D1 3B E7 8B 10 15 A1 EA 2A 55 E8 04 F4 5A… 0,,18881,1:09.952.748,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,18882,1:09.967.754,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 84 50 7C E2 45 56 44 84 51 E8 C0 06 D8 67 E3… 0,,18886,1:09.968.750,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,18887,1:09.982.756,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18891,1:09.983.752,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,18892,1:09.983.756,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 84 50 7C E2 45 56 44 84 51 E8 C0 06 D8 67 E3… 0,,18896,1:09.984.753,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,18897,1:09.999.758,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 02 A0 EE 57 73 0F EF C1 9B 4E 3A F1 FA 65 35 F3… 0,,18901,1:10.000.755,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,18902,1:10.014.760,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18906,1:10.015.757,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,18907,1:10.015.760,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 02 A0 EE 57 73 0F EF C1 9B 4E 3A F1 FA 65 35 F3… 0,,18911,1:10.016.757,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,18912,1:10.031.762,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 26 25 0C 16 72 D6 D3 5E 3A 17 17 A6 F0 32 68 77… 0,,18916,1:10.032.759,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,18917,1:10.046.764,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18921,1:10.047.761,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,18922,1:10.047.765,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 26 25 0C 16 72 D6 D3 5E 3A 17 17 A6 F0 32 68 77… 0,,18926,1:10.048.762,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,18927,1:10.063.767,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 9E D7 B7 DD 5C 6D F7 87 94 52 1D D8 19 CF 43… 0,,18931,1:10.064.764,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,18932,1:10.078.769,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18936,1:10.079.766,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,18937,1:10.079.769,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 9E D7 B7 DD 5C 6D F7 87 94 52 1D D8 19 CF 43… 0,,18941,1:10.080.766,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,18942,1:10.095.771,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 8D 8C E3 93 41 E4 4F 05 B1 F0 57 65 44 7C 5F… 0,,18946,1:10.096.768,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,18947,1:10.110.773,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18951,1:10.111.770,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,18952,1:10.111.774,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 8D 8C E3 93 41 E4 4F 05 B1 F0 57 65 44 7C 5F… 0,,18956,1:10.112.770,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,18957,1:10.127.776,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 CA 6E DC CC 23 A7 08 E4 5E 1C F6 2B 02 15 C4… 0,,18961,1:10.128.773,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,18962,1:10.142.778,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18966,1:10.143.775,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,18967,1:10.143.778,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 CA 6E DC CC 23 A7 08 E4 5E 1C F6 2B 02 15 C4… 0,,18971,1:10.144.775,15.004.916 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,18972,1:10.159.780,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B9 0A 8D BD AF 36 EE 5D 6F 3C 75 E1 11 B6 78 63… 0,,18976,1:10.160.777,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,18977,1:10.174.782,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18981,1:10.175.779,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,18982,1:10.175.782,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B9 0A 8D BD AF 36 EE 5D 6F 3C 75 E1 11 B6 78 63… 0,,18986,1:10.176.779,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,18987,1:10.191.785,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2F AD D4 D8 D1 2F E5 82 E2 5C AC 2D A6 C2 88 48… 0,,18991,1:10.192.781,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,18992,1:10.206.787,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,18996,1:10.207.784,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,18997,1:10.207.787,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2F AD D4 D8 D1 2F E5 82 E2 5C AC 2D A6 C2 88 48… 0,,19001,1:10.208.784,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,19002,1:10.223.789,50.916 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 11 90 93 FD 55 DB EF 2E D0 C0 ED FD CB 52 7F… 0,,19006,1:10.224.786,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,19007,1:10.238.791,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19011,1:10.239.788,2.833 us,,,,,[1 SOF],[Frame: 545] 0,,19012,1:10.239.791,50.916 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 11 90 93 FD 55 DB EF 2E D0 C0 ED FD CB 52 7F… 0,,19016,1:10.240.788,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,19017,1:10.255.793,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D6 BC F5 5B 73 FA 49 CF 8E 76 55 C1 B1 0A CA 9E… 0,,19021,1:10.256.790,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,19022,1:10.270.796,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19026,1:10.271.792,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,19027,1:10.271.796,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D6 BC F5 5B 73 FA 49 CF 8E 76 55 C1 B1 0A CA 9E… 0,,19031,1:10.272.793,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,19032,1:10.287.798,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 03 48 A2 27 9A 84 B7 B5 5E C2 B2 A9 25 AA 49 5F… 0,,19036,1:10.288.795,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,19037,1:10.302.800,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19041,1:10.303.797,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,19042,1:10.303.800,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 03 48 A2 27 9A 84 B7 B5 5E C2 B2 A9 25 AA 49 5F… 0,,19046,1:10.304.797,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,19047,1:10.319.802,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 86 AF E9 C6 3A 38 14 F3 A3 8E 4C 8C FD F5 C9 62… 0,,19051,1:10.320.799,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,19052,1:10.334.804,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19056,1:10.335.801,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,19057,1:10.335.805,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 86 AF E9 C6 3A 38 14 F3 A3 8E 4C 8C FD F5 C9 62… 0,,19061,1:10.336.801,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,19062,1:10.351.807,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 7D 87 D9 E4 E3 95 FC 58 CF BF 3E 2C 4A 3F B7… 0,,19066,1:10.352.804,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,19067,1:10.366.809,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19071,1:10.367.806,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,19072,1:10.367.809,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 7D 87 D9 E4 E3 95 FC 58 CF BF 3E 2C 4A 3F B7… 0,,19076,1:10.368.806,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,19077,1:10.383.811,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 AE F7 54 C2 39 BD 5B 71 6C C7 AC 71 C8 36 C8… 0,,19081,1:10.384.808,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,19082,1:10.398.813,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19086,1:10.399.810,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,19087,1:10.399.813,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 AE F7 54 C2 39 BD 5B 71 6C C7 AC 71 C8 36 C8… 0,,19091,1:10.400.810,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,19092,1:10.415.816,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3F 79 50 12 8E 43 7E 6B EC 7B E8 87 26 7C DC F5… 0,,19096,1:10.416.813,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,19097,1:10.430.818,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19101,1:10.431.815,16.005.041 ms,,,,,[17 SOF],[Frames: 737 - 753] 0,,19102,1:10.447.820,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 45 8F B8 17 67 A8 B3 77 87 1A CF E1 B5 FA 3F… 0,,19106,1:10.448.817,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,19107,1:10.462.822,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19111,1:10.463.819,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,19112,1:10.463.822,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 45 8F B8 17 67 A8 B3 77 87 1A CF E1 B5 FA 3F… 0,,19116,1:10.464.819,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,19117,1:10.479.825,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB D3 F4 FB 57 C9 3A B4 B4 D0 50 9D C1 F6 58 48… 0,,19121,1:10.480.821,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,19122,1:10.494.827,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19126,1:10.495.824,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,19127,1:10.495.827,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB D3 F4 FB 57 C9 3A B4 B4 D0 50 9D C1 F6 58 48… 0,,19131,1:10.496.824,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,19132,1:10.511.829,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 B9 E8 B6 6E 6D D1 7E 85 F2 C8 2A C5 DA 7A 0D… 0,,19136,1:10.512.826,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,19137,1:10.526.831,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19141,1:10.527.828,2.833 us,,,,,[1 SOF],[Frame: 833] 0,,19142,1:10.527.831,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 B9 E8 B6 6E 6D D1 7E 85 F2 C8 2A C5 DA 7A 0D… 0,,19146,1:10.528.828,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,19147,1:10.543.833,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF 3B 8B 2A A0 A6 84 C8 AA F5 F1 BE 2B C3 F9 8C… 0,,19151,1:10.544.830,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,19152,1:10.558.836,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19156,1:10.559.832,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,19157,1:10.559.836,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF 3B 8B 2A A0 A6 84 C8 AA F5 F1 BE 2B C3 F9 8C… 0,,19161,1:10.560.833,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,19162,1:10.575.838,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 0C CA AD 2A 36 7E 63 3D 82 1A CE E2 02 A5 D5… 0,,19166,1:10.576.835,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,19167,1:10.590.840,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19171,1:10.591.837,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,19172,1:10.591.840,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 0C CA AD 2A 36 7E 63 3D 82 1A CE E2 02 A5 D5… 0,,19176,1:10.592.837,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,19177,1:10.607.842,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 2C 49 95 8C E3 41 4A D4 FF 80 F4 F6 E6 27 99… 0,,19181,1:10.608.839,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,19182,1:10.622.844,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19186,1:10.623.841,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,19187,1:10.623.845,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 2C 49 95 8C E3 41 4A D4 FF 80 F4 F6 E6 27 99… 0,,19191,1:10.624.841,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,19192,1:10.639.847,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 79 DE 60 8D E8 A7 41 25 A8 74 5F 42 A7 40 A1… 0,,19196,1:10.640.844,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,19197,1:10.654.849,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19201,1:10.655.846,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,19202,1:10.655.849,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 79 DE 60 8D E8 A7 41 25 A8 74 5F 42 A7 40 A1… 0,,19206,1:10.656.846,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,19207,1:10.671.851,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 99 96 78 6E C6 0C 1D BC 33 2B 17 9E 38 F0 B3 67… 0,,19211,1:10.672.848,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,19212,1:10.686.853,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19216,1:10.687.850,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,19217,1:10.687.853,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 99 96 78 6E C6 0C 1D BC 33 2B 17 9E 38 F0 B3 67… 0,,19221,1:10.688.850,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,19222,1:10.703.856,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 8D 79 6D 4D C9 FD 5E D1 31 68 E2 15 FC 07 9C… 0,,19226,1:10.704.853,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,19227,1:10.718.858,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19231,1:10.719.855,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,19232,1:10.719.858,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 8D 79 6D 4D C9 FD 5E D1 31 68 E2 15 FC 07 9C… 0,,19236,1:10.720.855,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,19237,1:10.735.860,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 6D ED E4 E1 10 28 C9 46 42 92 68 EB 94 36 83… 0,,19241,1:10.736.857,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,19242,1:10.750.862,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19246,1:10.751.859,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,19247,1:10.751.862,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 6D ED E4 E1 10 28 C9 46 42 92 68 EB 94 36 83… 0,,19251,1:10.752.859,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,19252,1:10.767.865,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD B8 DF 89 01 83 30 50 29 2D 34 87 F9 99 9E C4… 0,,19256,1:10.768.861,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,19257,1:10.782.867,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19261,1:10.783.864,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,19262,1:10.783.867,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD B8 DF 89 01 83 30 50 29 2D 34 87 F9 99 9E C4… 0,,19266,1:10.784.864,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,19267,1:10.799.869,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 52 69 71 6E CC 73 70 27 06 99 01 F6 76 60 25 35… 0,,19271,1:10.800.866,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,19272,1:10.814.871,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19276,1:10.815.868,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,19277,1:10.815.871,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 52 69 71 6E CC 73 70 27 06 99 01 F6 76 60 25 35… 0,,19281,1:10.816.868,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,19282,1:10.831.873,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BA 5E 05 FC 9D B4 3E D0 DA 5C C1 3D 23 42 CA 5F… 0,,19286,1:10.832.870,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,19287,1:10.846.876,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19291,1:10.847.872,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,19292,1:10.847.876,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BA 5E 05 FC 9D B4 3E D0 DA 5C C1 3D 23 42 CA 5F… 0,,19296,1:10.848.873,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,19297,1:10.863.878,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 9E 8A 53 D0 DC 4E 9B 64 DE DB 4B 82 B2 F4 46… 0,,19301,1:10.864.875,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,19302,1:10.878.880,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19306,1:10.879.877,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,19307,1:10.879.880,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 9E 8A 53 D0 DC 4E 9B 64 DE DB 4B 82 B2 F4 46… 0,,19311,1:10.880.877,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,19312,1:10.895.882,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 CD 2F AC 62 CA F6 E8 11 3C 7D 14 31 78 DE DC… 0,,19316,1:10.896.879,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,19317,1:10.910.884,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19321,1:10.911.881,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,19322,1:10.911.885,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 CD 2F AC 62 CA F6 E8 11 3C 7D 14 31 78 DE DC… 0,,19326,1:10.912.881,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,19327,1:10.927.887,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 08 8E 8F 67 9C FA C0 0B D7 65 E4 03 3E 1C B6 E0… 0,,19331,1:10.928.884,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,19332,1:10.942.889,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19336,1:10.943.886,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,19337,1:10.943.889,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 08 8E 8F 67 9C FA C0 0B D7 65 E4 03 3E 1C B6 E0… 0,,19341,1:10.944.886,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,19342,1:10.959.891,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 2A DE 3F 8A 78 2D B6 3B 27 A9 2D DF 2A 01 53… 0,,19346,1:10.960.888,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,19347,1:10.974.893,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19351,1:10.975.890,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,19352,1:10.975.893,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 2A DE 3F 8A 78 2D B6 3B 27 A9 2D DF 2A 01 53… 0,,19356,1:10.976.890,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,19357,1:10.991.896,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AC 48 C2 EE B3 A2 B4 8A 83 B3 85 3E 9D 7D C7 5A… 0,,19361,1:10.992.893,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,19362,1:11.006.898,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19366,1:11.007.895,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,19367,1:11.007.898,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AC 48 C2 EE B3 A2 B4 8A 83 B3 85 3E 9D 7D C7 5A… 0,,19371,1:11.008.895,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,19372,1:11.023.900,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7A 42 3E B4 A5 A6 A6 88 96 95 E5 B4 75 29 C4 84… 0,,19376,1:11.024.897,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,19377,1:11.038.902,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19381,1:11.039.899,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,19382,1:11.039.902,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7A 42 3E B4 A5 A6 A6 88 96 95 E5 B4 75 29 C4 84… 0,,19386,1:11.040.899,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,19387,1:11.055.905,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F2 E7 A6 25 8E 13 74 8C C3 FA 6F 18 02 D8 69 18… 0,,19391,1:11.056.901,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,19392,1:11.070.907,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19396,1:11.071.904,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,19397,1:11.071.907,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F2 E7 A6 25 8E 13 74 8C C3 FA 6F 18 02 D8 69 18… 0,,19401,1:11.072.904,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,19402,1:11.087.909,50.645 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 E2 61 AF 01 D3 36 D1 12 54 FE 24 BD 80 FD 6A… 0,,19406,1:11.088.906,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,19407,1:11.102.911,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19411,1:11.103.908,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,19412,1:11.103.911,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 E2 61 AF 01 D3 36 D1 12 54 FE 24 BD 80 FD 6A… 0,,19416,1:11.104.908,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,19417,1:11.119.913,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 8B 5A FF 44 67 7E 34 C8 9D 01 6A D3 F1 E8 51… 0,,19421,1:11.120.910,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,19422,1:11.134.916,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19426,1:11.135.912,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,19427,1:11.135.916,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 8B 5A FF 44 67 7E 34 C8 9D 01 6A D3 F1 E8 51… 0,,19431,1:11.136.913,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,19432,1:11.151.918,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 E8 D0 5D E8 4A D6 84 8E CE 87 30 AD 00 54 CC… 0,,19436,1:11.152.915,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,19437,1:11.166.920,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19441,1:11.167.917,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,19442,1:11.167.920,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 E8 D0 5D E8 4A D6 84 8E CE 87 30 AD 00 54 CC… 0,,19446,1:11.168.917,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,19447,1:11.183.922,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 BB F4 0C A1 9E 85 20 C0 71 66 4F 06 22 6C 62… 0,,19451,1:11.184.919,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,19452,1:11.198.924,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19456,1:11.199.921,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,19457,1:11.199.925,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 BB F4 0C A1 9E 85 20 C0 71 66 4F 06 22 6C 62… 0,,19461,1:11.200.921,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,19462,1:11.215.927,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E9 9E 04 A0 87 1B F3 08 A2 DB C4 2F 4E DF B3 25… 0,,19466,1:11.216.924,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,19467,1:11.230.929,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19471,1:11.231.926,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,19472,1:11.231.929,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E9 9E 04 A0 87 1B F3 08 A2 DB C4 2F 4E DF B3 25… 0,,19476,1:11.232.926,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,19477,1:11.247.931,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9E 14 B3 32 D9 CB 77 44 94 5D 86 AC 9A FC DA 0D… 0,,19481,1:11.248.928,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,19482,1:11.262.933,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19486,1:11.263.930,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,19487,1:11.263.933,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9E 14 B3 32 D9 CB 77 44 94 5D 86 AC 9A FC DA 0D… 0,,19491,1:11.264.930,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,19492,1:11.279.936,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 BE EC 97 B1 67 73 47 67 EF 9A A9 E4 F3 E6 74… 0,,19496,1:11.280.933,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,19497,1:11.294.938,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19501,1:11.295.935,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,19502,1:11.295.938,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 BE EC 97 B1 67 73 47 67 EF 9A A9 E4 F3 E6 74… 0,,19506,1:11.296.935,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,19507,1:11.311.940,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 4D E0 19 F6 7A 38 17 11 BF 94 DD 04 88 79 BE… 0,,19511,1:11.312.937,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,19512,1:11.326.942,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19516,1:11.327.939,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,19517,1:11.327.942,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 4D E0 19 F6 7A 38 17 11 BF 94 DD 04 88 79 BE… 0,,19521,1:11.328.939,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,19522,1:11.343.945,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C9 61 BB 82 E6 2F 87 63 F8 7C C6 27 10 26 7C 0B… 0,,19526,1:11.344.941,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,19527,1:11.358.947,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19531,1:11.359.943,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,19532,1:11.359.947,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C9 61 BB 82 E6 2F 87 63 F8 7C C6 27 10 26 7C 0B… 0,,19536,1:11.360.944,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,19537,1:11.375.949,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B DD 5B AC D6 71 2D BB E7 CB FB 31 C0 81 31 16… 0,,19541,1:11.376.946,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,19542,1:11.390.951,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19546,1:11.391.948,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,19547,1:11.391.951,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B DD 5B AC D6 71 2D BB E7 CB FB 31 C0 81 31 16… 0,,19551,1:11.392.948,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,19552,1:11.407.953,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 88 F9 0C 87 7D 79 82 0B 94 BA FD EF 2F 8C D1… 0,,19556,1:11.408.950,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,19557,1:11.422.955,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19561,1:11.423.952,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,19562,1:11.423.956,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 88 F9 0C 87 7D 79 82 0B 94 BA FD EF 2F 8C D1… 0,,19566,1:11.424.953,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,19567,1:11.439.958,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F1 0A B2 4F E0 02 61 0D B9 0E 5D EE 76 5A 12 3A… 0,,19571,1:11.440.955,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,19572,1:11.454.960,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19576,1:11.455.957,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,19577,1:11.455.960,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F1 0A B2 4F E0 02 61 0D B9 0E 5D EE 76 5A 12 3A… 0,,19581,1:11.456.957,15.004.916 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,19582,1:11.471.962,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 C8 43 34 79 28 F2 E6 E9 48 7D 98 18 56 6F 63… 0,,19586,1:11.472.959,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,19587,1:11.486.964,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19591,1:11.487.961,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,19592,1:11.487.965,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 C8 43 34 79 28 F2 E6 E9 48 7D 98 18 56 6F 63… 0,,19596,1:11.488.961,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,19597,1:11.503.967,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 96 FD B4 23 3B 70 24 10 58 47 66 0C 61 49 0D 26… 0,,19601,1:11.504.964,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,19602,1:11.518.969,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19606,1:11.519.966,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,19607,1:11.519.969,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 96 FD B4 23 3B 70 24 10 58 47 66 0C 61 49 0D 26… 0,,19611,1:11.520.966,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,19612,1:11.535.971,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 67 F5 56 FA 58 5A 3E 81 36 FE DF D3 1D 6F 84… 0,,19616,1:11.536.968,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,19617,1:11.550.973,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19621,1:11.551.970,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,19622,1:11.551.973,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 67 F5 56 FA 58 5A 3E 81 36 FE DF D3 1D 6F 84… 0,,19626,1:11.552.970,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,19627,1:11.567.976,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0B 61 4F C7 1C 9B 25 BA D5 DF EA AC 3C 9F 55 85… 0,,19631,1:11.568.972,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,19632,1:11.582.978,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19636,1:11.583.975,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,19637,1:11.583.978,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0B 61 4F C7 1C 9B 25 BA D5 DF EA AC 3C 9F 55 85… 0,,19641,1:11.584.975,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,19642,1:11.599.980,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1F 89 9A B3 36 EB 27 8F 1C 7A B0 2E 93 04 71 35… 0,,19646,1:11.600.977,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,19647,1:11.614.982,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19651,1:11.615.979,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,19652,1:11.615.982,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1F 89 9A B3 36 EB 27 8F 1C 7A B0 2E 93 04 71 35… 0,,19656,1:11.616.979,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,19657,1:11.631.984,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 84 5E A8 C1 0B AF 16 D9 29 EE 65 23 FE F9 C0 BD… 0,,19661,1:11.632.981,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,19662,1:11.646.987,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19666,1:11.647.983,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,19667,1:11.647.987,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 84 5E A8 C1 0B AF 16 D9 29 EE 65 23 FE F9 C0 BD… 0,,19671,1:11.648.984,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,19672,1:11.663.989,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 50 A6 65 9F 73 55 82 3C C1 1C E0 4D FB 30 51… 0,,19676,1:11.664.986,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,19677,1:11.678.991,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19681,1:11.679.988,16.005.125 ms,,,,,[17 SOF],[Frames: 1985 - 2001] 0,,19682,1:11.695.993,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6A 15 0D 46 17 6C 6C 7B FF 71 D7 46 D9 B6 B8 A2… 0,,19686,1:11.696.990,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,19687,1:11.710.996,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19691,1:11.711.992,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,19692,1:11.711.996,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6A 15 0D 46 17 6C 6C 7B FF 71 D7 46 D9 B6 B8 A2… 0,,19696,1:11.712.992,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,19697,1:11.727.998,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 AD 97 37 22 80 A1 7F 83 77 EA AF 16 DB 47 9C… 0,,19701,1:11.728.995,14.004.750 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,19702,1:11.743.000,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19706,1:11.743.997,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,19707,1:11.744.000,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 AD 97 37 22 80 A1 7F 83 77 EA AF 16 DB 47 9C… 0,,19711,1:11.744.997,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,19712,1:11.760.002,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 33 BD A2 B8 59 58 85 2C 41 F1 DE 6D 6C E5 FF… 0,,19716,1:11.760.999,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,19717,1:11.775.004,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19721,1:11.776.001,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,19722,1:11.776.004,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 33 BD A2 B8 59 58 85 2C 41 F1 DE 6D 6C E5 FF… 0,,19726,1:11.777.001,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,19727,1:11.792.007,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6A DD 2B 58 66 06 2C E6 9E 01 CE B9 BA 54 C7 F8… 0,,19731,1:11.793.004,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,19732,1:11.807.009,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19736,1:11.808.006,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,19737,1:11.808.009,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6A DD 2B 58 66 06 2C E6 9E 01 CE B9 BA 54 C7 F8… 0,,19741,1:11.809.006,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,19742,1:11.824.011,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 7B 1C 59 BE 2B 70 C3 B4 F6 A9 4F E1 74 5B A1… 0,,19746,1:11.825.008,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,19747,1:11.839.013,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19751,1:11.840.010,2.833 us,,,,,[1 SOF],[Frame: 97] 0,,19752,1:11.840.013,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 7B 1C 59 BE 2B 70 C3 B4 F6 A9 4F E1 74 5B A1… 0,,19756,1:11.841.010,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,19757,1:11.856.016,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A 0B F6 6E 3D CB 74 2F F0 EC 9A FD 84 CC 0B 9E… 0,,19761,1:11.857.012,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,19762,1:11.871.018,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19766,1:11.872.015,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,19767,1:11.872.018,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A 0B F6 6E 3D CB 74 2F F0 EC 9A FD 84 CC 0B 9E… 0,,19771,1:11.873.015,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,19772,1:11.888.020,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 69 60 99 31 E9 DC FF 64 CF 85 48 13 27 C9 08… 0,,19776,1:11.889.017,14.004.750 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,19777,1:11.903.022,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19781,1:11.904.019,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,19782,1:11.904.022,50.645 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 69 60 99 31 E9 DC FF 64 CF 85 48 13 27 C9 08… 0,,19786,1:11.905.019,15.004.916 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,19787,1:11.920.024,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 16 E7 4B F6 8D C7 B7 B3 14 C9 94 4C 9C 8A 00… 0,,19791,1:11.921.021,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,19792,1:11.935.027,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19796,1:11.936.023,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,19797,1:11.936.027,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 16 E7 4B F6 8D C7 B7 B3 14 C9 94 4C 9C 8A 00… 0,,19801,1:11.937.024,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,19802,1:11.952.029,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 22 44 B2 54 44 A1 3B 99 0F DF 21 CC FE 56 E6… 0,,19806,1:11.953.026,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,19807,1:11.967.031,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19811,1:11.968.028,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,19812,1:11.968.031,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 22 44 B2 54 44 A1 3B 99 0F DF 21 CC FE 56 E6… 0,,19816,1:11.969.028,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,19817,1:11.984.033,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 74 D6 2E 62 9F E7 7F A6 B4 21 BE C2 6A E5 5E… 0,,19821,1:11.985.030,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,19822,1:11.999.035,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19826,1:12.000.032,2.833 us,,,,,[1 SOF],[Frame: 257] 0,,19827,1:12.000.036,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 74 D6 2E 62 9F E7 7F A6 B4 21 BE C2 6A E5 5E… 0,,19831,1:12.001.032,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,19832,1:12.016.038,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DA 67 8F 2D C4 AF A3 E1 5C C4 A4 3E 68 00 90 D1… 0,,19836,1:12.017.035,14.004.750 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,19837,1:12.031.040,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19841,1:12.032.037,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,19842,1:12.032.040,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DA 67 8F 2D C4 AF A3 E1 5C C4 A4 3E 68 00 90 D1… 0,,19846,1:12.033.037,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,19847,1:12.048.042,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 A1 5C EE DE 5C 90 9B 4D 61 0E 2C 73 A5 91 2B… 0,,19851,1:12.049.039,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,19852,1:12.063.044,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19856,1:12.064.041,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,19857,1:12.064.044,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 A1 5C EE DE 5C 90 9B 4D 61 0E 2C 73 A5 91 2B… 0,,19861,1:12.065.041,15.004.916 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,19862,1:12.080.047,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CB 0D C3 51 E4 74 17 86 8B 47 71 7D 44 45 4B 9B… 0,,19866,1:12.081.044,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,19867,1:12.095.049,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19871,1:12.096.046,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,19872,1:12.096.049,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CB 0D C3 51 E4 74 17 86 8B 47 71 7D 44 45 4B 9B… 0,,19876,1:12.097.046,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,19877,1:12.112.051,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 29 F5 B4 0B E2 AB B9 DD CA AC F9 E1 F2 34 8B 61… 0,,19881,1:12.113.048,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,19882,1:12.127.053,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19886,1:12.128.050,2.812 us,,,,,[1 SOF],[Frame: 385] 0,,19887,1:12.128.053,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 29 F5 B4 0B E2 AB B9 DD CA AC F9 E1 F2 34 8B 61… 0,,19891,1:12.129.050,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,19892,1:12.144.056,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD B6 CA 57 21 C4 D3 BC 7C 63 E6 44 25 5D 75 65… 0,,19896,1:12.145.052,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,19897,1:12.159.058,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19901,1:12.160.055,2.833 us,,,,,[1 SOF],[Frame: 417] 0,,19902,1:12.160.058,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD B6 CA 57 21 C4 D3 BC 7C 63 E6 44 25 5D 75 65… 0,,19906,1:12.161.055,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,19907,1:12.176.060,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F D0 37 0F 53 0E 76 05 F4 2C F7 F7 54 72 9A 85… 0,,19911,1:12.177.057,14.004.750 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,19912,1:12.191.062,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19916,1:12.192.059,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,19917,1:12.192.062,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F D0 37 0F 53 0E 76 05 F4 2C F7 F7 54 72 9A 85… 0,,19921,1:12.193.059,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,19922,1:12.208.064,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 30 D1 F2 DB 5D 2E E6 1B 52 EC 80 6A 40 87 52… 0,,19926,1:12.209.061,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,19927,1:12.223.067,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19931,1:12.224.063,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,19932,1:12.224.067,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 30 D1 F2 DB 5D 2E E6 1B 52 EC 80 6A 40 87 52… 0,,19936,1:12.225.064,15.004.916 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,19937,1:12.240.069,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8F 99 57 D2 1C 65 7E 46 4D 22 89 4A F4 D7 14 24… 0,,19941,1:12.241.066,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,19942,1:12.255.071,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19946,1:12.256.068,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,19947,1:12.256.071,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8F 99 57 D2 1C 65 7E 46 4D 22 89 4A F4 D7 14 24… 0,,19951,1:12.257.068,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,19952,1:12.272.073,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC C9 B4 C2 8B ED A6 E1 74 22 F5 46 3D C0 0B FF… 0,,19956,1:12.273.070,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,19957,1:12.287.075,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19961,1:12.288.072,16.005.041 ms,,,,,[17 SOF],[Frames: 545 - 561] 0,,19962,1:12.304.078,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 9D 15 49 9C 27 DC 86 44 B9 89 2F 22 B9 CE 96… 0,,19966,1:12.305.075,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,19967,1:12.319.080,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19971,1:12.320.077,2.833 us,,,,,[1 SOF],[Frame: 577] 0,,19972,1:12.320.080,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 9D 15 49 9C 27 DC 86 44 B9 89 2F 22 B9 CE 96… 0,,19976,1:12.321.077,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,19977,1:12.336.082,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 8C 86 59 7D A0 27 D7 C8 A8 C5 8D D8 D1 A7 92… 0,,19981,1:12.337.079,14.004.750 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,19982,1:12.351.084,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,19986,1:12.352.081,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,19987,1:12.352.084,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 8C 86 59 7D A0 27 D7 C8 A8 C5 8D D8 D1 A7 92… 0,,19991,1:12.353.081,15.004.916 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,19992,1:12.368.087,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BB 02 9B 66 6E 92 E6 5B 9A 1E 75 5C 55 B6 1D 86… 0,,19996,1:12.369.084,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,19997,1:12.383.089,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20001,1:12.384.086,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,20002,1:12.384.089,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BB 02 9B 66 6E 92 E6 5B 9A 1E 75 5C 55 B6 1D 86… 0,,20006,1:12.385.086,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,20007,1:12.400.091,50.687 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 A8 56 51 DC 9E A2 8E 24 A2 69 ED DC 22 09 94… 0,,20011,1:12.401.088,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,20012,1:12.415.093,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20016,1:12.416.090,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,20017,1:12.416.093,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 A8 56 51 DC 9E A2 8E 24 A2 69 ED DC 22 09 94… 0,,20021,1:12.417.090,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,20022,1:12.432.096,50.312 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 D0 2C 6A F0 56 CE CB 04 B1 94 41 12 5D BB E7… 0,,20026,1:12.433.092,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,20027,1:12.447.098,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20031,1:12.448.095,2.833 us,,,,,[1 SOF],[Frame: 705] 0,,20032,1:12.448.098,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 D0 2C 6A F0 56 CE CB 04 B1 94 41 12 5D BB E7… 0,,20036,1:12.449.095,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,20037,1:12.464.100,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 14 A8 AE 80 AE 7C 46 AE 67 1D 89 A3 11 8D 4C 8F… 0,,20041,1:12.465.097,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,20042,1:12.479.102,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20046,1:12.480.099,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,20047,1:12.480.102,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 14 A8 AE 80 AE 7C 46 AE 67 1D 89 A3 11 8D 4C 8F… 0,,20051,1:12.481.099,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,20052,1:12.496.104,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 96 CC 54 A9 38 F9 00 B3 DF 0A CC CE BE 39 51… 0,,20056,1:12.497.101,14.004.750 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,20057,1:12.511.107,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20061,1:12.512.103,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,20062,1:12.512.107,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2A 96 CC 54 A9 38 F9 00 B3 DF 0A CC CE BE 39 51… 0,,20066,1:12.513.104,15.004.916 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,20067,1:12.528.109,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CC 3C EC AA 7C 23 2E 93 8B A6 42 DA 84 4A 05 9F… 0,,20071,1:12.529.106,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,20072,1:12.543.111,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20076,1:12.544.108,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,20077,1:12.544.111,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CC 3C EC AA 7C 23 2E 93 8B A6 42 DA 84 4A 05 9F… 0,,20081,1:12.545.108,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,20082,1:12.560.113,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BD D0 50 FA 0E 95 FA CD 74 92 31 28 A5 64 87 2D… 0,,20086,1:12.561.110,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,20087,1:12.575.115,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20091,1:12.576.112,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,20092,1:12.576.116,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BD D0 50 FA 0E 95 FA CD 74 92 31 28 A5 64 87 2D… 0,,20096,1:12.577.112,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,20097,1:12.592.118,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 32 B5 A4 CA 9F FC F7 C8 81 8E AA AB C6 85 DC BA… 0,,20101,1:12.593.115,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,20102,1:12.607.120,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20106,1:12.608.117,2.833 us,,,,,[1 SOF],[Frame: 865] 0,,20107,1:12.608.120,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 32 B5 A4 CA 9F FC F7 C8 81 8E AA AB C6 85 DC BA… 0,,20111,1:12.609.117,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,20112,1:12.624.122,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D B6 80 1E D9 76 E0 65 70 14 95 FF 21 89 39 29… 0,,20116,1:12.625.119,14.004.750 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,20117,1:12.639.124,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20121,1:12.640.121,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,20122,1:12.640.124,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D B6 80 1E D9 76 E0 65 70 14 95 FF 21 89 39 29… 0,,20126,1:12.641.121,15.004.916 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,20127,1:12.656.127,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 72 2B 44 AF ED 17 62 A7 36 16 AD 65 69 C7 7E 79… 0,,20131,1:12.657.124,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,20132,1:12.671.129,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20136,1:12.672.126,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,20137,1:12.672.129,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 72 2B 44 AF ED 17 62 A7 36 16 AD 65 69 C7 7E 79… 0,,20141,1:12.673.126,15.004.916 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,20142,1:12.688.131,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 F4 AE 4F 47 D2 F6 97 1E C1 5D 16 99 F4 67 70… 0,,20146,1:12.689.128,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,20147,1:12.703.133,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20151,1:12.704.130,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,20152,1:12.704.133,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 F4 AE 4F 47 D2 F6 97 1E C1 5D 16 99 F4 67 70… 0,,20156,1:12.705.130,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,20157,1:12.720.136,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DB 39 CC 87 8F 6B EE EB C8 72 C1 C1 6C 16 3F 8F… 0,,20161,1:12.721.132,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,20162,1:12.735.138,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20166,1:12.736.134,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,20167,1:12.736.138,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DB 39 CC 87 8F 6B EE EB C8 72 C1 C1 6C 16 3F 8F… 0,,20171,1:12.737.135,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,20172,1:12.752.140,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 94 F4 6D 99 85 0D 86 C5 54 52 1B BD 52 81 C3 13… 0,,20176,1:12.753.137,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,20177,1:12.767.142,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20181,1:12.768.139,2.833 us,,,,,[1 SOF],[Frame: 1025] 0,,20182,1:12.768.142,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 94 F4 6D 99 85 0D 86 C5 54 52 1B BD 52 81 C3 13… 0,,20186,1:12.769.139,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,20187,1:12.784.144,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 47 8D C0 6D EB E2 D6 75 83 57 EC DE 26 D7 60… 0,,20191,1:12.785.141,14.004.750 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,20192,1:12.799.146,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20196,1:12.800.143,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,20197,1:12.800.147,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 47 8D C0 6D EB E2 D6 75 83 57 EC DE 26 D7 60… 0,,20201,1:12.801.144,15.004.916 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,20202,1:12.816.149,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 06 1A E6 CB 76 01 F4 DC 50 38 59 41 43 E2 A2 16… 0,,20206,1:12.817.146,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,20207,1:12.831.151,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20211,1:12.832.148,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,20212,1:12.832.151,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 06 1A E6 CB 76 01 F4 DC 50 38 59 41 43 E2 A2 16… 0,,20216,1:12.833.148,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,20217,1:12.848.153,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E 58 10 07 7F 52 63 19 50 67 A7 87 53 49 0D 11… 0,,20221,1:12.849.150,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,20222,1:12.863.155,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20226,1:12.864.152,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,20227,1:12.864.156,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E 58 10 07 7F 52 63 19 50 67 A7 87 53 49 0D 11… 0,,20231,1:12.865.152,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,20232,1:12.880.158,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BF 52 1C E2 C1 CC 5A 39 96 59 CF 5B F3 95 95 1E… 0,,20236,1:12.881.155,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,20237,1:12.895.160,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20241,1:12.896.157,2.916 us,,,,,[1 SOF],[Frame: 1153] 0,,20242,1:12.896.160,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BF 52 1C E2 C1 CC 5A 39 96 59 CF 5B F3 95 95 1E… 0,,20246,1:12.897.157,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,20247,1:12.912.162,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 5D 7B 48 56 3A D4 97 B5 C9 04 E8 6F 7F 6E 86… 0,,20251,1:12.913.159,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,20252,1:12.927.164,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20256,1:12.928.161,16.005.020 ms,,,,,[17 SOF],[Frames: 1185 - 1201] 0,,20257,1:12.944.167,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 67 E9 99 B4 1A 38 05 69 AD 64 3A 04 F4 71 E6 9B… 0,,20261,1:12.945.164,14.004.750 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,20262,1:12.959.169,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20266,1:12.960.166,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,20267,1:12.960.169,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 67 E9 99 B4 1A 38 05 69 AD 64 3A 04 F4 71 E6 9B… 0,,20271,1:12.961.166,15.004.916 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,20272,1:12.976.171,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 C6 C2 51 5D 49 76 7A 74 47 3B 7E 17 4E 13 61… 0,,20276,1:12.977.168,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,20277,1:12.991.173,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20281,1:12.992.170,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,20282,1:12.992.173,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 C6 C2 51 5D 49 76 7A 74 47 3B 7E 17 4E 13 61… 0,,20286,1:12.993.170,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,20287,1:13.008.176,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5F 53 C4 23 BD 4F 74 8E B9 0A 41 26 D6 DC 1E 24… 0,,20291,1:13.009.172,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,20292,1:13.023.178,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20296,1:13.024.174,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,20297,1:13.024.178,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5F 53 C4 23 BD 4F 74 8E B9 0A 41 26 D6 DC 1E 24… 0,,20301,1:13.025.175,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,20302,1:13.040.180,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D E3 77 31 4C 2D EF CD 96 2B 55 7E EB C3 93 4F… 0,,20306,1:13.041.177,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,20307,1:13.055.182,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20311,1:13.056.179,2.833 us,,,,,[1 SOF],[Frame: 1313] 0,,20312,1:13.056.182,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D E3 77 31 4C 2D EF CD 96 2B 55 7E EB C3 93 4F… 0,,20316,1:13.057.179,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,20317,1:13.072.184,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 07 9C 44 8B 04 EA AC 7F 02 99 89 6E 9C 78 A7… 0,,20321,1:13.073.181,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,20322,1:13.087.186,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20326,1:13.088.183,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,20327,1:13.088.187,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 07 9C 44 8B 04 EA AC 7F 02 99 89 6E 9C 78 A7… 0,,20331,1:13.089.183,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,20332,1:13.104.189,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4E BE 8A F7 08 C7 C5 C3 7C B3 20 AA 56 B1 8A 2E… 0,,20336,1:13.105.186,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,20337,1:13.119.191,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20341,1:13.120.188,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,20342,1:13.120.191,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4E BE 8A F7 08 C7 C5 C3 7C B3 20 AA 56 B1 8A 2E… 0,,20346,1:13.121.188,15.004.916 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,20347,1:13.136.193,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 16 8F 6C A7 3B D8 D1 C2 0C 5C 92 84 C0 66 5A 2B… 0,,20351,1:13.137.190,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,20352,1:13.151.195,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20356,1:13.152.192,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,20357,1:13.152.195,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 16 8F 6C A7 3B D8 D1 C2 0C 5C 92 84 C0 66 5A 2B… 0,,20361,1:13.153.192,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,20362,1:13.168.198,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 49 82 96 5D 1C 67 62 F7 A4 9A F5 47 B6 36 93… 0,,20366,1:13.169.195,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,20367,1:13.183.200,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20371,1:13.184.197,2.833 us,,,,,[1 SOF],[Frame: 1441] 0,,20372,1:13.184.200,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 49 82 96 5D 1C 67 62 F7 A4 9A F5 47 B6 36 93… 0,,20376,1:13.185.197,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,20377,1:13.200.202,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 31 29 EB 13 90 68 28 CB 35 F9 4A 08 DE 72 3E 29… 0,,20381,1:13.201.199,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,20382,1:13.215.204,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20386,1:13.216.201,2.833 us,,,,,[1 SOF],[Frame: 1473] 0,,20387,1:13.216.204,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 31 29 EB 13 90 68 28 CB 35 F9 4A 08 DE 72 3E 29… 0,,20391,1:13.217.201,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,20392,1:13.232.207,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FB 65 78 5F BE 1C CE 81 48 4B 71 B7 10 11 D7 27… 0,,20396,1:13.233.203,14.004.833 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,20397,1:13.247.209,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20401,1:13.248.206,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,20402,1:13.248.209,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FB 65 78 5F BE 1C CE 81 48 4B 71 B7 10 11 D7 27… 0,,20406,1:13.249.206,15.004.916 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,20407,1:13.264.211,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 02 D3 BE 02 9E 0E 81 08 4F 84 2D E9 6A 0A E6… 0,,20411,1:13.265.208,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,20412,1:13.279.213,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20416,1:13.280.210,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,20417,1:13.280.213,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 83 02 D3 BE 02 9E 0E 81 08 4F 84 2D E9 6A 0A E6… 0,,20421,1:13.281.210,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,20422,1:13.296.216,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B4 9C 3D 1B 3E 6C 99 17 39 E5 45 FF B7 F1 4E 30… 0,,20426,1:13.297.212,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,20427,1:13.311.218,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20431,1:13.312.214,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,20432,1:13.312.218,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B4 9C 3D 1B 3E 6C 99 17 39 E5 45 FF B7 F1 4E 30… 0,,20436,1:13.313.215,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,20437,1:13.328.220,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 ED 92 7D F1 7E 70 9B E0 8F 79 40 45 33 89 57… 0,,20441,1:13.329.217,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,20442,1:13.343.222,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20446,1:13.344.219,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,20447,1:13.344.222,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 ED 92 7D F1 7E 70 9B E0 8F 79 40 45 33 89 57… 0,,20451,1:13.345.219,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,20452,1:13.360.224,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 48 DB A4 5C 1F 9D 26 A3 C5 21 D5 8A 25 59 99 AB… 0,,20456,1:13.361.221,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,20457,1:13.375.226,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20461,1:13.376.223,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,20462,1:13.376.227,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 48 DB A4 5C 1F 9D 26 A3 C5 21 D5 8A 25 59 99 AB… 0,,20466,1:13.377.223,15.004.895 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,20467,1:13.392.229,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C1 23 1E 4C 27 4D EC E1 8C 3E D5 9E 29 29 97 DD… 0,,20471,1:13.393.226,14.004.750 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,20472,1:13.407.231,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20476,1:13.408.228,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,20477,1:13.408.231,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C1 23 1E 4C 27 4D EC E1 8C 3E D5 9E 29 29 97 DD… 0,,20481,1:13.409.228,15.004.916 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,20482,1:13.424.233,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1B 47 8C 4D 58 22 C5 E7 EB F9 54 71 B3 0A 5C 6B… 0,,20486,1:13.425.230,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,20487,1:13.439.235,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20491,1:13.440.232,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,20492,1:13.440.235,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1B 47 8C 4D 58 22 C5 E7 EB F9 54 71 B3 0A 5C 6B… 0,,20496,1:13.441.232,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,20497,1:13.456.238,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 AB FC 72 F4 51 93 59 D1 6E 46 B7 42 67 BB E7… 0,,20501,1:13.457.235,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,20502,1:13.471.240,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20506,1:13.472.237,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,20507,1:13.472.240,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 AB FC 72 F4 51 93 59 D1 6E 46 B7 42 67 BB E7… 0,,20511,1:13.473.237,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,20512,1:13.488.242,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3B 59 06 9F FF A3 0B 67 B3 1E C3 47 F1 26 37 94… 0,,20516,1:13.489.239,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,20517,1:13.503.244,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20521,1:13.504.241,2.916 us,,,,,[1 SOF],[Frame: 1761] 0,,20522,1:13.504.244,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3B 59 06 9F FF A3 0B 67 B3 1E C3 47 F1 26 37 94… 0,,20526,1:13.505.241,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,20527,1:13.520.247,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 31 7A 6C 5F 37 F1 E5 6D 67 98 02 74 46 52 FE… 0,,20531,1:13.521.243,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,20532,1:13.535.249,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20536,1:13.536.246,16.005.041 ms,,,,,[17 SOF],[Frames: 1793 - 1809] 0,,20537,1:13.552.251,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D3 46 E6 F2 A2 60 38 B1 7C A6 99 4F 81 2D B1 3B… 0,,20541,1:13.553.248,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,20542,1:13.567.253,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20546,1:13.568.250,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,20547,1:13.568.253,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D3 46 E6 F2 A2 60 38 B1 7C A6 99 4F 81 2D B1 3B… 0,,20551,1:13.569.250,15.004.916 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,20552,1:13.584.255,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 07 FC 18 32 17 3C DF 42 39 8B DE 10 46 48 B4… 0,,20556,1:13.585.252,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,20557,1:13.599.258,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20561,1:13.600.254,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,20562,1:13.600.258,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 07 FC 18 32 17 3C DF 42 39 8B DE 10 46 48 B4… 0,,20566,1:13.601.255,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,20567,1:13.616.260,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 97 8A 86 DC ED 40 03 DE A4 81 F9 77 E9 E2 1E 9B… 0,,20571,1:13.617.257,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,20572,1:13.631.262,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20576,1:13.632.259,16.005.041 ms,,,,,[17 SOF],[Frames: 1889 - 1905] 0,,20577,1:13.648.264,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0F 9D 62 3A 92 A7 E4 8D 2C 6B 45 E7 28 69 D9 D9… 0,,20581,1:13.649.261,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,20582,1:13.663.267,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20586,1:13.664.263,2.833 us,,,,,[1 SOF],[Frame: 1921] 0,,20587,1:13.664.267,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0F 9D 62 3A 92 A7 E4 8D 2C 6B 45 E7 28 69 D9 D9… 0,,20591,1:13.665.263,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,20592,1:13.680.269,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 04 4B 95 5F 50 05 F3 A2 83 C9 DF 32 17 63 FB 32… 0,,20596,1:13.681.266,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,20597,1:13.695.271,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20601,1:13.696.268,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,20602,1:13.696.271,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 04 4B 95 5F 50 05 F3 A2 83 C9 DF 32 17 63 FB 32… 0,,20606,1:13.697.268,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,20607,1:13.712.273,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0E 33 4E 28 7A 72 3F 79 29 E6 63 1C 34 F8 6F 57… 0,,20611,1:13.713.270,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,20612,1:13.727.275,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20616,1:13.728.272,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,20617,1:13.728.276,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0E 33 4E 28 7A 72 3F 79 29 E6 63 1C 34 F8 6F 57… 0,,20621,1:13.729.272,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,20622,1:13.744.278,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 88 63 FF 53 9F 63 84 B2 26 91 71 79 6F 18 72… 0,,20626,1:13.745.275,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,20627,1:13.759.280,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20631,1:13.760.277,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,20632,1:13.760.280,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 88 63 FF 53 9F 63 84 B2 26 91 71 79 6F 18 72… 0,,20636,1:13.761.277,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,20637,1:13.776.282,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE 15 BF 71 4C 95 BC 45 26 D8 7C 80 0D FA 78 F8… 0,,20641,1:13.777.279,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,20642,1:13.791.284,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20646,1:13.792.281,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,20647,1:13.792.284,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE 15 BF 71 4C 95 BC 45 26 D8 7C 80 0D FA 78 F8… 0,,20651,1:13.793.281,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,20652,1:13.808.287,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 32 2F 65 B1 C0 97 A0 DA E4 B9 04 3B 47 47 22… 0,,20656,1:13.809.283,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,20657,1:13.823.289,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20661,1:13.824.286,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,20662,1:13.824.289,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 32 2F 65 B1 C0 97 A0 DA E4 B9 04 3B 47 47 22… 0,,20666,1:13.825.286,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,20667,1:13.840.291,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 88 16 89 95 61 AB 9D EA 12 A0 10 47 9A D1 6A 6D… 0,,20671,1:13.841.288,14.004.750 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,20672,1:13.855.293,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20676,1:13.856.290,2.812 us,,,,,[1 SOF],[Frame: 65] 0,,20677,1:13.856.293,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 88 16 89 95 61 AB 9D EA 12 A0 10 47 9A D1 6A 6D… 0,,20681,1:13.857.290,15.004.916 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,20682,1:13.872.295,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 60 33 AB 7F A0 97 8C D8 D9 E9 D7 61 56 B2 E9 4C… 0,,20686,1:13.873.292,14.004.770 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,20687,1:13.887.298,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20691,1:13.888.294,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,20692,1:13.888.298,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 60 33 AB 7F A0 97 8C D8 D9 E9 D7 61 56 B2 E9 4C… 0,,20696,1:13.889.295,15.004.895 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,20697,1:13.904.300,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D F8 B7 CC D0 E6 01 05 81 19 08 71 AE 75 57 77… 0,,20701,1:13.905.297,14.004.770 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,20702,1:13.919.302,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20706,1:13.920.299,2.833 us,,,,,[1 SOF],[Frame: 129] 0,,20707,1:13.920.302,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D F8 B7 CC D0 E6 01 05 81 19 08 71 AE 75 57 77… 0,,20711,1:13.921.299,15.004.895 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,20712,1:13.936.304,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0D E2 10 C3 04 10 84 3B 8D 36 E4 AD 21 E2 97 BB… 0,,20716,1:13.937.301,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,20717,1:13.951.306,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20721,1:13.952.303,2.833 us,,,,,[1 SOF],[Frame: 161] 0,,20722,1:13.952.307,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0D E2 10 C3 04 10 84 3B 8D 36 E4 AD 21 E2 97 BB… 0,,20726,1:13.953.303,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,20727,1:13.968.309,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A DF EE 09 F5 F4 EB AE A2 80 89 D4 22 89 68 71… 0,,20731,1:13.969.306,14.004.750 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,20732,1:13.983.311,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20736,1:13.984.308,2.812 us,,,,,[1 SOF],[Frame: 193] 0,,20737,1:13.984.311,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A DF EE 09 F5 F4 EB AE A2 80 89 D4 22 89 68 71… 0,,20741,1:13.985.308,15.004.916 ms,,,,,[16 SOF],[Frames: 194 - 209] 0,,20742,1:14.000.313,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 EC FC 01 A8 42 4A 30 BF C0 1C D4 71 1B FB 17… 0,,20746,1:14.001.310,14.004.750 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,20747,1:14.015.315,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20751,1:14.016.312,2.812 us,,,,,[1 SOF],[Frame: 225] 0,,20752,1:14.016.315,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 EC FC 01 A8 42 4A 30 BF C0 1C D4 71 1B FB 17… 0,,20756,1:14.017.312,15.004.916 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,20757,1:14.032.318,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 CF B9 8B 71 DB F4 9C 48 B8 3F 46 F2 2A 3A 07 91… 0,,20761,1:14.033.315,14.004.770 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,20762,1:14.047.320,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20766,1:14.048.317,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,20767,1:14.048.320,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 CF B9 8B 71 DB F4 9C 48 B8 3F 46 F2 2A 3A 07 91… 0,,20771,1:14.049.317,15.004.895 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,20772,1:14.064.322,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 47 44 F8 65 20 F3 4B 67 5B F4 EE 63 DB DD C9… 0,,20776,1:14.065.319,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,20777,1:14.079.324,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20781,1:14.080.321,2.833 us,,,,,[1 SOF],[Frame: 289] 0,,20782,1:14.080.324,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 47 44 F8 65 20 F3 4B 67 5B F4 EE 63 DB DD C9… 0,,20786,1:14.081.321,15.004.895 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,20787,1:14.096.327,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C8 E0 BD A5 59 01 65 0A 31 C8 C6 D5 7D 69 68 48… 0,,20791,1:14.097.323,14.004.750 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,20792,1:14.111.329,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20796,1:14.112.325,2.833 us,,,,,[1 SOF],[Frame: 321] 0,,20797,1:14.112.329,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 C8 E0 BD A5 59 01 65 0A 31 C8 C6 D5 7D 69 68 48… 0,,20801,1:14.113.326,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,20802,1:14.128.331,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 D4 88 FA 6D D4 DC 95 E1 26 88 1F 73 A0 CD 9A… 0,,20806,1:14.129.328,14.004.750 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,20807,1:14.143.333,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20811,1:14.144.330,2.812 us,,,,,[1 SOF],[Frame: 353] 0,,20812,1:14.144.333,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 D4 88 FA 6D D4 DC 95 E1 26 88 1F 73 A0 CD 9A… 0,,20816,1:14.145.330,15.004.916 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,20817,1:14.160.335,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 3F 16 1E 42 B3 54 61 9F B5 DC 2A A5 6A 05 DE… 0,,20821,1:14.161.332,14.004.770 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,20822,1:14.175.337,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20826,1:14.176.334,16.005.041 ms,,,,,[17 SOF],[Frames: 385 - 401] 0,,20827,1:14.192.340,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D BF CC 9C A0 9A 9A B0 D9 EC 6E 90 D8 B5 E4 5D… 0,,20831,1:14.193.337,14.004.770 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,20832,1:14.207.342,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20836,1:14.208.339,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,20837,1:14.208.342,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D BF CC 9C A0 9A 9A B0 D9 EC 6E 90 D8 B5 E4 5D… 0,,20841,1:14.209.339,15.004.895 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,20842,1:14.224.344,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 91 A2 A1 7F 6C F5 23 E1 4F 2A F8 42 74 A0 32… 0,,20846,1:14.225.341,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,20847,1:14.239.346,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20851,1:14.240.343,2.833 us,,,,,[1 SOF],[Frame: 449] 0,,20852,1:14.240.347,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 91 A2 A1 7F 6C F5 23 E1 4F 2A F8 42 74 A0 32… 0,,20856,1:14.241.343,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,20857,1:14.256.349,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A5 DB E5 E8 DA FA 2D 63 3E 41 4A 34 C2 F7 EA C0… 0,,20861,1:14.257.346,14.004.750 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,20862,1:14.271.351,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20866,1:14.272.348,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,20867,1:14.272.351,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A5 DB E5 E8 DA FA 2D 63 3E 41 4A 34 C2 F7 EA C0… 0,,20871,1:14.273.348,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,20872,1:14.288.353,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 54 8B C2 FB 87 39 26 4D 99 29 22 7E C6 42 F7… 0,,20876,1:14.289.350,14.004.750 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,20877,1:14.303.355,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20881,1:14.304.352,2.812 us,,,,,[1 SOF],[Frame: 513] 0,,20882,1:14.304.355,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 54 8B C2 FB 87 39 26 4D 99 29 22 7E C6 42 F7… 0,,20886,1:14.305.352,15.004.916 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,20887,1:14.320.358,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6E 8E F9 6C D1 BF 3A 65 30 5E 71 0E 49 A9 5D 31… 0,,20891,1:14.321.355,14.004.770 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,20892,1:14.335.360,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20896,1:14.336.357,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,20897,1:14.336.360,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6E 8E F9 6C D1 BF 3A 65 30 5E 71 0E 49 A9 5D 31… 0,,20901,1:14.337.357,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,20902,1:14.352.362,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 39 73 BD A6 B8 74 2A 31 B2 2E 37 81 2D 8F C4 95… 0,,20906,1:14.353.359,14.004.770 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,20907,1:14.367.364,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20911,1:14.368.361,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,20912,1:14.368.364,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 39 73 BD A6 B8 74 2A 31 B2 2E 37 81 2D 8F C4 95… 0,,20916,1:14.369.361,15.004.895 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,20917,1:14.384.367,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 23 41 61 82 54 97 64 0E A3 93 30 38 39 02 CF 4D… 0,,20921,1:14.385.363,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,20922,1:14.399.369,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20926,1:14.400.365,2.833 us,,,,,[1 SOF],[Frame: 609] 0,,20927,1:14.400.369,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 23 41 61 82 54 97 64 0E A3 93 30 38 39 02 CF 4D… 0,,20931,1:14.401.366,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,20932,1:14.416.371,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 4D 91 D4 0F 75 94 95 D4 E2 72 6D BD CF DE 1F… 0,,20936,1:14.417.368,14.004.750 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,20937,1:14.431.373,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20941,1:14.432.370,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,20942,1:14.432.373,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 4D 91 D4 0F 75 94 95 D4 E2 72 6D BD CF DE 1F… 0,,20946,1:14.433.370,15.004.916 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,20947,1:14.448.375,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2C 2D F7 42 1D F4 AD FB 0A 13 DE 16 81 EB EE 3F… 0,,20951,1:14.449.372,14.004.750 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,20952,1:14.463.377,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20956,1:14.464.374,2.812 us,,,,,[1 SOF],[Frame: 673] 0,,20957,1:14.464.378,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2C 2D F7 42 1D F4 AD FB 0A 13 DE 16 81 EB EE 3F… 0,,20961,1:14.465.374,15.004.916 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,20962,1:14.480.380,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2D 94 A4 8D E6 C8 EB FF FC 90 B5 E7 01 A7 17 E2… 0,,20966,1:14.481.377,14.004.770 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,20967,1:14.495.382,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20971,1:14.496.379,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,20972,1:14.496.382,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2D 94 A4 8D E6 C8 EB FF FC 90 B5 E7 01 A7 17 E2… 0,,20976,1:14.497.379,15.004.895 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,20977,1:14.512.384,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 79 06 7F 0A 8F B1 B8 55 AC F1 04 AE C8 70 33 37… 0,,20981,1:14.513.381,14.004.770 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,20982,1:14.527.386,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,20986,1:14.528.383,2.833 us,,,,,[1 SOF],[Frame: 737] 0,,20987,1:14.528.386,50.687 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 79 06 7F 0A 8F B1 B8 55 AC F1 04 AE C8 70 33 37… 0,,20991,1:14.529.383,15.004.895 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,20992,1:14.544.389,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D1 43 C7 1F 09 71 BD 90 01 01 A6 35 62 E0 FD 53… 0,,20996,1:14.545.386,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,20997,1:14.559.391,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21001,1:14.560.388,2.833 us,,,,,[1 SOF],[Frame: 769] 0,,21002,1:14.560.391,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D1 43 C7 1F 09 71 BD 90 01 01 A6 35 62 E0 FD 53… 0,,21006,1:14.561.388,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,21007,1:14.576.393,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD E9 BA 67 6E CC 10 27 C7 37 F0 B4 FA 89 97 3B… 0,,21011,1:14.577.390,14.004.750 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,21012,1:14.591.395,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21016,1:14.592.392,2.812 us,,,,,[1 SOF],[Frame: 801] 0,,21017,1:14.592.395,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DD E9 BA 67 6E CC 10 27 C7 37 F0 B4 FA 89 97 3B… 0,,21021,1:14.593.392,15.004.916 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,21022,1:14.608.398,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 68 8A 13 25 18 69 82 95 8F 21 5E 7E C3 B6 10 FD… 0,,21026,1:14.609.394,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,21027,1:14.623.400,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21031,1:14.624.397,2.812 us,,,,,[1 SOF],[Frame: 833] 0,,21032,1:14.624.400,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 68 8A 13 25 18 69 82 95 8F 21 5E 7E C3 B6 10 FD… 0,,21036,1:14.625.397,15.004.895 ms,,,,,[16 SOF],[Frames: 834 - 849] 0,,21037,1:14.640.402,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 30 E5 33 EB F7 A9 7F CC 16 33 87 50 CE 98 8C 10… 0,,21041,1:14.641.399,14.004.770 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,21042,1:14.655.404,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21046,1:14.656.401,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,21047,1:14.656.404,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 30 E5 33 EB F7 A9 7F CC 16 33 87 50 CE 98 8C 10… 0,,21051,1:14.657.401,15.004.895 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,21052,1:14.672.406,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D F3 87 0D F4 E7 9F 63 3E D0 88 D1 B1 A9 63 24… 0,,21056,1:14.673.403,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,21057,1:14.687.409,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21061,1:14.688.405,2.833 us,,,,,[1 SOF],[Frame: 897] 0,,21062,1:14.688.409,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D F3 87 0D F4 E7 9F 63 3E D0 88 D1 B1 A9 63 24… 0,,21066,1:14.689.406,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,21067,1:14.704.411,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D7 45 E9 1D 4B 3D 33 1F A0 61 65 20 83 82 EB B8… 0,,21071,1:14.705.408,14.004.750 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,21072,1:14.719.413,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21076,1:14.720.410,2.833 us,,,,,[1 SOF],[Frame: 929] 0,,21077,1:14.720.413,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D7 45 E9 1D 4B 3D 33 1F A0 61 65 20 83 82 EB B8… 0,,21081,1:14.721.410,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,21082,1:14.736.415,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 E6 C0 28 7C 44 21 7C EE CC E9 08 2A C1 BC 0D… 0,,21086,1:14.737.412,14.004.750 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,21087,1:14.751.417,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21091,1:14.752.414,2.812 us,,,,,[1 SOF],[Frame: 961] 0,,21092,1:14.752.418,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 E6 C0 28 7C 44 21 7C EE CC E9 08 2A C1 BC 0D… 0,,21096,1:14.753.414,15.004.916 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,21097,1:14.768.420,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 C5 66 20 1A 85 55 45 0C D1 4F 9A 78 12 AA 7A 62… 0,,21101,1:14.769.417,14.004.770 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,21102,1:14.783.422,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21106,1:14.784.419,16.005.125 ms,,,,,[17 SOF],[Frames: 993 - 1009] 0,,21107,1:14.800.424,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 36 CC 29 A8 FA A0 81 CF 9A 91 1F B4 51 C1 A4 D4… 0,,21111,1:14.801.421,14.004.770 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,21112,1:14.815.426,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21116,1:14.816.423,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,21117,1:14.816.426,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 36 CC 29 A8 FA A0 81 CF 9A 91 1F B4 51 C1 A4 D4… 0,,21121,1:14.817.423,15.004.895 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,21122,1:14.832.429,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC F5 07 17 4E 6A 6D D9 98 9B C6 79 FB 7F F1 78… 0,,21126,1:14.833.426,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,21127,1:14.847.431,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21131,1:14.848.428,2.833 us,,,,,[1 SOF],[Frame: 1057] 0,,21132,1:14.848.431,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC F5 07 17 4E 6A 6D D9 98 9B C6 79 FB 7F F1 78… 0,,21136,1:14.849.428,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,21137,1:14.864.433,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6F C4 E6 66 07 55 80 8B A7 A8 CC 97 C9 83 56 53… 0,,21141,1:14.865.430,14.004.750 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,21142,1:14.879.435,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21146,1:14.880.432,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,21147,1:14.880.435,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6F C4 E6 66 07 55 80 8B A7 A8 CC 97 C9 83 56 53… 0,,21151,1:14.881.432,15.004.916 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,21152,1:14.896.438,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A4 C9 7C B0 EC FE 72 4A 36 35 F7 23 85 39 A4 79… 0,,21156,1:14.897.434,14.004.750 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,21157,1:14.911.440,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21161,1:14.912.437,2.812 us,,,,,[1 SOF],[Frame: 1121] 0,,21162,1:14.912.440,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A4 C9 7C B0 EC FE 72 4A 36 35 F7 23 85 39 A4 79… 0,,21166,1:14.913.437,15.004.916 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,21167,1:14.928.442,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 78 0C A9 A4 8C 95 30 CE 57 23 BE 4B AC FF 74… 0,,21171,1:14.929.439,14.004.770 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,21172,1:14.943.444,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21176,1:14.944.441,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,21177,1:14.944.444,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 78 0C A9 A4 8C 95 30 CE 57 23 BE 4B AC FF 74… 0,,21181,1:14.945.441,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,21182,1:14.960.446,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 0A EA 31 40 55 FF A6 C5 FA 22 37 69 EC 3F 3D E3… 0,,21186,1:14.961.443,14.004.770 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,21187,1:14.975.449,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21191,1:14.976.445,2.833 us,,,,,[1 SOF],[Frame: 1185] 0,,21192,1:14.976.449,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 0A EA 31 40 55 FF A6 C5 FA 22 37 69 EC 3F 3D E3… 0,,21196,1:14.977.446,15.004.895 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,21197,1:14.992.451,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B6 9C 6C EE D4 98 03 CE 55 77 13 95 DC 31 1C C4… 0,,21201,1:14.993.448,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,21202,1:15.007.453,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21206,1:15.008.450,2.833 us,,,,,[1 SOF],[Frame: 1217] 0,,21207,1:15.008.453,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B6 9C 6C EE D4 98 03 CE 55 77 13 95 DC 31 1C C4… 0,,21211,1:15.009.450,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,21212,1:15.024.455,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EF 4E 6D BC 3E 2B B9 51 8F 9A B9 EB E4 20 DD 83… 0,,21216,1:15.025.452,14.004.750 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,21217,1:15.039.457,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21221,1:15.040.454,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,21222,1:15.040.458,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 EF 4E 6D BC 3E 2B B9 51 8F 9A B9 EB E4 20 DD 83… 0,,21226,1:15.041.454,15.004.916 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,21227,1:15.056.460,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A E7 8F BA D5 27 AD B1 41 F0 E3 73 6D 18 7C 17… 0,,21231,1:15.057.457,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,21232,1:15.071.462,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21236,1:15.072.459,2.812 us,,,,,[1 SOF],[Frame: 1281] 0,,21237,1:15.072.462,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A E7 8F BA D5 27 AD B1 41 F0 E3 73 6D 18 7C 17… 0,,21241,1:15.073.459,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,21242,1:15.088.464,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 69 5A CF EE 0E AA 62 40 09 2A 1C EC 94 D1 A4 2D… 0,,21246,1:15.089.461,14.004.770 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,21247,1:15.103.466,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21251,1:15.104.463,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,21252,1:15.104.466,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 69 5A CF EE 0E AA 62 40 09 2A 1C EC 94 D1 A4 2D… 0,,21256,1:15.105.463,15.004.895 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,21257,1:15.120.469,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A2 7C 12 43 0A C2 EB 80 4A ED 40 D8 6C 39 95 34… 0,,21261,1:15.121.466,14.004.770 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,21262,1:15.135.471,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21266,1:15.136.468,2.833 us,,,,,[1 SOF],[Frame: 1345] 0,,21267,1:15.136.471,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A2 7C 12 43 0A C2 EB 80 4A ED 40 D8 6C 39 95 34… 0,,21271,1:15.137.468,15.004.895 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,21272,1:15.152.473,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4B E4 EB 24 2A 68 D3 6F BD 53 5F 21 02 6C 9B 8F… 0,,21276,1:15.153.470,14.004.750 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,21277,1:15.167.475,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21281,1:15.168.472,2.833 us,,,,,[1 SOF],[Frame: 1377] 0,,21282,1:15.168.475,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4B E4 EB 24 2A 68 D3 6F BD 53 5F 21 02 6C 9B 8F… 0,,21286,1:15.169.472,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,21287,1:15.184.478,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9B FF F4 08 0D C5 18 F3 CA CD 83 0F 1C 8D E7 C3… 0,,21291,1:15.185.474,14.004.750 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,21292,1:15.199.480,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21296,1:15.200.477,2.812 us,,,,,[1 SOF],[Frame: 1409] 0,,21297,1:15.200.480,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9B FF F4 08 0D C5 18 F3 CA CD 83 0F 1C 8D E7 C3… 0,,21301,1:15.201.477,15.004.916 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,21302,1:15.216.482,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D CA E9 F3 D2 F1 A1 99 AA C2 97 C8 55 22 D4 AC… 0,,21306,1:15.217.479,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,21307,1:15.231.484,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21311,1:15.232.481,2.812 us,,,,,[1 SOF],[Frame: 1441] 0,,21312,1:15.232.484,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D CA E9 F3 D2 F1 A1 99 AA C2 97 C8 55 22 D4 AC… 0,,21316,1:15.233.481,15.004.895 ms,,,,,[16 SOF],[Frames: 1442 - 1457] 0,,21317,1:15.248.486,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1C 90 FF 08 CA F0 AD E5 5A A9 48 26 BB 82 22 81… 0,,21321,1:15.249.483,14.004.770 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,21322,1:15.263.489,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21326,1:15.264.485,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,21327,1:15.264.489,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1C 90 FF 08 CA F0 AD E5 5A A9 48 26 BB 82 22 81… 0,,21331,1:15.265.486,15.004.895 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,21332,1:15.280.491,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 5A 0F 28 5F 1D 84 82 BB 4F E8 5F 32 A9 E0 05 71… 0,,21336,1:15.281.488,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,21337,1:15.295.493,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21341,1:15.296.490,2.833 us,,,,,[1 SOF],[Frame: 1505] 0,,21342,1:15.296.493,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 5A 0F 28 5F 1D 84 82 BB 4F E8 5F 32 A9 E0 05 71… 0,,21346,1:15.297.490,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,21347,1:15.312.495,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 4F D2 0D B9 0C 7B 01 D3 C2 74 18 AB E8 61 22 B3… 0,,21351,1:15.313.492,14.004.750 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,21352,1:15.327.497,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21356,1:15.328.494,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,21357,1:15.328.498,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 4F D2 0D B9 0C 7B 01 D3 C2 74 18 AB E8 61 22 B3… 0,,21361,1:15.329.494,15.005.000 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,21362,1:15.344.500,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 9C FC 8C 98 C5 D9 FC 19 60 EA FF 74 8C A8 73 A6… 0,,21366,1:15.345.497,14.004.750 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,21367,1:15.359.502,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21371,1:15.360.499,2.812 us,,,,,[1 SOF],[Frame: 1569] 0,,21372,1:15.360.502,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 9C FC 8C 98 C5 D9 FC 19 60 EA FF 74 8C A8 73 A6… 0,,21376,1:15.361.499,15.004.916 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,21377,1:15.376.504,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 94 D0 29 8F A8 1F 36 71 D2 09 29 9B EC 9E 36… 0,,21381,1:15.377.501,14.004.770 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,21382,1:15.391.506,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21386,1:15.392.503,2.812 us,,,,,[1 SOF],[Frame: 1601] 0,,21387,1:15.392.506,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 94 D0 29 8F A8 1F 36 71 D2 09 29 9B EC 9E 36… 0,,21391,1:15.393.503,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,21392,1:15.408.509,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E 6F 7E 4D 0A 19 3E 84 F2 F4 23 D6 99 C8 5F 7C… 0,,21396,1:15.409.506,14.004.770 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,21397,1:15.423.511,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21401,1:15.424.508,16.005.041 ms,,,,,[17 SOF],[Frames: 1633 - 1649] 0,,21402,1:15.440.513,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 99 79 FA 26 25 B1 E2 FE 60 5C 3F E4 3E 8C C9… 0,,21406,1:15.441.510,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,21407,1:15.455.515,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21411,1:15.456.512,2.833 us,,,,,[1 SOF],[Frame: 1665] 0,,21412,1:15.456.515,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 99 79 FA 26 25 B1 E2 FE 60 5C 3F E4 3E 8C C9… 0,,21416,1:15.457.512,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,21417,1:15.472.518,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 57 87 DA 4D F9 23 FD BD 60 20 43 FA AD A7 4B 13… 0,,21421,1:15.473.514,14.004.750 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,21422,1:15.487.520,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21426,1:15.488.517,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,21427,1:15.488.520,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 57 87 DA 4D F9 23 FD BD 60 20 43 FA AD A7 4B 13… 0,,21431,1:15.489.517,15.004.916 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,21432,1:15.504.522,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 FC 4F 07 2A 20 D2 5D C4 D9 1B 24 9A 6F D5 DE 94… 0,,21436,1:15.505.519,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,21437,1:15.519.524,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21441,1:15.520.521,2.812 us,,,,,[1 SOF],[Frame: 1729] 0,,21442,1:15.520.524,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 FC 4F 07 2A 20 D2 5D C4 D9 1B 24 9A 6F D5 DE 94… 0,,21446,1:15.521.521,15.004.916 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,21447,1:15.536.526,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E7 99 96 87 38 8E 44 E3 7C 26 BA 0A 12 46 CA 82… 0,,21451,1:15.537.523,14.004.770 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,21452,1:15.551.528,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21456,1:15.552.525,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,21457,1:15.552.529,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E7 99 96 87 38 8E 44 E3 7C 26 BA 0A 12 46 CA 82… 0,,21461,1:15.553.526,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,21462,1:15.568.531,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 77 1E 0E 4F 2E D3 35 B5 81 18 B8 44 1D 88 23… 0,,21466,1:15.569.528,14.004.770 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,21467,1:15.583.533,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21471,1:15.584.530,2.833 us,,,,,[1 SOF],[Frame: 1793] 0,,21472,1:15.584.533,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 77 1E 0E 4F 2E D3 35 B5 81 18 B8 44 1D 88 23… 0,,21476,1:15.585.530,15.004.895 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,21477,1:15.600.535,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DC 18 AF F9 3F 69 90 47 94 7A AA D8 BF 1A B6 0F… 0,,21481,1:15.601.532,14.004.750 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,21482,1:15.615.537,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21486,1:15.616.534,2.916 us,,,,,[1 SOF],[Frame: 1825] 0,,21487,1:15.616.538,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DC 18 AF F9 3F 69 90 47 94 7A AA D8 BF 1A B6 0F… 0,,21491,1:15.617.534,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,21492,1:15.632.540,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 75 E8 0A C9 05 FA 44 20 9C BC 21 91 41 36 32 57… 0,,21496,1:15.633.537,14.004.750 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,21497,1:15.647.542,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21501,1:15.648.539,2.812 us,,,,,[1 SOF],[Frame: 1857] 0,,21502,1:15.648.542,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 75 E8 0A C9 05 FA 44 20 9C BC 21 91 41 36 32 57… 0,,21506,1:15.649.539,15.004.916 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,21507,1:15.664.544,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 62 E8 93 28 C8 9E 42 FC 8A 63 75 72 7C 20 7A E2… 0,,21511,1:15.665.541,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,21512,1:15.679.546,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21516,1:15.680.543,2.812 us,,,,,[1 SOF],[Frame: 1889] 0,,21517,1:15.680.546,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 62 E8 93 28 C8 9E 42 FC 8A 63 75 72 7C 20 7A E2… 0,,21521,1:15.681.543,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,21522,1:15.696.549,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 47 0C E3 8B C1 8B EB 8D 1B F5 7E C3 77 42 94… 0,,21526,1:15.697.546,14.004.854 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,21527,1:15.711.551,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21531,1:15.712.548,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,21532,1:15.712.551,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 47 0C E3 8B C1 8B EB 8D 1B F5 7E C3 77 42 94… 0,,21536,1:15.713.548,15.004.895 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,21537,1:15.728.553,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 83 EC 17 C7 F4 D3 28 FB 8D EA 13 18 FC BE 4B C9… 0,,21541,1:15.729.550,14.004.770 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,21542,1:15.743.555,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21546,1:15.744.552,2.833 us,,,,,[1 SOF],[Frame: 1953] 0,,21547,1:15.744.555,50.833 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 83 EC 17 C7 F4 D3 28 FB 8D EA 13 18 FC BE 4B C9… 0,,21551,1:15.745.552,15.004.895 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,21552,1:15.760.558,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E3 45 A9 2A FB 11 B6 59 2B 60 AC B7 9A 05 0B 6B… 0,,21556,1:15.761.554,14.004.750 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,21557,1:15.775.560,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21561,1:15.776.556,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,21562,1:15.776.560,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E3 45 A9 2A FB 11 B6 59 2B 60 AC B7 9A 05 0B 6B… 0,,21566,1:15.777.557,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,21567,1:15.792.562,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B8 DB 28 70 76 41 D7 4F 9C 59 99 7B 75 3E 25 AC… 0,,21571,1:15.793.559,14.004.833 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,21572,1:15.807.564,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21576,1:15.808.561,2.895 us,,,,,[1 SOF],[Frame: 2017] 0,,21577,1:15.808.564,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B8 DB 28 70 76 41 D7 4F 9C 59 99 7B 75 3E 25 AC… 0,,21581,1:15.809.561,15.005.000 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,21582,1:15.824.566,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 18 61 8F D9 89 25 6C 31 42 7E 34 92 B6 EF 24 24… 0,,21586,1:15.825.563,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,21587,1:15.839.568,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21591,1:15.840.565,2.812 us,,,,,[1 SOF],[Frame: 1] 0,,21592,1:15.840.569,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 18 61 8F D9 89 25 6C 31 42 7E 34 92 B6 EF 24 24… 0,,21596,1:15.841.566,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,21597,1:15.856.571,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 53 28 AC FB 39 17 19 45 DD 5B 3D C2 9A D6 86 99… 0,,21601,1:15.857.568,14.004.770 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,21602,1:15.871.573,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21606,1:15.872.570,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,21607,1:15.872.573,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 53 28 AC FB 39 17 19 45 DD 5B 3D C2 9A D6 86 99… 0,,21611,1:15.873.570,15.004.895 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,21612,1:15.888.575,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E4 17 EA 04 90 1F A4 55 E9 AB F5 2D 34 73 79 42… 0,,21616,1:15.889.572,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,21617,1:15.903.577,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21621,1:15.904.574,2.833 us,,,,,[1 SOF],[Frame: 65] 0,,21622,1:15.904.577,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E4 17 EA 04 90 1F A4 55 E9 AB F5 2D 34 73 79 42… 0,,21626,1:15.905.574,15.004.895 ms,,,,,[16 SOF],[Frames: 66 - 81] 0,,21627,1:15.920.580,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E F3 AE 8F A7 67 2D F6 B2 FC 29 D5 8D 3F 97 97… 0,,21631,1:15.921.577,14.004.750 ms,,,,,[15 SOF],[Frames: 82 - 96] 0,,21632,1:15.935.582,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21636,1:15.936.579,2.812 us,,,,,[1 SOF],[Frame: 97] 0,,21637,1:15.936.582,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E F3 AE 8F A7 67 2D F6 B2 FC 29 D5 8D 3F 97 97… 0,,21641,1:15.937.579,15.004.916 ms,,,,,[16 SOF],[Frames: 98 - 113] 0,,21642,1:15.952.584,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 51 58 55 F6 2E 37 4F C6 9B 87 03 97 AC DD BB 40… 0,,21646,1:15.953.581,14.004.750 ms,,,,,[15 SOF],[Frames: 114 - 128] 0,,21647,1:15.967.586,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21651,1:15.968.583,2.812 us,,,,,[1 SOF],[Frame: 129] 0,,21652,1:15.968.586,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 51 58 55 F6 2E 37 4F C6 9B 87 03 97 AC DD BB 40… 0,,21656,1:15.969.583,15.004.916 ms,,,,,[16 SOF],[Frames: 130 - 145] 0,,21657,1:15.984.589,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F4 3A 34 E1 66 90 2A BA 8F B3 B8 CD EA 03 44 42… 0,,21661,1:15.985.585,14.004.770 ms,,,,,[15 SOF],[Frames: 146 - 160] 0,,21662,1:15.999.591,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21666,1:16.000.588,2.812 us,,,,,[1 SOF],[Frame: 161] 0,,21667,1:16.000.591,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F4 3A 34 E1 66 90 2A BA 8F B3 B8 CD EA 03 44 42… 0,,21671,1:16.001.588,15.004.895 ms,,,,,[16 SOF],[Frames: 162 - 177] 0,,21672,1:16.016.593,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DD AA 61 68 B4 EE 54 6A 7C 51 11 99 AB 6A F5 55… 0,,21676,1:16.017.590,14.004.770 ms,,,,,[15 SOF],[Frames: 178 - 192] 0,,21677,1:16.031.595,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21681,1:16.032.592,16.005.041 ms,,,,,[17 SOF],[Frames: 193 - 209] 0,,21682,1:16.048.597,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A B9 ED 0C 75 DC 6B 35 00 B0 BF 38 82 24 09 2D… 0,,21686,1:16.049.594,14.004.770 ms,,,,,[15 SOF],[Frames: 210 - 224] 0,,21687,1:16.063.600,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21691,1:16.064.596,2.833 us,,,,,[1 SOF],[Frame: 225] 0,,21692,1:16.064.600,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A B9 ED 0C 75 DC 6B 35 00 B0 BF 38 82 24 09 2D… 0,,21696,1:16.065.597,15.004.895 ms,,,,,[16 SOF],[Frames: 226 - 241] 0,,21697,1:16.080.602,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 DE EE 0E CF E6 93 70 86 4F E8 6F 74 39 EB 83 75… 0,,21701,1:16.081.599,14.004.750 ms,,,,,[15 SOF],[Frames: 242 - 256] 0,,21702,1:16.095.604,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21706,1:16.096.601,2.812 us,,,,,[1 SOF],[Frame: 257] 0,,21707,1:16.096.604,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 DE EE 0E CF E6 93 70 86 4F E8 6F 74 39 EB 83 75… 0,,21711,1:16.097.601,15.004.916 ms,,,,,[16 SOF],[Frames: 258 - 273] 0,,21712,1:16.112.606,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D9 76 1B 8D 0A C5 DA 9E AC 53 57 FB 64 78 F9 DB… 0,,21716,1:16.113.603,14.004.770 ms,,,,,[15 SOF],[Frames: 274 - 288] 0,,21717,1:16.127.608,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21721,1:16.128.605,2.812 us,,,,,[1 SOF],[Frame: 289] 0,,21722,1:16.128.609,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D9 76 1B 8D 0A C5 DA 9E AC 53 57 FB 64 78 F9 DB… 0,,21726,1:16.129.605,15.004.916 ms,,,,,[16 SOF],[Frames: 290 - 305] 0,,21727,1:16.144.611,50.354 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A6 9C 30 01 30 1C C2 B1 0B 70 12 D9 82 99 B6 B6… 0,,21731,1:16.145.608,14.004.770 ms,,,,,[15 SOF],[Frames: 306 - 320] 0,,21732,1:16.159.613,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21736,1:16.160.610,2.812 us,,,,,[1 SOF],[Frame: 321] 0,,21737,1:16.160.613,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A6 9C 30 01 30 1C C2 B1 0B 70 12 D9 82 99 B6 B6… 0,,21741,1:16.161.610,15.004.895 ms,,,,,[16 SOF],[Frames: 322 - 337] 0,,21742,1:16.176.615,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 A3 00 13 41 FE EC BB 10 DD 44 C2 40 C8 4C 67… 0,,21746,1:16.177.612,14.004.770 ms,,,,,[15 SOF],[Frames: 338 - 352] 0,,21747,1:16.191.617,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21751,1:16.192.614,2.833 us,,,,,[1 SOF],[Frame: 353] 0,,21752,1:16.192.617,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 A3 00 13 41 FE EC BB 10 DD 44 C2 40 C8 4C 67… 0,,21756,1:16.193.614,15.004.895 ms,,,,,[16 SOF],[Frames: 354 - 369] 0,,21757,1:16.208.620,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 50 A7 10 C2 25 7F 09 05 97 B8 B8 19 85 0E 97… 0,,21761,1:16.209.617,14.004.750 ms,,,,,[15 SOF],[Frames: 370 - 384] 0,,21762,1:16.223.622,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21766,1:16.224.619,2.833 us,,,,,[1 SOF],[Frame: 385] 0,,21767,1:16.224.622,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 50 A7 10 C2 25 7F 09 05 97 B8 B8 19 85 0E 97… 0,,21771,1:16.225.619,15.004.895 ms,,,,,[16 SOF],[Frames: 386 - 401] 0,,21772,1:16.240.624,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B2 7C 67 F8 00 C5 1A AB CD D6 D1 0A EE 7C B3 E3… 0,,21776,1:16.241.621,14.004.750 ms,,,,,[15 SOF],[Frames: 402 - 416] 0,,21777,1:16.255.626,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21781,1:16.256.623,2.812 us,,,,,[1 SOF],[Frame: 417] 0,,21782,1:16.256.626,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B2 7C 67 F8 00 C5 1A AB CD D6 D1 0A EE 7C B3 E3… 0,,21786,1:16.257.623,15.004.916 ms,,,,,[16 SOF],[Frames: 418 - 433] 0,,21787,1:16.272.629,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 B0 1B 3B D7 1B AB 9B 3F 5D DF 76 B5 BD E1 E8 7F… 0,,21791,1:16.273.625,14.004.770 ms,,,,,[15 SOF],[Frames: 434 - 448] 0,,21792,1:16.287.631,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21796,1:16.288.628,2.812 us,,,,,[1 SOF],[Frame: 449] 0,,21797,1:16.288.631,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 B0 1B 3B D7 1B AB 9B 3F 5D DF 76 B5 BD E1 E8 7F… 0,,21801,1:16.289.628,15.004.895 ms,,,,,[16 SOF],[Frames: 450 - 465] 0,,21802,1:16.304.633,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 32 05 5B 89 B3 43 64 2F 95 1B CA A1 84 50 A1… 0,,21806,1:16.305.630,14.004.770 ms,,,,,[15 SOF],[Frames: 466 - 480] 0,,21807,1:16.319.635,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21811,1:16.320.632,2.812 us,,,,,[1 SOF],[Frame: 481] 0,,21812,1:16.320.635,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 32 05 5B 89 B3 43 64 2F 95 1B CA A1 84 50 A1… 0,,21816,1:16.321.632,15.004.895 ms,,,,,[16 SOF],[Frames: 482 - 497] 0,,21817,1:16.336.637,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 38 BF F5 06 1A CC 68 7F 2D 80 93 87 C3 79 59 5E… 0,,21821,1:16.337.634,14.004.770 ms,,,,,[15 SOF],[Frames: 498 - 512] 0,,21822,1:16.351.640,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21826,1:16.352.636,2.833 us,,,,,[1 SOF],[Frame: 513] 0,,21827,1:16.352.640,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 38 BF F5 06 1A CC 68 7F 2D 80 93 87 C3 79 59 5E… 0,,21831,1:16.353.637,15.004.895 ms,,,,,[16 SOF],[Frames: 514 - 529] 0,,21832,1:16.368.642,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 01 4A 11 8E 57 8F 24 D9 64 C1 31 37 F8 1D F2 73… 0,,21836,1:16.369.639,14.004.750 ms,,,,,[15 SOF],[Frames: 530 - 544] 0,,21837,1:16.383.644,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21841,1:16.384.641,2.812 us,,,,,[1 SOF],[Frame: 545] 0,,21842,1:16.384.644,50.479 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 01 4A 11 8E 57 8F 24 D9 64 C1 31 37 F8 1D F2 73… 0,,21846,1:16.385.641,15.004.895 ms,,,,,[16 SOF],[Frames: 546 - 561] 0,,21847,1:16.400.646,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 34 73 70 59 D6 53 2A 68 B0 B4 3F 82 66 D6 F5 72… 0,,21851,1:16.401.643,14.004.750 ms,,,,,[15 SOF],[Frames: 562 - 576] 0,,21852,1:16.415.648,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21856,1:16.416.645,2.812 us,,,,,[1 SOF],[Frame: 577] 0,,21857,1:16.416.649,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 34 73 70 59 D6 53 2A 68 B0 B4 3F 82 66 D6 F5 72… 0,,21861,1:16.417.645,15.004.916 ms,,,,,[16 SOF],[Frames: 578 - 593] 0,,21862,1:16.432.651,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 18 4D F0 26 76 F6 E0 24 AC FB 70 74 2C 2B CF… 0,,21866,1:16.433.648,14.004.770 ms,,,,,[15 SOF],[Frames: 594 - 608] 0,,21867,1:16.447.653,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21871,1:16.448.650,2.812 us,,,,,[1 SOF],[Frame: 609] 0,,21872,1:16.448.653,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 18 4D F0 26 76 F6 E0 24 AC FB 70 74 2C 2B CF… 0,,21876,1:16.449.650,15.004.895 ms,,,,,[16 SOF],[Frames: 610 - 625] 0,,21877,1:16.464.655,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7D 34 62 BA 2F AB 8D 0B BF 7B B0 B4 97 1F CF B1… 0,,21881,1:16.465.652,14.004.770 ms,,,,,[15 SOF],[Frames: 626 - 640] 0,,21882,1:16.479.657,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21886,1:16.480.654,2.812 us,,,,,[1 SOF],[Frame: 641] 0,,21887,1:16.480.657,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7D 34 62 BA 2F AB 8D 0B BF 7B B0 B4 97 1F CF B1… 0,,21891,1:16.481.654,15.004.895 ms,,,,,[16 SOF],[Frames: 642 - 657] 0,,21892,1:16.496.660,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A0 5D EE 79 55 AF 08 74 DA C6 61 2C FB B0 2E 4D… 0,,21896,1:16.497.657,14.004.770 ms,,,,,[15 SOF],[Frames: 658 - 672] 0,,21897,1:16.511.662,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21901,1:16.512.659,2.833 us,,,,,[1 SOF],[Frame: 673] 0,,21902,1:16.512.662,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A0 5D EE 79 55 AF 08 74 DA C6 61 2C FB B0 2E 4D… 0,,21906,1:16.513.659,15.004.895 ms,,,,,[16 SOF],[Frames: 674 - 689] 0,,21907,1:16.528.664,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A3 95 FA 53 4D FC 22 ED 83 8A 14 8F 1C E8 0F 93… 0,,21911,1:16.529.661,14.004.750 ms,,,,,[15 SOF],[Frames: 690 - 704] 0,,21912,1:16.543.666,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21916,1:16.544.663,2.812 us,,,,,[1 SOF],[Frame: 705] 0,,21917,1:16.544.666,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A3 95 FA 53 4D FC 22 ED 83 8A 14 8F 1C E8 0F 93… 0,,21921,1:16.545.663,15.004.916 ms,,,,,[16 SOF],[Frames: 706 - 721] 0,,21922,1:16.560.669,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 74 A3 9B DE 66 EA 58 17 B2 BB AC F5 F8 DD 50 89… 0,,21926,1:16.561.665,14.004.750 ms,,,,,[15 SOF],[Frames: 722 - 736] 0,,21927,1:16.575.671,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21931,1:16.576.668,2.812 us,,,,,[1 SOF],[Frame: 737] 0,,21932,1:16.576.671,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 74 A3 9B DE 66 EA 58 17 B2 BB AC F5 F8 DD 50 89… 0,,21936,1:16.577.668,15.004.916 ms,,,,,[16 SOF],[Frames: 738 - 753] 0,,21937,1:16.592.673,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 61 F0 A2 3E D5 B0 13 D5 51 BC 1C 62 19 AE 19 75… 0,,21941,1:16.593.670,14.004.770 ms,,,,,[15 SOF],[Frames: 754 - 768] 0,,21942,1:16.607.675,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21946,1:16.608.672,2.812 us,,,,,[1 SOF],[Frame: 769] 0,,21947,1:16.608.675,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 61 F0 A2 3E D5 B0 13 D5 51 BC 1C 62 19 AE 19 75… 0,,21951,1:16.609.672,15.004.895 ms,,,,,[16 SOF],[Frames: 770 - 785] 0,,21952,1:16.624.677,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 7E A3 7E 11 5F 8A 2B 95 97 97 35 F3 09 BA F3 CD… 0,,21956,1:16.625.674,14.004.770 ms,,,,,[15 SOF],[Frames: 786 - 800] 0,,21957,1:16.639.680,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21961,1:16.640.676,2.833 us,,,,,[1 SOF],[Frame: 801] 0,,21962,1:16.640.680,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 7E A3 7E 11 5F 8A 2B 95 97 97 35 F3 09 BA F3 CD… 0,,21966,1:16.641.677,15.004.895 ms,,,,,[16 SOF],[Frames: 802 - 817] 0,,21967,1:16.656.682,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2A 85 A8 2E FC BD 67 99 55 5E 4D 20 D9 51 C9 AB… 0,,21971,1:16.657.679,14.004.770 ms,,,,,[15 SOF],[Frames: 818 - 832] 0,,21972,1:16.671.684,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21976,1:16.672.681,16.005.041 ms,,,,,[17 SOF],[Frames: 833 - 849] 0,,21977,1:16.688.686,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E6 79 66 BF CC D2 1D F6 80 17 90 C2 CB C9 50 10… 0,,21981,1:16.689.683,14.004.750 ms,,,,,[15 SOF],[Frames: 850 - 864] 0,,21982,1:16.703.688,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,21986,1:16.704.685,2.812 us,,,,,[1 SOF],[Frame: 865] 0,,21987,1:16.704.689,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E6 79 66 BF CC D2 1D F6 80 17 90 C2 CB C9 50 10… 0,,21991,1:16.705.685,15.004.916 ms,,,,,[16 SOF],[Frames: 866 - 881] 0,,21992,1:16.720.691,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 20 8A F5 C7 9D DD C3 9E 0C 32 1D 36 D2 1D 8E 64… 0,,21996,1:16.721.688,14.004.770 ms,,,,,[15 SOF],[Frames: 882 - 896] 0,,21997,1:16.735.693,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22001,1:16.736.690,2.812 us,,,,,[1 SOF],[Frame: 897] 0,,22002,1:16.736.693,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 20 8A F5 C7 9D DD C3 9E 0C 32 1D 36 D2 1D 8E 64… 0,,22006,1:16.737.690,15.004.895 ms,,,,,[16 SOF],[Frames: 898 - 913] 0,,22007,1:16.752.695,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 91 42 67 39 E2 34 47 2B 97 8B DE F3 09 5B 04 20… 0,,22011,1:16.753.692,14.004.770 ms,,,,,[15 SOF],[Frames: 914 - 928] 0,,22012,1:16.767.697,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22016,1:16.768.694,2.812 us,,,,,[1 SOF],[Frame: 929] 0,,22017,1:16.768.697,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 91 42 67 39 E2 34 47 2B 97 8B DE F3 09 5B 04 20… 0,,22021,1:16.769.694,15.004.895 ms,,,,,[16 SOF],[Frames: 930 - 945] 0,,22022,1:16.784.700,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BC 85 75 3A EE 9C E6 F6 74 5A 97 86 5D 90 68 48… 0,,22026,1:16.785.697,14.004.770 ms,,,,,[15 SOF],[Frames: 946 - 960] 0,,22027,1:16.799.702,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22031,1:16.800.699,2.833 us,,,,,[1 SOF],[Frame: 961] 0,,22032,1:16.800.702,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BC 85 75 3A EE 9C E6 F6 74 5A 97 86 5D 90 68 48… 0,,22036,1:16.801.699,15.004.895 ms,,,,,[16 SOF],[Frames: 962 - 977] 0,,22037,1:16.816.704,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 93 C2 E7 8E A2 64 38 A7 F4 20 C4 D5 25 CE 4D 2E… 0,,22041,1:16.817.701,14.004.750 ms,,,,,[15 SOF],[Frames: 978 - 992] 0,,22042,1:16.831.706,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22046,1:16.832.703,2.833 us,,,,,[1 SOF],[Frame: 993] 0,,22047,1:16.832.706,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 93 C2 E7 8E A2 64 38 A7 F4 20 C4 D5 25 CE 4D 2E… 0,,22051,1:16.833.703,15.004.979 ms,,,,,[16 SOF],[Frames: 994 - 1009] 0,,22052,1:16.848.709,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 1B 35 37 BB 4E F4 3D 3D 41 14 55 B4 B2 85 25… 0,,22056,1:16.849.705,14.004.750 ms,,,,,[15 SOF],[Frames: 1010 - 1024] 0,,22057,1:16.863.711,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22061,1:16.864.708,2.812 us,,,,,[1 SOF],[Frame: 1025] 0,,22062,1:16.864.711,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 1B 35 37 BB 4E F4 3D 3D 41 14 55 B4 B2 85 25… 0,,22066,1:16.865.708,15.004.916 ms,,,,,[16 SOF],[Frames: 1026 - 1041] 0,,22067,1:16.880.713,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 05 88 70 0E 10 4D 9F 00 42 65 9F DB 73 E6 11 D2… 0,,22071,1:16.881.710,14.004.770 ms,,,,,[15 SOF],[Frames: 1042 - 1056] 0,,22072,1:16.895.715,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22076,1:16.896.712,2.812 us,,,,,[1 SOF],[Frame: 1057] 0,,22077,1:16.896.715,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 05 88 70 0E 10 4D 9F 00 42 65 9F DB 73 E6 11 D2… 0,,22081,1:16.897.712,15.004.895 ms,,,,,[16 SOF],[Frames: 1058 - 1073] 0,,22082,1:16.912.717,50.395 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 73 DA 25 D2 EE ED D1 5D B9 79 68 A8 72 D6 A9 91… 0,,22086,1:16.913.714,14.004.770 ms,,,,,[15 SOF],[Frames: 1074 - 1088] 0,,22087,1:16.927.719,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22091,1:16.928.716,2.812 us,,,,,[1 SOF],[Frame: 1089] 0,,22092,1:16.928.720,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 73 DA 25 D2 EE ED D1 5D B9 79 68 A8 72 D6 A9 91… 0,,22096,1:16.929.717,15.004.895 ms,,,,,[16 SOF],[Frames: 1090 - 1105] 0,,22097,1:16.944.722,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8A D0 4D F6 3D 8D A3 CD 4E 9A 71 49 B3 0E 57 D7… 0,,22101,1:16.945.719,14.004.770 ms,,,,,[15 SOF],[Frames: 1106 - 1120] 0,,22102,1:16.959.724,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22106,1:16.960.721,2.833 us,,,,,[1 SOF],[Frame: 1121] 0,,22107,1:16.960.724,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8A D0 4D F6 3D 8D A3 CD 4E 9A 71 49 B3 0E 57 D7… 0,,22111,1:16.961.721,15.004.895 ms,,,,,[16 SOF],[Frames: 1122 - 1137] 0,,22112,1:16.976.726,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 19 47 62 00 6B DF 34 E8 4D 49 E0 7F 70 91 DB 9E… 0,,22116,1:16.977.723,14.004.750 ms,,,,,[15 SOF],[Frames: 1138 - 1152] 0,,22117,1:16.991.728,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22121,1:16.992.725,2.895 us,,,,,[1 SOF],[Frame: 1153] 0,,22122,1:16.992.729,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 19 47 62 00 6B DF 34 E8 4D 49 E0 7F 70 91 DB 9E… 0,,22126,1:16.993.725,15.004.895 ms,,,,,[16 SOF],[Frames: 1154 - 1169] 0,,22127,1:17.008.731,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 80 E4 9F AC 74 89 5A 33 6B DF DA 2F 5B 20 15 41… 0,,22131,1:17.009.728,14.004.750 ms,,,,,[15 SOF],[Frames: 1170 - 1184] 0,,22132,1:17.023.733,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22136,1:17.024.730,2.812 us,,,,,[1 SOF],[Frame: 1185] 0,,22137,1:17.024.733,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 80 E4 9F AC 74 89 5A 33 6B DF DA 2F 5B 20 15 41… 0,,22141,1:17.025.730,15.004.916 ms,,,,,[16 SOF],[Frames: 1186 - 1201] 0,,22142,1:17.040.735,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 4F 23 A0 B6 BD B3 A6 E2 48 CC E3 D1 D2 D3 83… 0,,22146,1:17.041.732,14.004.770 ms,,,,,[15 SOF],[Frames: 1202 - 1216] 0,,22147,1:17.055.737,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22151,1:17.056.734,2.812 us,,,,,[1 SOF],[Frame: 1217] 0,,22152,1:17.056.737,50.354 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 4F 23 A0 B6 BD B3 A6 E2 48 CC E3 D1 D2 D3 83… 0,,22156,1:17.057.734,15.004.895 ms,,,,,[16 SOF],[Frames: 1218 - 1233] 0,,22157,1:17.072.740,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 42 72 28 5C FC BB D9 43 6E 9F 41 CA 03 88 04 E8… 0,,22161,1:17.073.737,14.004.770 ms,,,,,[15 SOF],[Frames: 1234 - 1248] 0,,22162,1:17.087.742,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22166,1:17.088.739,2.812 us,,,,,[1 SOF],[Frame: 1249] 0,,22167,1:17.088.742,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 42 72 28 5C FC BB D9 43 6E 9F 41 CA 03 88 04 E8… 0,,22171,1:17.089.739,15.004.895 ms,,,,,[16 SOF],[Frames: 1250 - 1265] 0,,22172,1:17.104.744,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 6D 68 80 B2 E5 18 7D D1 08 64 A2 53 3D 77 71 AE… 0,,22176,1:17.105.741,14.004.770 ms,,,,,[15 SOF],[Frames: 1266 - 1280] 0,,22177,1:17.119.746,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22181,1:17.120.743,2.833 us,,,,,[1 SOF],[Frame: 1281] 0,,22182,1:17.120.746,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 6D 68 80 B2 E5 18 7D D1 08 64 A2 53 3D 77 71 AE… 0,,22186,1:17.121.743,15.004.895 ms,,,,,[16 SOF],[Frames: 1282 - 1297] 0,,22187,1:17.136.749,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 55 45 F1 6E 59 23 EA 3E 9D E3 79 53 05 E6 B7 B5… 0,,22191,1:17.137.745,14.004.750 ms,,,,,[15 SOF],[Frames: 1298 - 1312] 0,,22192,1:17.151.751,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22196,1:17.152.747,2.812 us,,,,,[1 SOF],[Frame: 1313] 0,,22197,1:17.152.751,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 55 45 F1 6E 59 23 EA 3E 9D E3 79 53 05 E6 B7 B5… 0,,22201,1:17.153.748,15.004.916 ms,,,,,[16 SOF],[Frames: 1314 - 1329] 0,,22202,1:17.168.753,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 85 93 69 F0 07 98 B2 48 4B 55 EF 94 EB CE FD 7F… 0,,22206,1:17.169.750,14.004.750 ms,,,,,[15 SOF],[Frames: 1330 - 1344] 0,,22207,1:17.183.755,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22211,1:17.184.752,2.812 us,,,,,[1 SOF],[Frame: 1345] 0,,22212,1:17.184.755,50.562 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 85 93 69 F0 07 98 B2 48 4B 55 EF 94 EB CE FD 7F… 0,,22216,1:17.185.752,15.004.916 ms,,,,,[16 SOF],[Frames: 1346 - 1361] 0,,22217,1:17.200.757,50.437 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 D5 52 3A AD 17 5F 96 50 0B D2 02 96 D7 61 2F 77… 0,,22221,1:17.201.754,14.004.770 ms,,,,,[15 SOF],[Frames: 1362 - 1376] 0,,22222,1:17.215.759,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22226,1:17.216.756,2.812 us,,,,,[1 SOF],[Frame: 1377] 0,,22227,1:17.216.760,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 D5 52 3A AD 17 5F 96 50 0B D2 02 96 D7 61 2F 77… 0,,22231,1:17.217.757,15.004.895 ms,,,,,[16 SOF],[Frames: 1378 - 1393] 0,,22232,1:17.232.762,50.750 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 59 BF 75 3F C3 23 21 74 0D BF 3F DA 3C 6C 55 A4… 0,,22236,1:17.233.759,14.004.770 ms,,,,,[15 SOF],[Frames: 1394 - 1408] 0,,22237,1:17.247.764,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22241,1:17.248.761,2.833 us,,,,,[1 SOF],[Frame: 1409] 0,,22242,1:17.248.764,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 59 BF 75 3F C3 23 21 74 0D BF 3F DA 3C 6C 55 A4… 0,,22246,1:17.249.761,15.004.895 ms,,,,,[16 SOF],[Frames: 1410 - 1425] 0,,22247,1:17.264.766,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 EC 66 64 36 91 B1 68 06 4A CF 14 D2 EE D3 71 9C… 0,,22251,1:17.265.763,14.004.770 ms,,,,,[15 SOF],[Frames: 1426 - 1440] 0,,22252,1:17.279.768,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22256,1:17.280.765,16.005.041 ms,,,,,[17 SOF],[Frames: 1441 - 1457] 0,,22257,1:17.296.771,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E0 A6 97 12 B0 75 09 AE C2 57 EC 5C A5 D5 78 6E… 0,,22261,1:17.297.768,14.004.750 ms,,,,,[15 SOF],[Frames: 1458 - 1472] 0,,22262,1:17.311.773,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22266,1:17.312.770,2.812 us,,,,,[1 SOF],[Frame: 1473] 0,,22267,1:17.312.773,50.395 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E0 A6 97 12 B0 75 09 AE C2 57 EC 5C A5 D5 78 6E… 0,,22271,1:17.313.770,15.004.916 ms,,,,,[16 SOF],[Frames: 1474 - 1489] 0,,22272,1:17.328.775,50.604 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 71 87 EF E1 C3 79 05 1B BC 3A 7E 9F 9A B3 52 01… 0,,22276,1:17.329.772,14.004.854 ms,,,,,[15 SOF],[Frames: 1490 - 1504] 0,,22277,1:17.343.777,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22281,1:17.344.774,2.812 us,,,,,[1 SOF],[Frame: 1505] 0,,22282,1:17.344.777,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 71 87 EF E1 C3 79 05 1B BC 3A 7E 9F 9A B3 52 01… 0,,22286,1:17.345.774,15.004.895 ms,,,,,[16 SOF],[Frames: 1506 - 1521] 0,,22287,1:17.360.780,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 3E A7 33 B7 87 45 D0 50 16 1F A6 16 77 E5 CF AA… 0,,22291,1:17.361.776,14.004.770 ms,,,,,[15 SOF],[Frames: 1522 - 1536] 0,,22292,1:17.375.782,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22296,1:17.376.779,2.812 us,,,,,[1 SOF],[Frame: 1537] 0,,22297,1:17.376.782,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 3E A7 33 B7 87 45 D0 50 16 1F A6 16 77 E5 CF AA… 0,,22301,1:17.377.779,15.004.979 ms,,,,,[16 SOF],[Frames: 1538 - 1553] 0,,22302,1:17.392.784,50.833 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 41 1D 11 E1 EF B2 B4 E4 C4 D2 D7 56 B4 83 F8 35… 0,,22306,1:17.393.781,14.004.770 ms,,,,,[15 SOF],[Frames: 1554 - 1568] 0,,22307,1:17.407.786,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22311,1:17.408.783,2.833 us,,,,,[1 SOF],[Frame: 1569] 0,,22312,1:17.408.786,50.770 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 41 1D 11 E1 EF B2 B4 E4 C4 D2 D7 56 B4 83 F8 35… 0,,22316,1:17.409.783,15.004.895 ms,,,,,[16 SOF],[Frames: 1570 - 1585] 0,,22317,1:17.424.788,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 BE BA BD 83 3F 5C A2 7A 72 F1 8A 31 24 8B F8 DF… 0,,22321,1:17.425.785,14.004.750 ms,,,,,[15 SOF],[Frames: 1586 - 1600] 0,,22322,1:17.439.791,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22326,1:17.440.787,2.833 us,,,,,[1 SOF],[Frame: 1601] 0,,22327,1:17.440.791,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 BE BA BD 83 3F 5C A2 7A 72 F1 8A 31 24 8B F8 DF… 0,,22331,1:17.441.788,15.004.895 ms,,,,,[16 SOF],[Frames: 1602 - 1617] 0,,22332,1:17.456.793,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 82 2B 44 73 4D 7C 6D 4F 06 B1 D4 60 C6 71 E4 05… 0,,22336,1:17.457.790,14.004.750 ms,,,,,[15 SOF],[Frames: 1618 - 1632] 0,,22337,1:17.471.795,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22341,1:17.472.792,2.812 us,,,,,[1 SOF],[Frame: 1633] 0,,22342,1:17.472.795,50.333 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 82 2B 44 73 4D 7C 6D 4F 06 B1 D4 60 C6 71 E4 05… 0,,22346,1:17.473.792,15.004.916 ms,,,,,[16 SOF],[Frames: 1634 - 1649] 0,,22347,1:17.488.797,50.770 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 2B 6D F3 E7 F3 47 B2 E6 44 1B 92 40 10 AE 12 84… 0,,22351,1:17.489.794,14.004.770 ms,,,,,[15 SOF],[Frames: 1650 - 1664] 0,,22352,1:17.503.799,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22356,1:17.504.796,2.812 us,,,,,[1 SOF],[Frame: 1665] 0,,22357,1:17.504.800,50.750 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 2B 6D F3 E7 F3 47 B2 E6 44 1B 92 40 10 AE 12 84… 0,,22361,1:17.505.796,15.004.895 ms,,,,,[16 SOF],[Frames: 1666 - 1681] 0,,22362,1:17.520.802,50.479 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD E9 AB D2 8E AA 62 E7 41 42 89 3A A0 A7 8F 34… 0,,22366,1:17.521.799,14.004.770 ms,,,,,[15 SOF],[Frames: 1682 - 1696] 0,,22367,1:17.535.804,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22371,1:17.536.801,2.812 us,,,,,[1 SOF],[Frame: 1697] 0,,22372,1:17.536.804,50.520 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 AD E9 AB D2 8E AA 62 E7 41 42 89 3A A0 A7 8F 34… 0,,22376,1:17.537.801,15.004.895 ms,,,,,[16 SOF],[Frames: 1698 - 1713] 0,,22377,1:17.552.806,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 00 22 85 5E E6 EC 30 C3 BA 8E C6 DB 17 AD F9 95… 0,,22381,1:17.553.803,14.004.770 ms,,,,,[15 SOF],[Frames: 1714 - 1728] 0,,22382,1:17.567.808,50.979 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22386,1:17.568.805,2.833 us,,,,,[1 SOF],[Frame: 1729] 0,,22387,1:17.568.808,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 00 22 85 5E E6 EC 30 C3 BA 8E C6 DB 17 AD F9 95… 0,,22391,1:17.569.805,15.004.895 ms,,,,,[16 SOF],[Frames: 1730 - 1745] 0,,22392,1:17.584.811,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 24 49 0D BB 8E 8F D0 90 E0 72 81 E4 97 6C C3 DA… 0,,22396,1:17.585.808,14.004.750 ms,,,,,[15 SOF],[Frames: 1746 - 1760] 0,,22397,1:17.599.813,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22401,1:17.600.810,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,22402,1:17.600.813,50.416 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 24 49 0D BB 8E 8F D0 90 E0 72 81 E4 97 6C C3 DA… 0,,22406,1:17.601.810,15.004.895 ms,,,,,[16 SOF],[Frames: 1762 - 1777] 0,,22407,1:17.616.815,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 F3 A2 AC 73 66 1D 33 65 7F A6 6F 16 F9 D4 08 38… 0,,22411,1:17.617.812,14.004.750 ms,,,,,[15 SOF],[Frames: 1778 - 1792] 0,,22412,1:17.631.817,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22416,1:17.632.814,2.812 us,,,,,[1 SOF],[Frame: 1793] 0,,22417,1:17.632.817,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 F3 A2 AC 73 66 1D 33 65 7F A6 6F 16 F9 D4 08 38… 0,,22421,1:17.633.814,15.004.916 ms,,,,,[16 SOF],[Frames: 1794 - 1809] 0,,22422,1:17.648.820,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 89 5E DB FC B4 1B AC 8A E4 79 38 95 B2 05 45 32… 0,,22426,1:17.649.816,14.004.770 ms,,,,,[15 SOF],[Frames: 1810 - 1824] 0,,22427,1:17.663.822,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22431,1:17.664.819,2.895 us,,,,,[1 SOF],[Frame: 1825] 0,,22432,1:17.664.822,50.583 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 89 5E DB FC B4 1B AC 8A E4 79 38 95 B2 05 45 32… 0,,22436,1:17.665.819,15.004.895 ms,,,,,[16 SOF],[Frames: 1826 - 1841] 0,,22437,1:17.680.824,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 A8 E1 F0 F6 D1 E2 F8 1B 03 1F D6 76 43 2E 66 DA… 0,,22441,1:17.681.821,14.004.770 ms,,,,,[15 SOF],[Frames: 1842 - 1856] 0,,22442,1:17.695.826,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22446,1:17.696.823,2.833 us,,,,,[1 SOF],[Frame: 1857] 0,,22447,1:17.696.826,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 A8 E1 F0 F6 D1 E2 F8 1B 03 1F D6 76 43 2E 66 DA… 0,,22451,1:17.697.823,15.004.895 ms,,,,,[16 SOF],[Frames: 1858 - 1873] 0,,22452,1:17.712.828,50.583 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 1D CC C4 A4 BB 48 52 5C 46 FE 52 59 2C 9C 5E BB… 0,,22456,1:17.713.825,14.004.770 ms,,,,,[15 SOF],[Frames: 1874 - 1888] 0,,22457,1:17.727.831,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22461,1:17.728.827,2.833 us,,,,,[1 SOF],[Frame: 1889] 0,,22462,1:17.728.831,50.604 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 1D CC C4 A4 BB 48 52 5C 46 FE 52 59 2C 9C 5E BB… 0,,22466,1:17.729.828,15.004.895 ms,,,,,[16 SOF],[Frames: 1890 - 1905] 0,,22467,1:17.744.833,50.666 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 33 F1 74 FA 9D A3 EF CA 8F B3 59 33 9B 91 BD 67… 0,,22471,1:17.745.830,14.004.833 ms,,,,,[15 SOF],[Frames: 1906 - 1920] 0,,22472,1:17.759.835,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22476,1:17.760.832,2.812 us,,,,,[1 SOF],[Frame: 1921] 0,,22477,1:17.760.835,50.666 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 33 F1 74 FA 9D A3 EF CA 8F B3 59 33 9B 91 BD 67… 0,,22481,1:17.761.832,15.004.916 ms,,,,,[16 SOF],[Frames: 1922 - 1937] 0,,22482,1:17.776.837,50.333 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 E2 29 5A 9E 9B 85 94 8B 05 C0 21 63 49 8B 63 8C… 0,,22486,1:17.777.834,14.004.750 ms,,,,,[15 SOF],[Frames: 1938 - 1952] 0,,22487,1:17.791.839,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22491,1:17.792.836,2.812 us,,,,,[1 SOF],[Frame: 1953] 0,,22492,1:17.792.840,50.312 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 E2 29 5A 9E 9B 85 94 8B 05 C0 21 63 49 8B 63 8C… 0,,22496,1:17.793.836,15.004.916 ms,,,,,[16 SOF],[Frames: 1954 - 1969] 0,,22497,1:17.808.842,50.520 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 13 80 95 DD 59 55 E5 D8 99 AA 17 F5 43 6D 64 4B… 0,,22501,1:17.809.839,14.004.770 ms,,,,,[15 SOF],[Frames: 1970 - 1984] 0,,22502,1:17.823.844,51.000 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22506,1:17.824.841,2.895 us,,,,,[1 SOF],[Frame: 1985] 0,,22507,1:17.824.844,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 13 80 95 DD 59 55 E5 D8 99 AA 17 F5 43 6D 64 4B… 0,,22511,1:17.825.841,15.004.979 ms,,,,,[16 SOF],[Frames: 1986 - 2001] 0,,22512,1:17.840.846,50.562 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 8E 29 42 0B 6C 60 AC E1 98 BA EF 0C 31 B6 0A 32… 0,,22516,1:17.841.843,14.004.854 ms,,,,,[15 SOF],[Frames: 2002 - 2016] 0,,22517,1:17.855.848,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22521,1:17.856.845,2.916 us,,,,,[1 SOF],[Frame: 2017] 0,,22522,1:17.856.849,50.500 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 8E 29 42 0B 6C 60 AC E1 98 BA EF 0C 31 B6 0A 32… 0,,22526,1:17.857.845,15.004.979 ms,,,,,[16 SOF],[Frames: 2018 - 2033] 0,,22527,1:17.872.851,50.416 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 49 D6 29 39 4D 9E B7 BA 86 2C 4B 98 20 57 46 D8… 0,,22531,1:17.873.848,14.004.770 ms,,,,,[15 SOF],[Frames: 2034 - 0] 0,,22532,1:17.887.853,50.895 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22536,1:17.888.850,2.833 us,,,,,[1 SOF],[Frame: 1] 0,,22537,1:17.888.853,50.437 us,64 B,,01,01,OUT txn,05 00 1D 01 00 00 20 00 49 D6 29 39 4D 9E B7 BA 86 2C 4B 98 20 57 46 D8… 0,,22541,1:17.889.850,15.004.895 ms,,,,,[16 SOF],[Frames: 2 - 17] 0,,22542,1:17.904.855,50.500 us,64 B,,01,01,OUT txn,27 00 16 23 00 00 20 00 AD 1B 7C 84 88 E7 FE E7 C4 2B D3 D6 BA 65 DA 94… 0,,22546,1:17.905.852,14.004.750 ms,,,,,[15 SOF],[Frames: 18 - 32] 0,,22547,1:17.919.857,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22551,1:17.920.854,2.812 us,,,,,[1 SOF],[Frame: 33] 0,,22552,1:17.920.857,50.479 us,64 B,,01,01,OUT txn,13 00 16 0F 00 06 0C 00 CA 3D CE FC D4 F7 41 57 01 0D E7 84 BA 65 DA 94… 0,,22556,1:17.921.854,15.004.916 ms,,,,,[16 SOF],[Frames: 34 - 49] 0,,22557,1:17.936.860,50.520 us,64 B,,01,01,OUT txn,05 00 16 01 00 02 0C 00 CA 3D CE FC D4 F7 41 57 01 0D E7 84 BA 65 DA 94… 0,,22561,1:17.937.856,14.004.770 ms,,,,,[15 SOF],[Frames: 50 - 64] 0,,22562,1:17.951.862,50.916 us,64 B,,01,02,IN txn,06 00 16 01 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00… 0,,22566,1:17.952.859,41.008.500 ms,,,,,[42 SOF],[Frames: 65 - 106] 0,,22567,1:17.983.866,4.583 us,,,01,02,[1 IN-NAK], 0,,22568,1:17.994.154,,,,,, / , 0,,22569,1:18.761.750,,,,,, / , 0,,22570,1:18.941.662,,,,,, / , 0,,22571,1:19.007.714,,,,,, / , 0,,22572,1:19.491.928,,,,,, / , 0,,22573,1:19.523.678,,,,,, / , 0,,22574,1:19.524.077,31.007.125 ms,,,,,[32 SOF],[Frames: 1636 - 1667] 0,,22575,1:19.555.084,12.979 us,8 B,,00,00,SETUP txn,80 06 00 01 00 00 40 00 0,,22579,1:19.556.081,2.833 us,,,,,[1 SOF],[Frame: 1668] 0,,22580,1:19.556.084,20.250 us,18 B,,00,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22584,1:19.557.081,1.002.958 ms,,,,,[2 SOF],[Frames: 1669 - 1670] 0,,22585,1:19.558.085,7.645 us,0 B,,00,00,OUT txn, 0,,22589,1:19.559.081,2.833 us,,,,,[1 SOF],[Frame: 1671] 0,,22590,1:19.559.159,,,,,, / , 0,,22591,1:19.585.387,,,,,, / , 0,,22592,1:19.586.085,31.007.125 ms,,,,,[32 SOF],[Frames: 1698 - 1729] 0,,22593,1:19.617.093,12.979 us,8 B,,00,00,SETUP txn,00 05 01 00 00 00 00 00 0,,22597,1:19.618.090,2.833 us,,,,,[1 SOF],[Frame: 1730] 0,,22598,1:19.618.093,8.250 us,0 B,,00,00,IN txn, 0,,22602,1:19.619.090,29.006.854 ms,,,,,[30 SOF],[Frames: 1731 - 1760] 0,,22603,1:19.648.098,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,22607,1:19.649.094,2.895 us,,,,,[1 SOF],[Frame: 1761] 0,,22608,1:19.649.097,20.229 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22612,1:19.650.094,2.812 us,,,,,[1 SOF],[Frame: 1762] 0,,22613,1:19.650.097,7.666 us,0 B,,01,00,OUT txn, 0,,22617,1:19.651.094,1.002.958 ms,,,,,[2 SOF],[Frames: 1763 - 1764] 0,,22618,1:19.652.098,13.083 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 FF 00 0,,22622,1:19.653.095,2.812 us,,,,,[1 SOF],[Frame: 1765] 0,,22623,1:19.653.098,35.562 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,22627,1:19.654.095,1.003.041 ms,,,,,[2 SOF],[Frames: 1766 - 1767] 0,,22628,1:19.655.098,7.666 us,0 B,,01,00,OUT txn, 0,,22632,1:19.656.095,1.002.958 ms,,,,,[2 SOF],[Frames: 1768 - 1769] 0,,22633,1:19.657.099,13.083 us,8 B,,01,00,SETUP txn,80 06 00 03 00 00 FF 00 0,,22637,1:19.658.095,2.812 us,,,,,[1 SOF],[Frame: 1770] 0,,22638,1:19.658.098,10.895 us,4 B,,01,00,IN txn,04 03 09 04 0,,22642,1:19.659.095,1.002.958 ms,,,,,[2 SOF],[Frames: 1771 - 1772] 0,,22643,1:19.660.099,7.666 us,0 B,,01,00,OUT txn, 0,,22647,1:19.661.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1773 - 1774] 0,,22648,1:19.662.099,13.083 us,8 B,,01,00,SETUP txn,80 06 02 03 09 04 FF 00 0,,22652,1:19.663.096,2.833 us,,,,,[1 SOF],[Frame: 1775] 0,,22653,1:19.663.099,28.250 us,30 B,,01,00,IN txn,1E 03 53 00 46 00 43 00 33 00 30 00 20 00 4A 00 6F 00 79 00 73 00 74 00… 0,,22657,1:19.664.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1776 - 1777] 0,,22658,1:19.665.099,7.645 us,0 B,,01,00,OUT txn, 0,,22662,1:19.666.096,1.002.958 ms,,,,,[2 SOF],[Frames: 1778 - 1779] 0,,22663,1:19.667.100,13.000 us,8 B,,01,00,SETUP txn,80 06 00 06 00 00 0A 00 0,,22667,1:19.668.097,2.812 us,,,,,[1 SOF],[Frame: 1780] 0,,22668,1:19.668.100,4.562 us,,,01,00,IN txn (STALL), 0,,22671,1:19.669.097,1.556.218.812 s,,,,,[1557 SOF],[Frames: 1781 - 1289] 0,,22672,1:21.225.316,13.000 us,8 B,,01,00,SETUP txn,80 06 00 01 00 00 12 00 0,,22676,1:21.226.313,2.812 us,,,,,[1 SOF],[Frame: 1290] 0,,22677,1:21.226.316,20.250 us,18 B,,01,00,IN txn,12 01 00 02 00 00 00 40 35 12 21 AB 01 00 01 02 00 01 0,,22681,1:21.227.313,2.812 us,,,,,[1 SOF],[Frame: 1291] 0,,22682,1:21.227.316,7.645 us,0 B,,01,00,OUT txn, 0,,22686,1:21.228.313,1.002.958 ms,,,,,[2 SOF],[Frames: 1292 - 1293] 0,,22687,1:21.229.318,13.000 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 09 00 0,,22691,1:21.230.313,2.812 us,,,,,[1 SOF],[Frame: 1294] 0,,22692,1:21.230.317,14.312 us,9 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 0,,22696,1:21.231.314,2.833 us,,,,,[1 SOF],[Frame: 1295] 0,,22697,1:21.231.317,7.666 us,0 B,,01,00,OUT txn, 0,,22701,1:21.232.314,1.002.958 ms,,,,,[2 SOF],[Frames: 1296 - 1297] 0,,22702,1:21.233.317,12.979 us,8 B,,01,00,SETUP txn,80 06 00 02 00 00 29 00 0,,22706,1:21.234.314,2.833 us,,,,,[1 SOF],[Frame: 1298] 0,,22707,1:21.234.317,35.583 us,41 B,,01,00,IN txn,09 02 29 00 01 01 00 C0 F0 09 04 00 00 02 03 00 00 00 09 21 10 01 00 01… 0,,22711,1:21.235.314,2.812 us,,,,,[1 SOF],[Frame: 1299] 0,,22712,1:21.235.317,7.666 us,0 B,,01,00,OUT txn, 0,,22716,1:21.236.314,1.002.958 ms,,,,,[2 SOF],[Frames: 1300 - 1301] 0,,22717,1:21.237.318,13.000 us,8 B,,01,00,SETUP txn,00 09 01 00 00 00 00 00 0,,22721,1:21.238.315,2.895 us,,,,,[1 SOF],[Frame: 1302] 0,,22722,1:21.238.318,8.229 us,0 B,,01,00,IN txn, 0,,22726,1:21.239.315,1.002.958 ms,,,,,[2 SOF],[Frames: 1303 - 1304] 0,,22727,1:21.240.318,13.000 us,8 B,,01,00,SETUP txn,21 0A 00 00 00 00 00 00 0,,22731,1:21.241.315,2.812 us,,,,,[1 SOF],[Frame: 1305] 0,,22732,1:21.241.318,4.583 us,,,01,00,IN txn (STALL), 0,,22735,1:21.242.315,2.003.083 ms,,,,,[3 SOF],[Frames: 1306 - 1308] 0,,22736,1:21.244.319,12.979 us,8 B,,01,00,SETUP txn,81 06 00 22 00 00 A3 00 0,,22740,1:21.245.315,2.812 us,,,,,[1 SOF],[Frame: 1309] 0,,22741,1:21.245.319,51.062 us,64 B,,01,00,IN txn,05 01 09 04 A1 01 A1 02 75 08 95 04 15 00 26 FF 00 35 00 46 FF 00 09 30… 0,,22745,1:21.246.316,2.833 us,,,,,[1 SOF],[Frame: 1310] 0,,22746,1:21.246.319,31.750 us,35 B,,01,00,IN txn,02 06 00 FF 75 01 95 08 25 01 45 01 09 01 81 02 C0 A1 02 75 08 95 08 46… 0,,22750,1:21.247.316,1.002.958 ms,,,,,[2 SOF],[Frames: 1311 - 1312] 0,,22751,1:21.248.321,7.645 us,0 B,,01,00,OUT txn, 0,,22755,1:21.249.316,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1313 - 1264] [Periodic Timeout] 0,,22756,1:21.280.324,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22757,1:23.249.594,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1265 - 1216] [Periodic Timeout] 0,,22758,1:23.296.603,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22759,1:25.249.871,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1217 - 1168] [Periodic Timeout] 0,,22760,1:25.312.883,1.984.279.958 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22761,1:27.250.149,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1169 - 1120] [Periodic Timeout] 0,,22762,1:27.329.163,1.984.279.979 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22763,1:29.250.426,1.999.280.291 s,,,,,[2000 SOF],[Frames: 1121 - 1072] [Periodic Timeout] 0,,22764,1:29.345.443,1.984.279.958 s,,,01,01,[63 IN-NAK],[Periodic Timeout] 0,,22765,1:31.250.704,1.798.252.375 s,,,,,[1799 SOF],[Frames: 1073 - 823] 0,,22766,1:31.361.723,1.664.235.541 s,,,01,01,[53 IN-NAK], 0,,22767,1:33.048.954,,,,,,Capture stopped,[Fri 03 Jun 2016 12:01:34 BST] fwupd-1.7.5/plugins/ebitdo/data/update.tdc000066400000000000000000017532571420024370600205110ustar00rootroot00000000000000TPDC?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.7.5/plugins/ebitdo/ebitdo-legacy.quirk000066400000000000000000000005571420024370600213720ustar00rootroot00000000000000# 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.7.5/plugins/ebitdo/ebitdo.quirk000066400000000000000000000040721420024370600201240ustar00rootroot00000000000000# 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.7.5/plugins/ebitdo/fu-ebitdo-common.c000066400000000000000000000044441420024370600211140ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ebitdo-common.h" const gchar * fu_ebitdo_pkt_type_to_string(FuEbitdoPktType cmd) { if (cmd == FU_EBITDO_PKT_TYPE_USER_CMD) return "user-cmd"; if (cmd == FU_EBITDO_PKT_TYPE_USER_DATA) return "user-data"; if (cmd == FU_EBITDO_PKT_TYPE_MID_CMD) return "mid-cmd"; return NULL; } const gchar * fu_ebitdo_pkt_cmd_to_string(FuEbitdoPktCmd cmd) { if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_DATA) return "fw-update-data"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER) return "fw-update-header"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_OK) return "fw-update-ok"; if (cmd == FU_EBITDO_PKT_CMD_FW_UPDATE_ERROR) return "fw-update-error"; if (cmd == FU_EBITDO_PKT_CMD_FW_GET_VERSION) return "fw-get-version"; if (cmd == FU_EBITDO_PKT_CMD_FW_SET_VERSION) return "fw-set-version"; if (cmd == FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID) return "fw-set-encode-id"; if (cmd == FU_EBITDO_PKT_CMD_ACK) return "ack"; if (cmd == FU_EBITDO_PKT_CMD_NAK) return "nak"; if (cmd == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA) return "update-firmware-data"; if (cmd == FU_EBITDO_PKT_CMD_TRANSFER_ABORT) return "transfer-abort"; if (cmd == FU_EBITDO_PKT_CMD_VERIFICATION_ID) return "verification-id"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID) return "get-verification-id"; if (cmd == FU_EBITDO_PKT_CMD_VERIFY_ERROR) return "verify-error"; if (cmd == FU_EBITDO_PKT_CMD_VERIFY_OK) return "verify-ok"; if (cmd == FU_EBITDO_PKT_CMD_TRANSFER_TIMEOUT) return "transfer-timeout"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERSION) return "get-version"; if (cmd == FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE) return "get-version-response"; return NULL; } void fu_ebitdo_dump_pkt(FuEbitdoPkt *hdr) { g_print("PktLength: 0x%02x\n", hdr->pkt_len); g_print("PktType: 0x%02x [%s]\n", hdr->type, fu_ebitdo_pkt_type_to_string(hdr->type)); g_print("CmdSubtype: 0x%02x [%s]\n", hdr->subtype, fu_ebitdo_pkt_cmd_to_string(hdr->subtype)); g_print("CmdLen: 0x%04x\n", GUINT16_FROM_LE(hdr->cmd_len)); g_print("Cmd: 0x%02x [%s]\n", hdr->cmd, fu_ebitdo_pkt_cmd_to_string(hdr->cmd)); g_print("Payload Len: 0x%04x\n", GUINT16_FROM_LE(hdr->payload_len)); } fwupd-1.7.5/plugins/ebitdo/fu-ebitdo-common.h000066400000000000000000000046111420024370600211150ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* little endian */ typedef struct __attribute__((packed)) { guint8 pkt_len; guint8 type; /* FuEbitdoPktType */ guint8 subtype; /* FuEbitdoPktCmd */ guint16 cmd_len; guint8 cmd; /* FuEbitdoPktCmd */ guint16 payload_len; /* optional */ } FuEbitdoPkt; #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 */ typedef enum { FU_EBITDO_PKT_TYPE_USER_CMD = 0x00, FU_EBITDO_PKT_TYPE_USER_DATA = 0x01, FU_EBITDO_PKT_TYPE_MID_CMD = 0x02, FU_EBITDO_PKT_TYPE_LAST } FuEbitdoPktType; /* commands */ typedef enum { FU_EBITDO_PKT_CMD_FW_UPDATE_DATA = 0x00, /* update firmware data */ FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER = 0x01, /* update firmware header */ FU_EBITDO_PKT_CMD_FW_UPDATE_OK = 0x02, /* mark update as successful */ FU_EBITDO_PKT_CMD_FW_UPDATE_ERROR = 0x03, /* update firmware error */ FU_EBITDO_PKT_CMD_FW_GET_VERSION = 0x04, /* get cur firmware vision */ FU_EBITDO_PKT_CMD_FW_SET_VERSION = 0x05, /* set firmware version */ FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID = 0x06, /* set app firmware encode ID */ FU_EBITDO_PKT_CMD_ACK = 0x14, /* acknowledge */ FU_EBITDO_PKT_CMD_NAK = 0x15, /* negative acknowledge */ FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA = 0x16, /* update firmware data */ FU_EBITDO_PKT_CMD_TRANSFER_ABORT = 0x18, /* aborts transfer */ FU_EBITDO_PKT_CMD_VERIFICATION_ID = 0x19, /* verification id (only BT?) */ FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID = 0x1a, /* verification id (only BT) */ FU_EBITDO_PKT_CMD_VERIFY_ERROR = 0x1b, /* verification error */ FU_EBITDO_PKT_CMD_VERIFY_OK = 0x1c, /* verification successful */ FU_EBITDO_PKT_CMD_TRANSFER_TIMEOUT = 0x1d, /* send or receive data timeout */ FU_EBITDO_PKT_CMD_GET_VERSION = 0x21, /* get fw ver, joystick mode */ FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE = 0x22, /* get fw version response */ FU_EBITDO_PKT_CMD_FW_LAST } FuEbitdoPktCmd; const gchar * fu_ebitdo_pkt_cmd_to_string(FuEbitdoPktCmd cmd); const gchar * fu_ebitdo_pkt_type_to_string(FuEbitdoPktType type); void fu_ebitdo_dump_pkt(FuEbitdoPkt *hdr); fwupd-1.7.5/plugins/ebitdo/fu-ebitdo-device.c000066400000000000000000000470411420024370600210630ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-ebitdo-common.h" #include "fu-ebitdo-device.h" #include "fu-ebitdo-firmware.h" struct _FuEbitdoDevice { FuUsbDevice parent_instance; guint32 serial[9]; }; G_DEFINE_TYPE(FuEbitdoDevice, fu_ebitdo_device, FU_TYPE_USB_DEVICE) 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)); guint8 packet[FU_EBITDO_USB_EP_SIZE] = {0}; gsize actual_length; guint8 ep_out = FU_EBITDO_USB_RUNTIME_EP_OUT; g_autoptr(GError) error_local = NULL; FuEbitdoPkt *hdr = (FuEbitdoPkt *)packet; /* 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 */ hdr->type = type; hdr->subtype = subtype; /* do we have a payload */ if (in_len > 0) { hdr->cmd_len = GUINT16_TO_LE(in_len + 3); hdr->cmd = cmd; hdr->payload_len = GUINT16_TO_LE(in_len); if (!fu_memcpy_safe(packet, sizeof(packet), 0x08, /* dst */ in, in_len, 0x0, /* src */ in_len, error)) return FALSE; hdr->pkt_len = (guint8)(in_len + 7); } else { hdr->cmd_len = GUINT16_TO_LE(in_len + 1); hdr->cmd = cmd; hdr->pkt_len = 5; } /* debug */ if (g_getenv("FWUPD_EBITDO_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "->DEVICE", packet, (gsize)hdr->pkt_len + 1); fu_ebitdo_dump_pkt(hdr); } /* get data from device */ if (!g_usb_device_interrupt_transfer(usb_device, ep_out, packet, FU_EBITDO_USB_EP_SIZE, &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(GError) error_local = NULL; FuEbitdoPkt *hdr = (FuEbitdoPkt *)packet; /* 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, FU_EBITDO_USB_EP_SIZE, &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 */ if (g_getenv("FWUPD_EBITDO_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "<-DEVICE", packet, actual_length); fu_ebitdo_dump_pkt(hdr); } /* get-version (booloader) */ if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && hdr->cmd == FU_EBITDO_PKT_CMD_FW_GET_VERSION) { if (out != NULL) { if (hdr->payload_len < 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, hdr->payload_len); return FALSE; } if (!fu_memcpy_safe(out, out_len, 0x0, /* dst */ packet, sizeof(packet), sizeof(FuEbitdoPkt), /* src */ out_len, error)) return FALSE; } return TRUE; } /* get-version (firmware) -- not a packet, just raw data! */ if (hdr->pkt_len == 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 (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_VERIFICATION_ID) { if (out != NULL) { if (hdr->cmd_len != 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, hdr->cmd_len); return FALSE; } if (!fu_memcpy_safe(out, out_len, 0x0, /* dst */ packet, sizeof(packet), sizeof(FuEbitdoPkt) - 3, /* src */ hdr->cmd_len, error)) return FALSE; } return TRUE; } /* update-firmware-data */ if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD && hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && hdr->payload_len == 0x00) { if (hdr->cmd != 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(hdr->cmd)); return FALSE; } return TRUE; } /* unhandled */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected device response"); return FALSE; } static void fu_ebitdo_device_set_version(FuEbitdoDevice *self, guint32 version) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("%u.%02u", version / 100, version % 100); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version_raw(FU_DEVICE(self), version); fu_device_set_version(FU_DEVICE(self), tmp); } 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); gdouble tmp; 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; } tmp = (gdouble)GUINT32_FROM_LE(version_tmp); fu_ebitdo_device_set_version(self, 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; } tmp = (gdouble)GUINT32_FROM_LE(version_tmp); fu_ebitdo_device_set_version(self, 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)); fu_device_emit_request(device, request); return TRUE; } 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2); /* 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, 0x0, 32); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (g_getenv("FWUPD_EBITDO_VERBOSE") != NULL) { 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, chunks->len); } 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) { /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_ebitdo_device_parent_class)->probe(device, error)) return FALSE; /* 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_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_EBITDO_FIRMWARE); } 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; } fwupd-1.7.5/plugins/ebitdo/fu-ebitdo-device.h000066400000000000000000000004471420024370600210670ustar00rootroot00000000000000/* * 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.7.5/plugins/ebitdo/fu-ebitdo-firmware.c000066400000000000000000000074071420024370600214420ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ebitdo-firmware.h" struct _FuEbitdoFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuEbitdoFirmware, fu_ebitdo_firmware, FU_TYPE_FIRMWARE) /* little endian */ typedef struct __attribute__((packed)) { guint32 version; guint32 destination_addr; guint32 destination_len; guint32 reserved[4]; } FuEbitdoFirmwareHeader; static gboolean fu_ebitdo_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuEbitdoFirmwareHeader *hdr; guint32 payload_len; g_autofree gchar *version = NULL; g_autoptr(FuFirmware) img_hdr = fu_firmware_new(); g_autoptr(GBytes) fw_hdr = NULL; g_autoptr(GBytes) fw_payload = NULL; /* corrupt */ if (g_bytes_get_size(fw) < sizeof(FuEbitdoFirmwareHeader)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware too small for header"); return FALSE; } /* check the file size */ hdr = (FuEbitdoFirmwareHeader *)g_bytes_get_data(fw, NULL); payload_len = (guint32)(g_bytes_get_size(fw) - sizeof(FuEbitdoFirmwareHeader)); if (payload_len != GUINT32_FROM_LE(hdr->destination_len)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", (guint)GUINT32_FROM_LE(hdr->destination_len), (guint)payload_len); return FALSE; } /* check if this is firmware */ for (guint i = 0; i < 4; i++) { if (hdr->reserved[i] != 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data invalid, reserved[%u] = 0x%04x", i, hdr->reserved[i]); return FALSE; } } /* parse version */ version = g_strdup_printf("%.2f", GUINT32_FROM_LE(hdr->version) / 100.f); fu_firmware_set_version(firmware, version); fu_firmware_set_version_raw(firmware, GUINT32_FROM_LE(hdr->version)); /* add header */ fw_hdr = fu_common_bytes_new_offset(fw, 0x0, sizeof(FuEbitdoFirmwareHeader), 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_common_bytes_new_offset(fw, sizeof(FuEbitdoFirmwareHeader), payload_len, error); if (fw_payload == NULL) return FALSE; fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_set_addr(firmware, GUINT32_FROM_LE(hdr->destination_addr)); fu_firmware_set_bytes(firmware, fw_payload); return TRUE; } static GBytes * fu_ebitdo_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; /* header then payload */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, fu_firmware_get_addr(firmware), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, g_bytes_get_size(blob), G_LITTLE_ENDIAN); for (guint i = 0; i < 4; i++) fu_byte_array_append_uint32(buf, 0, G_LITTLE_ENDIAN); fu_byte_array_append_bytes(buf, blob); return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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; } FuFirmware * fu_ebitdo_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EBITDO_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/ebitdo/fu-ebitdo-firmware.h000066400000000000000000000005341420024370600214410ustar00rootroot00000000000000/* * 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) FuFirmware * fu_ebitdo_firmware_new(void); fwupd-1.7.5/plugins/ebitdo/fu-plugin-ebitdo.c000066400000000000000000000010301420024370600211060ustar00rootroot00000000000000/* * 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" static void fu_plugin_ebitdo_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_EBITDO_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_EBITDO_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_ebitdo_init; } fwupd-1.7.5/plugins/ebitdo/meson.build000066400000000000000000000011201420024370600177320ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginEbitdo"'] install_data(['ebitdo.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ebitdo', fu_hash, sources : [ 'fu-plugin-ebitdo.c', 'fu-ebitdo-common.c', 'fu-ebitdo-device.c', 'fu-ebitdo-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/ebitdo/tests/000077500000000000000000000000001420024370600167405ustar00rootroot00000000000000fwupd-1.7.5/plugins/ebitdo/tests/ebitdo.bin000066400000000000000000000000471420024370600207010ustar00rootroot000000000000000 hello worldfwupd-1.7.5/plugins/ebitdo/tests/ebitdo.builder.xml000066400000000000000000000002131420024370600223510ustar00rootroot00000000000000 0x10002 0x3000 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/elanfp/000077500000000000000000000000001420024370600155755ustar00rootroot00000000000000fwupd-1.7.5/plugins/elanfp/README.md000066400000000000000000000012411420024370600170520ustar00rootroot00000000000000Elan Fingerprint Sensor Support ================================= 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&REV_0304` * `USB\VID_04F3&PID_0C7E` * `USB\VID_04F3` Vendor ID Security ------------------ The vendor ID is set from the USB vendor, in this instance set to `USB:0x04F3` fwupd-1.7.5/plugins/elanfp/elanfp.quirk000066400000000000000000000000501420024370600201120ustar00rootroot00000000000000[USB\VID_04F3&PID_0C7E] Plugin = elanfp fwupd-1.7.5/plugins/elanfp/fu-elanfp-device.c000066400000000000000000000253221420024370600210570ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.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 1000 #define BULK_RECV_TIMEOUT_MS 1000 #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 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, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual = 0; guint8 buftmp[61] = {request, 0}; if (buf != NULL) { if (!fu_memcpy_safe(buftmp, sizeof(buftmp), 0x1, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, 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_INTERFACE, request, /* request */ 0x00, /* value */ 0x00, /* index */ buftmp, bufsz + 1, &actual, CTRL_SEND_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to send command: "); return FALSE; } if (actual != bufsz + 1) { 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)); gsize actual = 0; if (!g_usb_device_bulk_transfer(usb_device, ELAN_EP_CMD_IN, 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_common_read_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_DEVICE_FLAG_FIRST_BLOCK; else if (i == chunks->len - 1) databuf[0] = FU_CFU_DEVICE_FLAG_LAST_BLOCK; /* length */ databuf[1] = fu_chunk_get_data_sz(chk); /* sequence number */ if (!fu_common_write_uint16_safe(databuf, sizeof(databuf), 0x2, i + 1, G_LITTLE_ENDIAN, error)) return FALSE; /* address */ if (!fu_common_write_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), 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_DEVICE_STATUS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send chunk %u: %s", i + 1, fu_cfu_device_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), 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_device_offer_to_string(recvbuf[13]), fu_cfu_device_reject_to_string(recvbuf[9])); if (recvbuf[13] == FU_CFU_DEVICE_OFFER_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_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); } 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.7.5/plugins/elanfp/fu-elanfp-device.h000066400000000000000000000005551420024370600210650ustar00rootroot00000000000000/* * 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) struct _FuElanfpDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.7.5/plugins/elanfp/fu-elanfp-firmware.c000066400000000000000000000137441420024370600214410ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-elanfp-firmware.h" struct _FuElanfpFirmware { FuFirmwareClass parent_instance; guint32 format_version; }; G_DEFINE_TYPE(FuElanfpFirmware, fu_elanfp_firmware, FU_TYPE_FIRMWARE) 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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); const guint8 *buf; gsize bufsz; guint32 tag = 0; gsize offset = 0x00; guint img_cnt = 0; /* check the tag */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_common_read_uint32_safe(buf, bufsz, offset + 0x0, &tag, G_LITTLE_ENDIAN, error)) return FALSE; if (tag != 0x46325354) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "tag is not valid"); return FALSE; } /* file format version */ if (!fu_common_read_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; /* check sanity */ if (img_cnt++ > 256) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "too many images detected"); return FALSE; } /* type, reserved, start-addr, len */ if (!fu_common_read_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_common_read_uint32_safe(buf, bufsz, offset + 0x8, &start_addr, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_addr(img, start_addr); if (!fu_common_read_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_common_bytes_new_offset(fw, start_addr, length, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(img, blob, flags, error)) return FALSE; fu_firmware_add_image(firmware, img); offset += 0x10; } /* success */ return TRUE; } static GBytes * 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_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_elanfp_firmware_init(FuElanfpFirmware *self) { } static void fu_elanfp_firmware_class_init(FuElanfpFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); 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; } FuFirmware * fu_elanfp_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ELANFP_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/elanfp/fu-elanfp-firmware.h000066400000000000000000000012241420024370600214340ustar00rootroot00000000000000/* * 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 FuFirmware * fu_elanfp_firmware_new(void); fwupd-1.7.5/plugins/elanfp/fu-plugin-elanfp.c000066400000000000000000000007311420024370600211130ustar00rootroot00000000000000/* * Copyright (C) 2021 * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elanfp-device.h" #include "fu-elanfp-firmware.h" static void fu_plugin_elanfp_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANFP_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ELANFP_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_elanfp_init; } fwupd-1.7.5/plugins/elanfp/meson.build000066400000000000000000000010621420024370600177360ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginElanfp"'] install_data(['elanfp.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_elanfp', fu_hash, sources : [ 'fu-plugin-elanfp.c', 'fu-elanfp-device.c', 'fu-elanfp-firmware.c' # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/elanfp/tests/000077500000000000000000000000001420024370600167375ustar00rootroot00000000000000fwupd-1.7.5/plugins/elanfp/tests/elanfp.bin000066400000000000000000000002401420024370600206720ustar00rootroot00000000000000TS2F#r`sptu4xV4xV4 hello world4 hello worldfwupd-1.7.5/plugins/elanfp/tests/elanfp.builder.xml000066400000000000000000000014351420024370600223560ustar00rootroot00000000000000 0x123 0x72 0x1234 0x5678 0x1 0x73 0x1234 0x5678 0x2 0x74 aGVsbG8gd29ybGQ= 0x8001234 0x75 aGVsbG8gd29ybGQ= 0x8001234 fwupd-1.7.5/plugins/elantp/000077500000000000000000000000001420024370600156135ustar00rootroot00000000000000fwupd-1.7.5/plugins/elantp/README.md000066400000000000000000000032561420024370600171000ustar00rootroot00000000000000# 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 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.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` Additionally another instance ID is added which corresponds to the IC type & module ID: * `ELANTP\ICTYPE_09&MOD_1234` ## 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: ### 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`. fwupd-1.7.5/plugins/elantp/elantp.quirk000066400000000000000000000036141420024370600201570ustar00rootroot00000000000000[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 # 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.7.5/plugins/elantp/fu-elantp-common.h000066400000000000000000000025161420024370600211510ustar00rootroot00000000000000/* * 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_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_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_DELAY_UNLOCK 100 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK 35 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK_512 50 /* ms */ fwupd-1.7.5/plugins/elantp/fu-elantp-firmware.c000066400000000000000000000123331420024370600214660ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" struct _FuElantpFirmware { FuFirmwareClass parent_instance; guint16 module_id; guint16 iap_addr; }; G_DEFINE_TYPE(FuElantpFirmware, fu_elantp_firmware, FU_TYPE_FIRMWARE) /* firmware block update */ #define ETP_IAP_START_ADDR_WRDS 0x0083 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_iap_addr(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); return self->iap_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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); gsize bufsz = 0; guint16 iap_addr_wrds; guint16 module_id_wrds; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* presumably in words */ if (!fu_common_read_uint16_safe(buf, bufsz, 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_common_read_uint16_safe(buf, bufsz, 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_common_read_uint16_safe(buf, bufsz, module_id_wrds * 2, &self->module_id, G_LITTLE_ENDIAN, error)) return FALSE; /* check signature */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { gsize offset = bufsz - sizeof(elantp_signature); for (gsize i = 0; i < sizeof(elantp_signature); i++) { guint8 tmp = 0x0; if (!fu_common_read_uint8_safe(buf, bufsz, offset + 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; } } } /* whole image */ fu_firmware_set_bytes(firmware, fw); 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 GBytes * 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); if (!fu_common_write_uint16_safe(buf->data, buf->len, ETP_IAP_START_ADDR_WRDS * 2, self->iap_addr / 2, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_uint16_safe(buf->data, buf->len, self->iap_addr, (self->iap_addr + 2) / 2, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_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_byte_array_free_to_bytes(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->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.7.5/plugins/elantp/fu-elantp-firmware.h000066400000000000000000000007371420024370600215000ustar00rootroot00000000000000/* * 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_iap_addr(FuElantpFirmware *self); fwupd-1.7.5/plugins/elantp/fu-elantp-hid-device.c000066400000000000000000000453261420024370600216630ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" struct _FuElantpHidDevice { FuUdevDevice parent_instance; guint16 ic_page_count; guint16 iap_type; guint16 iap_ctrl; guint16 iap_password; guint16 module_id; guint16 fw_page_size; guint8 pattern; }; G_DEFINE_TYPE(FuElantpHidDevice, fu_elantp_hid_device, FU_TYPE_UDEV_DEVICE) 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_common_string_append_kx(str, idt, "ModuleId", self->module_id); fu_common_string_append_kx(str, idt, "Pattern", self->pattern); fu_common_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); fu_common_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); fu_common_string_append_kx(str, idt, "IapType", self->iap_type); fu_common_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); } static gboolean fu_elantp_hid_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_elantp_hid_device_parent_class)->probe(device, error)) return FALSE; /* 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)) < 0x3000 || fu_udev_device_get_model(FU_UDEV_DEVICE(device)) >= 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; if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "SetReport", tx, txsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(txsz), tx, NULL, 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, error)) return FALSE; if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) fu_common_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_common_write_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_common_write_uint16(buf + 0x1, reg, G_LITTLE_ENDIAN); fu_common_write_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_common_read_uint16(buf, G_LITTLE_ENDIAN); /* 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_hid_device_setup(FuDevice *device, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); guint16 fwver; guint16 iap_ver; guint16 tmp; guint8 buf[2] = {0x0}; guint8 ic_type; g_autofree gchar *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; g_autofree gchar *instance_id_ic_type = NULL; g_autofree gchar *version_bl = NULL; g_autofree gchar *version = 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_common_read_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_common_read_uint16(buf, G_LITTLE_ENDIAN); if (fwver == 0xFFFF || fwver == ETP_CMD_I2C_FW_VERSION) fwver = 0; version = fu_common_version_from_uint16(fwver, FWUPD_VERSION_FORMAT_HEX); fu_device_set_version(device, version); /* 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_common_read_uint16(buf, G_LITTLE_ENDIAN); } version_bl = fu_common_version_from_uint16(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_common_read_uint16(buf, G_LITTLE_ENDIAN); /* define the extra instance IDs */ instance_id1 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&MOD_%04X", fu_udev_device_get_vendor(udev_device), fu_udev_device_get_model(udev_device), self->module_id); fu_device_add_instance_id(device, instance_id1); /* 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_common_read_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_common_read_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; } else { ic_type = (tmp >> 8) & 0xFF; } instance_id_ic_type = g_strdup_printf("ELANTP\\ICTYPE_%02X", ic_type); fu_device_add_instance_id(device, instance_id_ic_type); /* define the extra instance IDs (ic_type + module_id) */ instance_id2 = g_strdup_printf("ELANTP\\ICTYPE_%02X&MOD_%04X", ic_type, self->module_id); fu_device_add_instance_id(device, instance_id2); /* 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; } /* 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; /* 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; 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_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; /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 30); 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); chunks = fu_chunk_array_new(buf + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 csum_tmp = fu_common_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_common_write_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; g_usleep(self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512 * 1000 : ELANTP_DELAY_WRITE_BLOCK * 1000); 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_common_read_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_progress_sleep(fu_progress_get_child(progress), ELANTP_DELAY_COMPLETE); 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_debug("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; g_usleep(ELANTP_DELAY_RESET * 1000); } /* 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_common_read_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_common_read_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_common_read_uint16(buf, G_LITTLE_ENDIAN); } 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_common_read_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_cmd(self, ETP_CMD_I2C_IAP, self->iap_password, error)) return FALSE; g_usleep(ELANTP_DELAY_UNLOCK * 1000); 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; g_usleep(ELANTP_DELAY_RESET * 1000); 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->ic_page_count = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpIapPassword") == 0) { if (!fu_common_strtoull_full(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_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.7.5/plugins/elantp/fu-elantp-hid-device.h000066400000000000000000000004731420024370600216620ustar00rootroot00000000000000/* * 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.7.5/plugins/elantp/fu-elantp-i2c-device.c000066400000000000000000000516121420024370600215670ustar00rootroot00000000000000/* * 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 { FuUdevDevice 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; }; G_DEFINE_TYPE(FuElantpI2cDevice, fu_elantp_i2c_device, FU_TYPE_UDEV_DEVICE) 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_common_string_append_kx(str, idt, "I2cAddr", self->i2c_addr); fu_common_string_append_kx(str, idt, "ModuleId", self->module_id); fu_common_string_append_kx(str, idt, "Pattern", self->pattern); fu_common_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); fu_common_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); fu_common_string_append_kx(str, idt, "IapType", self->iap_type); fu_common_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); } static gboolean fu_elantp_i2c_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_elantp_i2c_device_parent_class)->probe(device, error)) return FALSE; /* check is valid */ 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) { if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "Write", tx, txsz); if (!fu_udev_device_pwrite_full(FU_UDEV_DEVICE(self), 0, tx, txsz, error)) return FALSE; if (rxsz == 0) return TRUE; if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(self), 0, rx, rxsz, error)) return FALSE; if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) fu_common_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_common_write_uint16(buf + 0x0, reg, G_LITTLE_ENDIAN); fu_common_write_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_common_write_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_common_read_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 *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; g_autofree gchar *instance_id_ic_type = NULL; g_autofree gchar *version_bl = NULL; g_autofree gchar *version = 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_common_read_uint16_safe(buf, sizeof(buf), 20, &vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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 */ if (vid != 0x0 && pid != 0x0) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("HIDRAW\\VID_%04X&PID_%04X", vid, pid); fu_device_add_instance_id(device, devid); } /* 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_common_read_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_common_read_uint16_safe(buf, sizeof(buf), 0x0, &fwver, G_LITTLE_ENDIAN, error)) return FALSE; if (fwver == 0xFFFF || fwver == ETP_CMD_I2C_FW_VERSION) fwver = 0; version = fu_common_version_from_uint16(fwver, FWUPD_VERSION_FORMAT_HEX); fu_device_set_version(device, version); /* 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_common_read_uint16_safe(buf, sizeof(buf), 0x0, &iap_ver, G_LITTLE_ENDIAN, error)) return FALSE; } version_bl = fu_common_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_common_read_uint16_safe(buf, sizeof(buf), 0x0, &self->module_id, G_LITTLE_ENDIAN, error)) return FALSE; /* define the extra instance IDs */ instance_id1 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&MOD_%04X", vid, pid, self->module_id); fu_device_add_instance_id(device, instance_id1); /* 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_common_read_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_common_read_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; ic_type = tmp & 0xFF; } else { ic_type = (tmp >> 8) & 0xFF; } instance_id_ic_type = g_strdup_printf("ELANTP\\ICTYPE_%02X", ic_type); fu_device_add_instance_id(device, instance_id_ic_type); /* define the extra instance IDs (ic_type + module_id) */ instance_id2 = g_strdup_printf("ELANTP\\ICTYPE_%02X&MOD_%04X", ic_type, self->module_id); fu_device_add_instance_id(device, instance_id2); /* 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, NULL)) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(device), I2C_SLAVE_FORCE, GINT_TO_POINTER(addr), NULL, 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_full(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); 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; /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1); /* 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 */ buf = g_bytes_get_data(fw, &bufsz); iap_addr = fu_elantp_firmware_get_iap_addr(firmware_elantp); chunks = fu_chunk_array_new(buf + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 csum_tmp = fu_common_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_common_write_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; g_usleep(self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512 * 1000 : ELANTP_DELAY_WRITE_BLOCK * 1000); 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)chunks->len); } 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_common_read_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_progress_sleep(fu_progress_get_child(progress), ELANTP_DELAY_COMPLETE); 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_debug("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; g_usleep(ELANTP_DELAY_RESET * 1000); } /* 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_common_read_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_common_read_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_common_read_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_common_read_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; g_usleep(ELANTP_DELAY_UNLOCK * 1000); 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; g_usleep(ELANTP_DELAY_RESET * 1000); 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_write_cmd(self, 0x0306, 0x003, error)) { g_prefix_error(error, "cannot switch to TP PTP mode: "); return FALSE; } if (!fu_elantp_i2c_device_ensure_iap_ctrl(self, error)) 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->ic_page_count = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpIapPassword") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->iap_password = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpI2cTargetAddress") == 0) { if (!fu_common_strtoull_full(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_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_summary(FU_DEVICE(self), "Touchpad (I²C recovery)"); 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_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE); } static void fu_elantp_i2c_device_finalize(GObject *object) { 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.7.5/plugins/elantp/fu-elantp-i2c-device.h000066400000000000000000000004731420024370600215730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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, FuUdevDevice) fwupd-1.7.5/plugins/elantp/fu-plugin-elantp.c000066400000000000000000000025601420024370600211510ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" #include "fu-elantp-i2c-device.h" static gboolean fu_plugin_elantp_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")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not required"); return FALSE; } return TRUE; } static void fu_plugin_elantp_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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_context_add_quirk_key(ctx, "ElantpI2cTargetAddress"); fu_context_add_quirk_key(ctx, "ElantpIapPassword"); fu_context_add_quirk_key(ctx, "ElantpIcPageCount"); fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANTP_I2C_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANTP_HID_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_elantp_init; vfuncs->device_created = fu_plugin_elantp_device_created; } fwupd-1.7.5/plugins/elantp/fu-self-test.c000066400000000000000000000033221420024370600202750ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { 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.7.5/plugins/elantp/meson.build000066400000000000000000000025401420024370600177560ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginElantp"'] install_data([ 'elantp.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_elantp', fu_hash, sources : [ 'fu-plugin-elantp.c', 'fu-elantp-firmware.c', # fuzzing 'fu-elantp-hid-device.c', 'fu-elantp-i2c-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) endif 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-elantp-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('elantp-self-test', e, env : env) endif fwupd-1.7.5/plugins/elantp/tests/000077500000000000000000000000001420024370600167555ustar00rootroot00000000000000fwupd-1.7.5/plugins/elantp/tests/elantp.bin000066400000000000000000000070251420024370600207360ustar00rootroot00000000000000hello worldU3fwupd-1.7.5/plugins/elantp/tests/elantp.builder.xml000066400000000000000000000002321420024370600224040ustar00rootroot00000000000000 0xe00 0x2 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/emmc/000077500000000000000000000000001420024370600152515ustar00rootroot00000000000000fwupd-1.7.5/plugins/emmc/README.md000066400000000000000000000013011420024370600165230ustar00rootroot00000000000000# 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%` * `EMMC\%MANFID%&%OEMID%` * `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. ## 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. fwupd-1.7.5/plugins/emmc/emmc.quirk000066400000000000000000000001031420024370600172410ustar00rootroot00000000000000# match all devices with this udev subsystem [BLOCK] Plugin = emmc fwupd-1.7.5/plugins/emmc/fu-emmc-device.c000066400000000000000000000412611420024370600202070ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #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 */ /* 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) struct _FuEmmcDevice { FuUdevDevice parent_instance; guint32 sect_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_common_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 for %s", name, sysfs); 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 *name_only = NULL; g_autofree gchar *man_oem = NULL; g_autofree gchar *man_oem_name = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GRegex) dev_regex = NULL; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_emmc_device_parent_class)->probe(device, error)) return FALSE; 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_set_name(device, tmp); name_only = g_strdup_printf("EMMC\\%s", fu_device_get_name(device)); fu_device_add_instance_id(device, name_only); /* 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; man_oem = g_strdup_printf("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT, manfid, oemid); fu_device_add_instance_id(device, man_oem); 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); /* 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); } return TRUE; } static gboolean fu_emmc_read_extcsd(FuEmmcDevice *self, guint8 *ext_csd, gsize ext_csd_sz, GError **error) { struct mmc_ioc_cmd idata = {0x0}; idata.write_flag = 0; idata.opcode = MMC_SEND_EXT_CSD; idata.arg = 0; idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; idata.blksz = 512; idata.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, 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; gsize total_done; guint32 arg; guint32 sect_done = 0; 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 45); 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); /* 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) + 3 * sizeof(struct mmc_ioc_cmd)); multi_cmd->num_of_cmds = 3; /* 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 image chunk */ multi_cmd->cmds[1].opcode = MMC_WRITE_BLOCK; multi_cmd->cmds[1].blksz = self->sect_size; multi_cmd->cmds[1].blocks = 1; multi_cmd->cmds[1].arg = arg; multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; multi_cmd->cmds[1].write_flag = 1; /* return device into normal mode */ multi_cmd->cmds[2].opcode = MMC_SWITCH; multi_cmd->cmds[2].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[2].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[2].write_flag = 1; fu_progress_step_done(progress); /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, /* start addr */ 0x00, /* page_sz */ self->sect_size); while (sect_done == 0) { for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); mmc_ioc_cmd_set_data(multi_cmd->cmds[1], fu_chunk_get_data(chk)); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_MULTI_CMD, (guint8 *)multi_cmd, NULL, 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[2], NULL, &error_local)) { g_prefix_error(error, "%s: ", error_local->message); } return FALSE; } if (!fu_emmc_read_extcsd(self, ext_csd, sizeof(ext_csd), error)) return FALSE; /* if we need to restart the download */ 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) { if (failure_cnt >= 3) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "programming failed"); return FALSE; } failure_cnt++; g_debug("programming failed: retrying (%u)", failure_cnt); break; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)chunks->len); } } fu_progress_step_done(progress); /* sanity check */ 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 (!ext_csd[EXT_CSD_FFU_FEATURES]) { 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, 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, &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 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"); } 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->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.7.5/plugins/emmc/fu-emmc-device.h000066400000000000000000000004501420024370600202070ustar00rootroot00000000000000/* * 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.7.5/plugins/emmc/fu-plugin-emmc.c000066400000000000000000000007441420024370600202470ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-emmc-device.h" static void fu_plugin_emmc_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_EMMC_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_emmc_init; } fwupd-1.7.5/plugins/emmc/meson.build000066400000000000000000000011261420024370600174130ustar00rootroot00000000000000if get_option('plugin_emmc') if not get_option('gudev') error('gudev is required for plugin_emmc') endif cargs = ['-DG_LOG_DOMAIN="FuPluginEmmc"'] install_data(['emmc.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_emmc', fu_hash, sources : [ 'fu-plugin-emmc.c', 'fu-emmc-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/ep963x/000077500000000000000000000000001420024370600153665ustar00rootroot00000000000000fwupd-1.7.5/plugins/ep963x/README.md000066400000000000000000000015461420024370600166530ustar00rootroot00000000000000# Explore 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&REV_0001` * `USB\VID_17EF&PID_7226` * `USB\VID_17EF` ## 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`. fwupd-1.7.5/plugins/ep963x/ep963x.quirk000066400000000000000000000000501420024370600174740ustar00rootroot00000000000000[USB\VID_17EF&PID_7226] Plugin = ep963x fwupd-1.7.5/plugins/ep963x/fu-ep963x-common.c000066400000000000000000000012261420024370600204670ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ep963x-common.h" const gchar * fu_ep963x_smbus_strerror(guint8 val) { if (val == FU_EP963_SMBUS_ERROR_NONE) return "none"; if (val == FU_EP963_SMBUS_ERROR_ADDRESS) return "address"; if (val == FU_EP963_SMBUS_ERROR_NO_ACK) return "no-ack"; if (val == FU_EP963_SMBUS_ERROR_ARBITRATION) return "arbitration"; if (val == FU_EP963_SMBUS_ERROR_COMMAND) return "command"; if (val == FU_EP963_SMBUS_ERROR_TIMEOUT) return "timeout"; if (val == FU_EP963_SMBUS_ERROR_BUSY) return "busy"; return "unknown"; } fwupd-1.7.5/plugins/ep963x/fu-ep963x-common.h000066400000000000000000000042321420024370600204740ustar00rootroot00000000000000/* * 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 /* byte 0x07 */ #define FU_EP963_SMBUS_ERROR_NONE 0x00 #define FU_EP963_SMBUS_ERROR_ADDRESS 0x01 #define FU_EP963_SMBUS_ERROR_NO_ACK 0x02 #define FU_EP963_SMBUS_ERROR_ARBITRATION 0x04 #define FU_EP963_SMBUS_ERROR_COMMAND 0x08 #define FU_EP963_SMBUS_ERROR_TIMEOUT 0x10 #define FU_EP963_SMBUS_ERROR_BUSY 0x20 const gchar * fu_ep963x_smbus_strerror(guint8 val); fwupd-1.7.5/plugins/ep963x/fu-ep963x-device.c000066400000000000000000000233711420024370600204430ustar00rootroot00000000000000/*# * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ep963x-common.h" #include "fu-ep963x-device.h" #include "fu-ep963x-firmware.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 */ g_usleep(100 * 1000); 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_usleep(100 * 1000); } /* 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(GPtrArray) 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); /* 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, 0x00, FU_EP963_TRANSFER_BLOCK_SIZE); for (guint i = 0; i < blocks->len; i++) { FuChunk *chk2 = g_ptr_array_index(blocks, i); guint8 buf[] = {i}; g_autoptr(GPtrArray) chunks = NULL; /* 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(fu_chunk_get_data(chk2), fu_chunk_get_data_sz(chk2), fu_chunk_get_address(chk2), 0x0, FU_EP963_TRANSFER_CHUNK_SIZE); for (guint j = 0; j < chunks->len; j++) { FuChunk *chk = g_ptr_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)chunks->len); } 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_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.7.5/plugins/ep963x/fu-ep963x-device.h000066400000000000000000000004471420024370600204470ustar00rootroot00000000000000/* * 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.7.5/plugins/ep963x/fu-ep963x-firmware.c000066400000000000000000000027411420024370600210160ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize len = 0x0; const guint8 *data = g_bytes_get_data(fw, &len); /* 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; } /* check signature */ if (memcmp(data + 16, "EP963", 5) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid EP963x binary file"); return FALSE; } /* success */ fu_firmware_set_bytes(firmware, fw); 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->parse = fu_ep963x_firmware_parse; } FuFirmware * fu_ep963x_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EP963X_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/ep963x/fu-ep963x-firmware.h000066400000000000000000000005341420024370600210210ustar00rootroot00000000000000/* * 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) FuFirmware * fu_ep963x_firmware_new(void); fwupd-1.7.5/plugins/ep963x/fu-plugin-ep963x.c000066400000000000000000000010301420024370600204660ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ep963x-device.h" #include "fu-ep963x-firmware.h" static void fu_plugin_ep963x_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_EP963X_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_EP963X_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_ep963x_init; } fwupd-1.7.5/plugins/ep963x/meson.build000066400000000000000000000012421420024370600175270ustar00rootroot00000000000000if get_option('plugin_ep963x') if not get_option('gudev') error('gudev is required for plugin_ep963x') endif cargs = ['-DG_LOG_DOMAIN="FuPluginEp963x"'] install_data([ 'ep963x.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_ep963x', fu_hash, sources : [ 'fu-ep963x-common.c', 'fu-ep963x-device.c', 'fu-ep963x-firmware.c', 'fu-plugin-ep963x.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/fastboot/000077500000000000000000000000001420024370600161515ustar00rootroot00000000000000fwupd-1.7.5/plugins/fastboot/README.md000066400000000000000000000031671420024370600174370ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_18D1&PID_4EE0` * `USB\VID_18D1` ## 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`. fwupd-1.7.5/plugins/fastboot/data/000077500000000000000000000000001420024370600170625ustar00rootroot00000000000000fwupd-1.7.5/plugins/fastboot/data/android/000077500000000000000000000000001420024370600205025ustar00rootroot00000000000000fwupd-1.7.5/plugins/fastboot/data/android/flashfile.xml000066400000000000000000000003701420024370600231610ustar00rootroot00000000000000 fwupd-1.7.5/plugins/fastboot/data/lsusb.txt000066400000000000000000000036661420024370600207660ustar00rootroot00000000000000Bus 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.7.5/plugins/fastboot/data/qfil/000077500000000000000000000000001420024370600200155ustar00rootroot00000000000000fwupd-1.7.5/plugins/fastboot/data/qfil/partition_nand.xml000066400000000000000000000011751420024370600235540ustar00rootroot00000000000000 0xAA7D1B9A 0x1F7D48BC 0x4 0:SBL 0x8 0x2 0 0xFF 0x01 0x00 0xFE sbl1.mbn fwupd-1.7.5/plugins/fastboot/fastboot.quirk000066400000000000000000000002631420024370600210500ustar00rootroot00000000000000# All fastboot devices [USB\CLASS_FF&SUBCLASS_42&PROT_03] Plugin = fastboot # Quectel EG25-G modem [USB\VID_18D1&PID_D00D] FastbootBlockSize = 16384 FastbootOperationDelay = 250 fwupd-1.7.5/plugins/fastboot/fu-fastboot-device.c000066400000000000000000000506771420024370600220220ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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 */ #define FASTBOOT_US_TO_MS 1000 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_common_string_append_kx(str, idt, "BlockSize", self->blocksz); fu_common_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 void fu_fastboot_buffer_dump(const gchar *title, const guint8 *buf, gsize sz) { if (g_getenv("FWUPD_FASTBOOT_VERBOSE") == NULL) return; g_print("%s (%" G_GSIZE_FORMAT "):\n", title, sz); for (gsize i = 0; i < sz; i++) { g_print("%02x[%c] ", buf[i], g_ascii_isprint(buf[i]) ? buf[i] : '?'); if (i > 0 && (i + 1) % 256 == 0) g_print("\n"); } g_print("\n"); } 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 */ buf2 = fu_memdup_safe(buf, buflen, error); if (buf2 == NULL) return FALSE; fu_fastboot_buffer_dump("writing", buf, buflen); 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 */ g_usleep(self->operation_delay * FASTBOOT_US_TO_MS); 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 */ g_usleep(self->operation_delay * FASTBOOT_US_TO_MS); 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_fastboot_buffer_dump("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); if (!fu_fastboot_device_writestr(device, tmp, error)) return FALSE; if (!fu_fastboot_device_read(device, str, NULL, 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(GPtrArray) 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, /* start addr */ 0x00, /* page_sz */ self->blocksz); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len); } 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_debug("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"}, {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_common_strtoull_full(value, &tmp, 0x40, 0x100000, error)) return FALSE; self->blocksz = tmp; return TRUE; } if (g_strcmp0(key, "FastbootOperationDelay") == 0) { if (!fu_common_strtoull_full(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.7.5/plugins/fastboot/fu-fastboot-device.h000066400000000000000000000004611420024370600220110ustar00rootroot00000000000000/* * 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.7.5/plugins/fastboot/fu-plugin-fastboot.c000066400000000000000000000006721420024370600220470ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-fastboot-device.h" static void fu_plugin_fastboot_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_FASTBOOT_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_fastboot_init; } fwupd-1.7.5/plugins/fastboot/meson.build000066400000000000000000000011631420024370600203140ustar00rootroot00000000000000if get_option('plugin_fastboot') if not get_option('gusb') error('gusb is required for plugin_fastboot') endif cargs = ['-DG_LOG_DOMAIN="FuPluginFastboot"'] install_data(['fastboot.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_fastboot', fu_hash, sources : [ 'fu-plugin-fastboot.c', 'fu-fastboot-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/flashrom/000077500000000000000000000000001420024370600161435ustar00rootroot00000000000000fwupd-1.7.5/plugins/flashrom/README.md000066400000000000000000000035151420024370600174260ustar00rootroot00000000000000# Flashrom ## Introduction This plugin uses `flashrom` to update the system firmware. ## 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. ## 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` ## Quirk Use This plugin uses the following plugin-specific quirks: ### FlashromProgrammer Used to specify the libflashrom programmer to be used. Since: 1.5.9 ## 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. fwupd-1.7.5/plugins/flashrom/example/000077500000000000000000000000001420024370600175765ustar00rootroot00000000000000fwupd-1.7.5/plugins/flashrom/example/build.sh000077500000000000000000000003241420024370600212330ustar00rootroot00000000000000#!/bin/sh appstream-util validate-relax com.Flashrom.Laptop.metainfo.xml tar -cf firmware.tar startup.sh random-tool gcab --create --nopath Flashrom-Laptop-1.2.3.cab firmware.tar com.Flashrom.Laptop.metainfo.xml fwupd-1.7.5/plugins/flashrom/example/com.Flashrom.Laptop.metainfo.xml000066400000000000000000000026271420024370600257160ustar00rootroot00000000000000 com.Flashrom.Laptop.firmware Flashrom Laptop Firmware

    System firmware for a Flashrom laptop

    The laptop can be updated using flashrom.

    a0ce5085-2dea-5086-ae72-45810a186ad0 http://www.bbc.co.uk/ CC0-1.0 Proprietary Flashrom

    This release updates a frobnicator to frob faster.

    startup.sh firmware.bin org.freedesktop.fwupd fwupd-1.7.5/plugins/flashrom/example/random-tool000077500000000000000000000000501420024370600217520ustar00rootroot00000000000000#!/bin/sh echo "hello from the sandbox" fwupd-1.7.5/plugins/flashrom/example/startup.sh000077500000000000000000000003021420024370600216320ustar00rootroot00000000000000#!/bin/sh # do something with the old firmware sha1sum /boot/flashrom-librem15v3.bin # run a random tool ./random-tool # this is the deliverable cp /boot/flashrom-librem15v3.bin firmware.bin fwupd-1.7.5/plugins/flashrom/flashrom.quirk000066400000000000000000000021711420024370600210340ustar00rootroot00000000000000# Purism [a0ce5085-2dea-5086-ae72-45810a186ad0] Plugin = flashrom # Libretrend [52b68c34-6b31-5ecc-8a5c-de37e666ccd5] Plugin = flashrom VersionFormat = quad # StarLite Mk II (HwId) [013b60e5-1023-5bee-8ae5-14cae21377b7] Plugin = flashrom # StarLite Mk III (HwId) [d5521faa-c50b-5d64-971d-8fd400030c51] Plugin = flashrom # StarLabTop Mk III (HwId) [013b60e5-1023-5bee-8ae5-14cae21377b7] Plugin = flashrom # StarLabTop Mk IV (HwId) [baf1d04e-fd16-5e6a-93cc-1c23d171f879] Plugin = flashrom # StarBookMk V (HwId) [85aba599-addd-5985-a2e8-eddb41c61ba3] Plugin = flashrom # StarLabTop Mk III (coreboot GUID) [d33219e2-b84c-53a8-a624-27af9752dba6] Branch = coreboot VersionFormat = plain Flags = reset-cmos # StarLabTop Mk IV (coreboot GUID) [0ee5867c-93f0-5fb4-adf1-9d686ea1183a] Branch = coreboot VersionFormat = plain Flags = reset-cmos # StarBook Mk V (coreboot GUID) [54c96fef-31e7-5011-a3ff-ea8e855d9acd] Branch = coreboot VersionFormat = plain Flags = reset-cmos # NovaCustom NV4x (HwId) [25b6ea34-8f52-598e-a27a-31e03014dbe3] Plugin = flashrom # NovaCustom NV4x (Dasharo GUID) [9a8c30e2-1b7e-5b28-9632-fc2fbf8cd0ba] Branch = dasharo fwupd-1.7.5/plugins/flashrom/fu-flashrom-cmos.c000066400000000000000000000025761420024370600215030ustar00rootroot00000000000000/* * 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.7.5/plugins/flashrom/fu-flashrom-cmos.h000066400000000000000000000007301420024370600214760ustar00rootroot00000000000000/* * 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.7.5/plugins/flashrom/fu-flashrom-device.c000066400000000000000000000141741420024370600217760ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-flashrom-device.h" typedef struct { gchar *programmer_name; gchar *programmer_args; gsize flash_size; struct flashrom_flashctx *flashctx; struct flashrom_programmer *flashprog; } FuFlashromDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuFlashromDevice, fu_flashrom_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_flashrom_device_get_instance_private(o)) void fu_flashrom_device_set_programmer_name(FuFlashromDevice *self, const gchar *name) { FuFlashromDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FLASHROM_DEVICE(self)); if (g_strcmp0(priv->programmer_name, name) == 0) return; g_free(priv->programmer_name); priv->programmer_name = g_strdup(name); } const gchar * fu_flashrom_device_get_programmer_name(FuFlashromDevice *self) { FuFlashromDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FLASHROM_DEVICE(self), NULL); return priv->programmer_name; } void fu_flashrom_device_set_programmer_args(FuFlashromDevice *self, const gchar *args) { FuFlashromDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FLASHROM_DEVICE(self)); if (g_strcmp0(priv->programmer_args, args) == 0) return; g_free(priv->programmer_args); priv->programmer_args = g_strdup(args); } gsize fu_flashrom_device_get_flash_size(FuFlashromDevice *self) { FuFlashromDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FLASHROM_DEVICE(self), 0); return priv->flash_size; } struct flashrom_flashctx * fu_flashrom_device_get_flashctx(FuFlashromDevice *self) { FuFlashromDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FLASHROM_DEVICE(self), NULL); return priv->flashctx; } static void fu_flashrom_device_init(FuFlashromDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "org.flashrom"); } static void fu_flashrom_device_finalize(GObject *object) { FuFlashromDevicePrivate *priv = GET_PRIVATE(FU_FLASHROM_DEVICE(object)); g_free(priv->programmer_name); g_free(priv->programmer_args); G_OBJECT_CLASS(fu_flashrom_device_parent_class)->finalize(object); } 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; fu_device_set_metadata_integer(device, "PciBcrAddr", tmp); return TRUE; } if (g_strcmp0(key, "FlashromProgrammer") == 0) { fu_flashrom_device_set_programmer_name(FU_FLASHROM_DEVICE(device), value); 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; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_flashrom_device_parent_class)->probe(device, error)) return FALSE; 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_ONLY_QUIRKS); } return TRUE; } static gboolean fu_flashrom_device_open(FuDevice *device, GError **error) { FuFlashromDevicePrivate *priv = GET_PRIVATE(FU_FLASHROM_DEVICE(device)); gint rc; if (priv->programmer_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "programmer not specified"); return FALSE; } if (flashrom_programmer_init(&priv->flashprog, priv->programmer_name, priv->programmer_args)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "programmer initialization failed"); return FALSE; } rc = flashrom_flash_probe(&priv->flashctx, priv->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; } priv->flash_size = flashrom_flash_getsize(priv->flashctx); if (priv->flash_size == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash size zero"); return FALSE; } return TRUE; } static gboolean fu_flashrom_device_close(FuDevice *device, GError **error) { FuFlashromDevicePrivate *priv = GET_PRIVATE(FU_FLASHROM_DEVICE(device)); flashrom_flash_release(priv->flashctx); flashrom_programmer_shutdown(priv->flashprog); 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_class_init(FuFlashromDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); 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; } fwupd-1.7.5/plugins/flashrom/fu-flashrom-device.h000066400000000000000000000014171420024370600217770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include struct _FuFlashromDeviceClass { FuUdevDeviceClass parent_class; }; #define FU_TYPE_FLASHROM_DEVICE (fu_flashrom_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFlashromDevice, fu_flashrom_device, FU, FLASHROM_DEVICE, FuUdevDevice) void fu_flashrom_device_set_programmer_name(FuFlashromDevice *self, const gchar *name); const gchar * fu_flashrom_device_get_programmer_name(FuFlashromDevice *self); void fu_flashrom_device_set_programmer_args(FuFlashromDevice *self, const gchar *args); gsize fu_flashrom_device_get_flash_size(FuFlashromDevice *self); struct flashrom_flashctx * fu_flashrom_device_get_flashctx(FuFlashromDevice *self); fwupd-1.7.5/plugins/flashrom/fu-flashrom-internal-device.c000066400000000000000000000142621420024370600236060ustar00rootroot00000000000000/* * 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" #include "fu-flashrom-internal-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) struct _FuFlashromInternalDevice { FuFlashromDevice parent_instance; }; G_DEFINE_TYPE(FuFlashromInternalDevice, fu_flashrom_internal_device, FU_TYPE_FLASHROM_DEVICE) static void fu_flashrom_internal_device_init(FuFlashromInternalDevice *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_instance_id(FU_DEVICE(self), "main-system-firmware"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER); fu_device_set_physical_id(FU_DEVICE(self), "flashrom"); fu_device_set_logical_id(FU_DEVICE(self), "bios"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_register_private_flag(FU_DEVICE(self), FU_FLASHROM_DEVICE_FLAG_RESET_CMOS, "reset-cmos"); } static gboolean fu_flashrom_internal_device_prepare(FuDevice *device, FwupdInstallFlags flags, GError **error) { g_autofree gchar *firmware_orig = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *basename = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* if the original firmware doesn't exist, grab it now */ basename = g_strdup_printf("flashrom-%s.bin", fu_device_get_id(device)); localstatedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); firmware_orig = g_build_filename(localstatedir, "builder", basename, NULL); if (!fu_common_mkdir_parent(firmware_orig, error)) return FALSE; if (!g_file_test(firmware_orig, G_FILE_TEST_EXISTS)) { FuFlashromDevice *parent = FU_FLASHROM_DEVICE(device); struct flashrom_flashctx *flashctx = fu_flashrom_device_get_flashctx(parent); gsize flash_size = fu_flashrom_device_get_flash_size(parent); struct flashrom_layout *layout; g_autofree guint8 *newcontents = g_malloc0(flash_size); g_autoptr(GBytes) buf = NULL; if (flashrom_layout_read_from_ifd(&layout, flashctx, NULL, 0)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read layout from Intel ICH descriptor"); return FALSE; } /* include bios region for safety reasons */ if (flashrom_layout_include_region(layout, "bios")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid region name"); return FALSE; } /* read region */ flashrom_layout_set(flashctx, layout); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); if (flashrom_image_read(flashctx, newcontents, flash_size)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to back up original firmware"); return FALSE; } buf = g_bytes_new_static(newcontents, flash_size); if (!fu_common_set_contents_bytes(firmware_orig, buf, error)) return FALSE; } return TRUE; } static gboolean fu_flashrom_internal_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuFlashromDevice *parent = FU_FLASHROM_DEVICE(device); struct flashrom_flashctx *flashctx = fu_flashrom_device_get_flashctx(parent); gsize flash_size = fu_flashrom_device_get_flash_size(parent); struct flashrom_layout *layout; gsize sz = 0; gint rc; const guint8 *buf; g_autoptr(GBytes) blob_fw = fu_firmware_get_bytes(firmware, error); if (blob_fw == 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_WRITE, 90); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10); buf = g_bytes_get_data(blob_fw, &sz); if (flashrom_layout_read_from_ifd(&layout, flashctx, NULL, 0)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read layout from Intel ICH descriptor"); return FALSE; } /* include bios region for safety reasons */ if (flashrom_layout_include_region(layout, "bios")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid region name"); return FALSE; } /* write region */ flashrom_layout_set(flashctx, layout); if (sz != flash_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid image size 0x%x, expected 0x%x", (guint)sz, (guint)flash_size); return FALSE; } rc = flashrom_image_write(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(flashctx, (void *)buf, sz)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image verify failed"); return FALSE; } fu_progress_step_done(progress); flashrom_layout_release(layout); /* 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_internal_device_class_init(FuFlashromInternalDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->prepare = fu_flashrom_internal_device_prepare; klass_device->write_firmware = fu_flashrom_internal_device_write_firmware; } FuDevice * fu_flashrom_internal_device_new(FuContext *ctx) { return FU_DEVICE(g_object_new(FU_TYPE_FLASHROM_INTERNAL_DEVICE, "context", ctx, NULL)); } fwupd-1.7.5/plugins/flashrom/fu-flashrom-internal-device.h000066400000000000000000000007051420024370600236100ustar00rootroot00000000000000/* * Copyright (C) 2021 Daniel Campello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-flashrom-device.h" #define FU_TYPE_FLASHROM_INTERNAL_DEVICE (fu_flashrom_internal_device_get_type()) G_DECLARE_FINAL_TYPE(FuFlashromInternalDevice, fu_flashrom_internal_device, FU, FLASHROM_INTERNAL_DEVICE, FuFlashromDevice) FuDevice * fu_flashrom_internal_device_new(FuContext *ctx); fwupd-1.7.5/plugins/flashrom/fu-plugin-flashrom.c000066400000000000000000000134541420024370600220350ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2017 Richard Hughes * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-flashrom-internal-device.h" #define SELFCHECK_TRUE 1 static void fu_plugin_flashrom_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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_context_add_quirk_key(ctx, "FlashromProgrammer"); } static int fu_plugin_flashrom_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_common_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_debug("%s", str); break; case FLASHROM_MSG_DEBUG: case FLASHROM_MSG_DEBUG2: if (g_getenv("FWUPD_FLASHROM_VERBOSE") != NULL) g_debug("%s", str); break; case FLASHROM_MSG_SPEW: break; default: break; } return 0; } static void fu_plugin_flashrom_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 void fu_plugin_flashrom_device_set_bios_info(FuPlugin *plugin, FuDevice *device) { FuContext *ctx = fu_plugin_get_context(plugin); const guint8 *buf; gsize bufsz; guint32 bios_char = 0x0; guint8 bios_sz = 0x0; g_autoptr(GBytes) bios_table = NULL; /* get SMBIOS info */ bios_table = fu_context_get_smbios_data(ctx, FU_SMBIOS_STRUCTURE_TYPE_BIOS); if (bios_table == NULL) return; /* ROM size */ buf = g_bytes_get_data(bios_table, &bufsz); if (fu_common_read_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_common_read_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"); } } } static void fu_plugin_flashrom_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 gboolean fu_plugin_flashrom_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *dmi_vendor; g_autoptr(FuDevice) device = fu_flashrom_internal_device_new(ctx); fu_device_set_context(device, ctx); fu_device_set_name(device, fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_NAME)); fu_device_set_vendor(device, fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER)); /* 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_plugin_flashrom_device_set_version(plugin, device); fu_plugin_flashrom_device_set_hwids(plugin, device); fu_plugin_flashrom_device_set_bios_info(plugin, device); fu_flashrom_device_set_programmer_name(FU_FLASHROM_DEVICE(device), "internal"); if (!fu_device_setup(device, error)) return FALSE; /* success */ fu_plugin_device_add(plugin, device); return TRUE; } static gboolean fu_plugin_flashrom_startup(FuPlugin *plugin, GError **error) { 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_plugin_flashrom_debug_cb); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_flashrom_init; vfuncs->startup = fu_plugin_flashrom_startup; vfuncs->coldplug = fu_plugin_flashrom_coldplug; } fwupd-1.7.5/plugins/flashrom/meson.build000066400000000000000000000012711420024370600203060ustar00rootroot00000000000000if get_option('plugin_flashrom') cargs = ['-DG_LOG_DOMAIN="FuPluginFlashrom"'] install_data(['flashrom.quirk'], install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d') ) shared_module('fu_plugin_flashrom', fu_hash, sources : [ 'fu-flashrom-device.c', 'fu-flashrom-internal-device.c', 'fu-plugin-flashrom.c', 'fu-flashrom-cmos.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, libflashrom, ], ) endif fwupd-1.7.5/plugins/fresco-pd/000077500000000000000000000000001420024370600162125ustar00rootroot00000000000000fwupd-1.7.5/plugins/fresco-pd/README.md000066400000000000000000000015711420024370600174750ustar00rootroot00000000000000# Fresco PD ## Introduction This plugin is used to update Power Devlivery devices by Fresco. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecifed 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&REV_0001` * `USB\VID_1D5C&PID_7102` * `USB\VID_1D5C` 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`. fwupd-1.7.5/plugins/fresco-pd/fresco-pd.quirk000066400000000000000000000001511420024370600211460ustar00rootroot00000000000000# FL7102 [USB\VID_1D5C&PID_7102] Plugin = fresco_pd # FL7112 [USB\VID_1D5C&PID_7112] Plugin = fresco_pd fwupd-1.7.5/plugins/fresco-pd/fu-fresco-pd-common.c000066400000000000000000000006671420024370600221470ustar00rootroot00000000000000/* * 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.7.5/plugins/fresco-pd/fu-fresco-pd-common.h000066400000000000000000000003631420024370600221450ustar00rootroot00000000000000/* * 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.7.5/plugins/fresco-pd/fu-fresco-pd-device.c000066400000000000000000000325261420024370600221150ustar00rootroot00000000000000/* * 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_common_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 */ if (g_getenv("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_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 */ if (g_getenv("FWUPD_FRESCO_PD_VERBOSE") != NULL) fu_common_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); FuUsbDevice *usb_device = FU_USB_DEVICE(device); guint8 ver[4] = {0x0}; g_autofree gchar *instance_id = NULL; 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]; instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%02X", fu_usb_device_get_vid(usb_device), fu_usb_device_get_pid(usb_device), self->customer_id); fu_device_add_instance_id(device, instance_id); /* success */ return TRUE; } 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); /* 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 (guint16 i = begin_addr + 3; i < 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), "audio-card"); 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.7.5/plugins/fresco-pd/fu-fresco-pd-device.h000066400000000000000000000005711420024370600221150ustar00rootroot00000000000000/* * 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) struct _FuFrescoPdDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.7.5/plugins/fresco-pd/fu-fresco-pd-firmware.c000066400000000000000000000037321420024370600224670ustar00rootroot00000000000000/* * 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, guint64 addr_start, guint64 addr_end, 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); fu_firmware_set_bytes(firmware, fw); return TRUE; } static void fu_fresco_pd_firmware_init(FuFrescoPdFirmware *self) { } 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.7.5/plugins/fresco-pd/fu-fresco-pd-firmware.h000066400000000000000000000007301420024370600224670ustar00rootroot00000000000000/* * 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.7.5/plugins/fresco-pd/fu-plugin-fresco-pd.c000066400000000000000000000010521420024370600221420ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" static void fu_plugin_fresco_pd_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_FRESCO_PD_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_FRESCO_PD_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_fresco_pd_init; } fwupd-1.7.5/plugins/fresco-pd/lsusb.txt000066400000000000000000000047141420024370600201110ustar00rootroot00000000000000Bus 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 iAddtionalInfoURL 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.7.5/plugins/fresco-pd/meson.build000066400000000000000000000011261420024370600203540ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginFrescoPd"'] install_data(['fresco-pd.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_fresco_pd', fu_hash, sources : [ 'fu-plugin-fresco-pd.c', 'fu-fresco-pd-common.c', 'fu-fresco-pd-device.c', 'fu-fresco-pd-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/goodix-moc/000077500000000000000000000000001420024370600163755ustar00rootroot00000000000000fwupd-1.7.5/plugins/goodix-moc/README.md000066400000000000000000000015001420024370600176500ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_27C6&PID_6001` * `USB\VID_27C6` ## 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`. fwupd-1.7.5/plugins/goodix-moc/fu-goodixmoc-common.h000066400000000000000000000023321420024370600224340ustar00rootroot00000000000000/* * 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.7.5/plugins/goodix-moc/fu-goodixmoc-device.c000066400000000000000000000305621420024370600224040ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_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_common_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; } if (g_getenv("FWUPD_GOODIXFP_VERBOSE") != NULL) { fu_common_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); 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; if (g_getenv("FWUPD_GOODIXFP_VERBOSE") != NULL) { fu_common_dump_full(G_LOG_DOMAIN, "REPLY", reply->data, actual_len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } /* parse package header */ if (!fu_common_read_uint8_safe(reply->data, reply->len, 0x0, &header_cmd0, error)) return FALSE; if (!fu_common_read_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_common_crc32(reply->data, offset); if (!fu_common_read_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_common_read_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_common_read_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(GPtrArray) 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); /* 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, 0x00, /* page_sz */ 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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 == chunks->len - 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)chunks->len); } 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_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.7.5/plugins/goodix-moc/fu-goodixmoc-device.h000066400000000000000000000005521420024370600224050ustar00rootroot00000000000000/* * 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.7.5/plugins/goodix-moc/fu-plugin-goodixmoc.c000066400000000000000000000007621420024370600224420ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-goodixmoc-device.h" static void fu_plugin_goodixmoc_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_GOODIXMOC_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_goodixmoc_init; } fwupd-1.7.5/plugins/goodix-moc/goodixmoc.quirk000066400000000000000000000004361420024370600214450ustar00rootroot00000000000000# Goodix Fingerprint sensor [USB\VID_27C6&PID_60A2] Plugin = goodixmoc [USB\VID_27C6&PID_6384] Plugin = goodixmoc [USB\VID_27C6&PID_639C] Plugin = goodixmoc [USB\VID_27C6&PID_63AC] Plugin = goodixmoc [USB\VID_27C6&PID_6594] Plugin = goodixmoc [USB\VID_27C6&PID_6496] Plugin = goodixmoc fwupd-1.7.5/plugins/goodix-moc/meson.build000066400000000000000000000010421420024370600205340ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginGoodixMoc"'] install_data([ 'goodixmoc.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_goodixmoc', fu_hash, sources : [ 'fu-goodixmoc-device.c', 'fu-plugin-goodixmoc.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/hailuck/000077500000000000000000000000001420024370600157505ustar00rootroot00000000000000fwupd-1.7.5/plugins/hailuck/README.md000066400000000000000000000022041420024370600172250ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/hailuck/data/000077500000000000000000000000001420024370600166615ustar00rootroot00000000000000fwupd-1.7.5/plugins/hailuck/data/lspci-bl.txt000066400000000000000000000030331420024370600211260ustar00rootroot00000000000000Bus 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.7.5/plugins/hailuck/data/lspci.txt000066400000000000000000000057371420024370600205500ustar00rootroot00000000000000Bus 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.7.5/plugins/hailuck/fu-hailuck-bl-device.c000066400000000000000000000213761420024370600220050ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-hailuck-bl-device.h" #include "fu-hailuck-common.h" #include "fu-hailuck-kbd-firmware.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) { g_autofree gchar *devid = NULL; /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_hailuck_bl_device_parent_class)->probe(device, error)) return FALSE; /* add extra keyboard-specific GUID */ devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&MODE_KBD", fu_usb_device_get_vid(FU_USB_DEVICE(device)), fu_usb_device_get_pid(FU_USB_DEVICE(device))); fu_device_add_instance_id(device, devid); /* success */ return TRUE; } 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_common_write_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 */ g_usleep(10000); 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); chunks = fu_chunk_array_mutable_new(fwbuf->data, fwbuf->len, 0x0, 0x0, 2048); 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_set_percentage_full(progress, i + 1, chunks->len); } /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&fwbuf)); } 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_progress_sleep(progress, 2000); 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_common_write_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 */ g_usleep(10000); 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); FuChunk *chk0; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_new = NULL; g_autoptr(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); /* block 0 */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9); /* 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, 0x00, 2048); /* intentionally corrupt first chunk so that CRC fails */ chk0 = g_ptr_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 < chunks->len; i++) { FuChunk *chk = g_ptr_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, chunks->len); } 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_common_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_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.7.5/plugins/hailuck/fu-hailuck-bl-device.h000066400000000000000000000004721420024370600220040ustar00rootroot00000000000000/* * 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.7.5/plugins/hailuck/fu-hailuck-common.c000066400000000000000000000025101420024370600214300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-common.h" const gchar * fu_hailuck_cmd_to_string(guint8 cmd) { if (cmd == FU_HAILUCK_CMD_ERASE) return "erase"; if (cmd == FU_HAILUCK_CMD_READ_BLOCK_START) return "read-block-start"; if (cmd == FU_HAILUCK_CMD_WRITE_BLOCK_START) return "write-block-start"; if (cmd == FU_HAILUCK_CMD_READ_BLOCK) return "read-block"; if (cmd == FU_HAILUCK_CMD_WRITE_BLOCK) return "write-block"; if (cmd == FU_HAILUCK_CMD_GET_STATUS) return "get-status"; if (cmd == FU_HAILUCK_CMD_DETACH) return "detach"; if (cmd == FU_HAILUCK_CMD_ATTACH) return "attach"; if (cmd == FU_HAILUCK_CMD_WRITE_TP) return "write-tp"; if (cmd == FU_HAILUCK_CMD_I2C_CHECK_CHECKSUM) return "i2c-check-checksum"; if (cmd == FU_HAILUCK_CMD_I2C_ENTER_BL) return "i2c-enter-bl"; if (cmd == FU_HAILUCK_CMD_I2C_ERASE) return "i2c-erase"; if (cmd == FU_HAILUCK_CMD_I2C_PROGRAM) return "i2c-program"; if (cmd == FU_HAILUCK_CMD_I2C_VERIFY_BLOCK) return "i2c-verify-block"; if (cmd == FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM) return "i2c-verify-checksum"; if (cmd == FU_HAILUCK_CMD_I2C_PROGRAMPASS) return "i2c-programpass"; if (cmd == FU_HAILUCK_CMD_I2C_END_PROGRAM) return "i2c-end-program"; return NULL; } fwupd-1.7.5/plugins/hailuck/fu-hailuck-common.h000066400000000000000000000020561420024370600214420ustar00rootroot00000000000000/* * 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 #define FU_HAILUCK_CMD_ERASE 0x45 #define FU_HAILUCK_CMD_READ_BLOCK_START 0x52 #define FU_HAILUCK_CMD_ATTACH 0x55 /* guessed */ #define FU_HAILUCK_CMD_WRITE_BLOCK_START 0x57 #define FU_HAILUCK_CMD_READ_BLOCK 0x72 #define FU_HAILUCK_CMD_DETACH 0x75 /* guessed */ #define FU_HAILUCK_CMD_WRITE_BLOCK 0x77 #define FU_HAILUCK_CMD_GET_STATUS 0xA1 #define FU_HAILUCK_CMD_WRITE_TP 0xD0 /* guessed */ #define FU_HAILUCK_CMD_I2C_CHECK_CHECKSUM 0xF0 #define FU_HAILUCK_CMD_I2C_ENTER_BL 0xF1 #define FU_HAILUCK_CMD_I2C_ERASE 0xF2 #define FU_HAILUCK_CMD_I2C_PROGRAM 0xF3 #define FU_HAILUCK_CMD_I2C_VERIFY_BLOCK 0xF4 #define FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM 0xF5 #define FU_HAILUCK_CMD_I2C_PROGRAMPASS 0xF6 #define FU_HAILUCK_CMD_I2C_END_PROGRAM 0xF7 const gchar * fu_hailuck_cmd_to_string(guint8 cmd); fwupd-1.7.5/plugins/hailuck/fu-hailuck-kbd-device.c000066400000000000000000000062051420024370600221420ustar00rootroot00000000000000/* * 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-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_autofree gchar *devid = NULL; g_autoptr(FuHailuckTpDevice) tp_device = fu_hailuck_tp_device_new(FU_DEVICE(device)); /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_hailuck_kbd_device_parent_class)->probe(device, error)) return FALSE; /* add extra keyboard-specific GUID */ devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&MODE_KBD", fu_usb_device_get_vid(FU_USB_DEVICE(device)), fu_usb_device_get_pid(FU_USB_DEVICE(device))); fu_device_add_instance_id(device, devid); /* 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_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.7.5/plugins/hailuck/fu-hailuck-kbd-device.h000066400000000000000000000004771420024370600221540ustar00rootroot00000000000000/* * 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.7.5/plugins/hailuck/fu-hailuck-kbd-firmware.c000066400000000000000000000051621420024370600225200ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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, guint64 addr_start, guint64 addr_end, 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); } 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_byte_array_free_to_bytes(g_steal_pointer(&buf)); fu_firmware_set_bytes(firmware, fw_new); return TRUE; } static void fu_hailuck_kbd_firmware_init(FuHailuckKbdFirmware *self) { } 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; } FuFirmware * fu_hailuck_kbd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_HAILUCK_KBD_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/hailuck/fu-hailuck-kbd-firmware.h000066400000000000000000000006311420024370600225210ustar00rootroot00000000000000/* * 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) FuFirmware * fu_hailuck_kbd_firmware_new(void); fwupd-1.7.5/plugins/hailuck/fu-hailuck-tp-device.c000066400000000000000000000174501420024370600220310ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-hailuck-common.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) { FuDevice *parent = fu_device_get_parent(device); g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; devid1 = g_strdup_printf("USB\\VID_%04X&PID_%04X", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent))); fu_device_add_instance_id(device, devid1); devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X&MODE_TP", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent))); fu_device_add_instance_id(device, devid2); return TRUE; } 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* end-program */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 3); 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; } g_usleep(10000); fu_progress_step_done(progress); /* write */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, block_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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; } g_usleep(150 * 1000); /* 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, chunks->len); } g_usleep(50 * 1000); 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; } g_usleep(50 * 1000); 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; } g_usleep(50 * 1000); 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_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 *device) { FuHailuckTpDevice *self; self = g_object_new(FU_TYPE_HAILUCK_TP_DEVICE, "parent", device, NULL); return FU_HAILUCK_TP_DEVICE(self); } fwupd-1.7.5/plugins/hailuck/fu-hailuck-tp-device.h000066400000000000000000000005701420024370600220310ustar00rootroot00000000000000/* * 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.7.5/plugins/hailuck/fu-plugin-hailuck.c000066400000000000000000000012201420024370600214330ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-hailuck-bl-device.h" #include "fu-hailuck-kbd-device.h" #include "fu-hailuck-kbd-firmware.h" static void fu_plugin_hailuck_init(FuPlugin *plugin) { 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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_hailuck_init; } fwupd-1.7.5/plugins/hailuck/hailuck.quirk000066400000000000000000000007611420024370600204510ustar00rootroot00000000000000# 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.7.5/plugins/hailuck/meson.build000066400000000000000000000012371420024370600201150ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginNovatek"'] install_data([ 'hailuck.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_hailuck', fu_hash, sources : [ 'fu-hailuck-common.c', 'fu-hailuck-bl-device.c', 'fu-hailuck-kbd-device.c', 'fu-hailuck-kbd-firmware.c', # fuzzing 'fu-hailuck-tp-device.c', 'fu-plugin-hailuck.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/intel-spi/000077500000000000000000000000001420024370600162345ustar00rootroot00000000000000fwupd-1.7.5/plugins/intel-spi/README.md000066400000000000000000000004521420024370600175140ustar00rootroot00000000000000# 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. fwupd-1.7.5/plugins/intel-spi/fu-ifd-device.c000066400000000000000000000101121420024370600210020ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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); g_autofree gchar *instance_id = NULL; g_autofree gchar *region_str_up = NULL; 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); region_str_up = g_ascii_strup(region_str, -1); instance_id = g_strdup_printf("IFD\\%s", region_str_up); fu_device_add_instance_id(FU_DEVICE(self), instance_id); } 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_common_string_append_kv(str, idt, "Region", fu_ifd_region_to_string(priv->region)); fu_common_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_common_string_append_kv(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_NONE, 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(FuIfdRegion region, guint32 freg) { FuIfdDevice *self = FU_IFD_DEVICE(g_object_new(FU_TYPE_IFD_DEVICE, NULL)); fu_ifd_device_set_region(self, region); fu_ifd_device_set_freg(self, freg); return FU_DEVICE(self); } fwupd-1.7.5/plugins/intel-spi/fu-ifd-device.h000066400000000000000000000007601420024370600210170ustar00rootroot00000000000000/* * 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(FuIfdRegion region, guint32 freg); void fu_ifd_device_set_access(FuIfdDevice *self, FuIfdRegion region, FuIfdAccess access); fwupd-1.7.5/plugins/intel-spi/fu-intel-spi-common.c000066400000000000000000000045461420024370600222130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include "fu-intel-spi-common.h" FuIntelSpiKind fu_intel_spi_kind_from_string(const gchar *kind) { if (g_strcmp0(kind, "ich9") == 0) return FU_INTEL_SPI_KIND_ICH9; if (g_strcmp0(kind, "pch100") == 0) return FU_INTEL_SPI_KIND_PCH100; if (g_strcmp0(kind, "apl") == 0) return FU_INTEL_SPI_KIND_APL; if (g_strcmp0(kind, "c620") == 0) return FU_INTEL_SPI_KIND_C620; if (g_strcmp0(kind, "ich0") == 0) return FU_INTEL_SPI_KIND_ICH0; if (g_strcmp0(kind, "ich2345") == 0) return FU_INTEL_SPI_KIND_ICH2345; if (g_strcmp0(kind, "ich6") == 0) return FU_INTEL_SPI_KIND_ICH6; if (g_strcmp0(kind, "pch300") == 0) return FU_INTEL_SPI_KIND_PCH300; if (g_strcmp0(kind, "pch400") == 0) return FU_INTEL_SPI_KIND_PCH400; if (g_strcmp0(kind, "poulsbo") == 0) return FU_INTEL_SPI_KIND_POULSBO; return FU_INTEL_SPI_KIND_UNKNOWN; } const gchar * fu_intel_spi_kind_to_string(FuIntelSpiKind kind) { if (kind == FU_INTEL_SPI_KIND_ICH9) return "ich9"; if (kind == FU_INTEL_SPI_KIND_PCH100) return "pch100"; if (kind == FU_INTEL_SPI_KIND_APL) return "apl"; if (kind == FU_INTEL_SPI_KIND_C620) return "c620"; if (kind == FU_INTEL_SPI_KIND_ICH0) return "ich0"; if (kind == FU_INTEL_SPI_KIND_ICH2345) return "ich2345"; if (kind == FU_INTEL_SPI_KIND_ICH6) return "ich6"; if (kind == FU_INTEL_SPI_KIND_PCH300) return "pch300"; if (kind == FU_INTEL_SPI_KIND_PCH400) return "pch400"; if (kind == FU_INTEL_SPI_KIND_POULSBO) return "poulsbo"; return NULL; } 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.7.5/plugins/intel-spi/fu-intel-spi-common.h000066400000000000000000000037131420024370600222130ustar00rootroot00000000000000/* * 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) typedef enum { FU_INTEL_SPI_KIND_UNKNOWN, FU_INTEL_SPI_KIND_APL, FU_INTEL_SPI_KIND_C620, FU_INTEL_SPI_KIND_ICH0, FU_INTEL_SPI_KIND_ICH2345, FU_INTEL_SPI_KIND_ICH6, FU_INTEL_SPI_KIND_ICH9, FU_INTEL_SPI_KIND_PCH100, FU_INTEL_SPI_KIND_PCH300, FU_INTEL_SPI_KIND_PCH400, FU_INTEL_SPI_KIND_POULSBO, FU_INTEL_SPI_KIND_LAST } FuIntelSpiKind; FuIntelSpiKind fu_intel_spi_kind_from_string(const gchar *kind); const gchar * fu_intel_spi_kind_to_string(FuIntelSpiKind kind); 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.7.5/plugins/intel-spi/fu-intel-spi-device.c000066400000000000000000000370501420024370600221560ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: GPL-2+ */ #include "config.h" #include #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-pci-device.h" struct _FuIntelSpiDevice { FuDevice parent_instance; FuIntelSpiKind kind; gchar *spibar_proxy; 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 /** * 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_common_string_append_kv(str, idt, "Kind", fu_intel_spi_kind_to_string(self->kind)); fu_common_string_append_kx(str, idt, "SPIBAR", self->phys_spibar); fu_common_string_append_kx(str, idt, "HSFS", self->hsfs); fu_common_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_common_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_common_string_append_kx(str, idt, title, self->flash_master[i]); } fu_common_string_append_kx(str, idt, "FLVALSIG", self->flvalsig); fu_common_string_append_kx(str, idt, "FLMAP0", self->descriptor_map0); fu_common_string_append_kx(str, idt, "FLMAP1", self->descriptor_map1); fu_common_string_append_kx(str, idt, "FLMAP2", self->descriptor_map2); fu_common_string_append_kx(str, idt, "FLCOMP", self->components_rcd); fu_common_string_append_kx(str, idt, "FLILL", self->illegal_jedec); fu_common_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_common_string_append_kv(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 = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); 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); return; } /* FLOCKDN is unset */ if ((self->hsfs >> 15 & 0b1) == 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } 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; } /* use a hidden PCI device to get the RCBA */ if (self->spibar_proxy != NULL) { g_autoptr(FuDevice) pcidev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* get SPIBAR from a hidden (VID set to 0xFFFF) PCI device */ pcidev = fu_pci_device_new(self->spibar_proxy, error); if (pcidev == NULL) return FALSE; locker = fu_device_locker_new(pcidev, error); if (locker == NULL) return FALSE; self->phys_spibar = fu_pci_device_read_config(FU_PCI_DEVICE(pcidev), PCI_BASE_ADDRESS_0); if (self->phys_spibar == 0 || self->phys_spibar == G_MAXUINT32) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPIBAR not valid: 0x%x", self->phys_spibar); 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; guint16 reg_pr0 = fu_device_has_private_flag(device, FU_INTEL_SPI_DEVICE_FLAG_ICH) ? ICH9_REG_PR0 : PCH100_REG_FPR0; /* dump everything */ if (g_getenv("FWUPD_INTEL_SPI_VERBOSE") != NULL) { 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 += 1 << (19 + comp1_density); comp2_density = (self->components_rcd & 0xf0) >> 4; if (comp2_density != 0xf) total_size += 1 << (19 + comp2_density); fu_device_set_firmware_size(device, total_size); /* 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(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); } 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_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->phys_spibar = tmp; return TRUE; } if (g_strcmp0(key, "IntelSpiKind") == 0) { g_autofree gchar *instance_id = NULL; g_autofree gchar *kind_up = NULL; /* 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 */ kind_up = g_ascii_strup(value, -1); instance_id = g_strdup_printf("INTEL_SPI_CHIPSET\\%s", kind_up); fu_device_add_instance_id(device, instance_id); return TRUE; } if (g_strcmp0(key, "IntelSpiBarProxy") == 0) { self->spibar_proxy = g_strdup(value); return TRUE; } 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.7.5/plugins/intel-spi/fu-intel-spi-device.h000066400000000000000000000007321420024370600221600ustar00rootroot00000000000000/* * 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.7.5/plugins/intel-spi/fu-pci-device.c000066400000000000000000000100221420024370600210130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-pci-device.h" typedef struct { guint32 bus; guint32 dev; guint32 fun; } FuPciDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuPciDevice, fu_pci_device, FU_TYPE_DEVICE) #define PCI_CONFIG_ADDRESS 0x0CF8 #define PCI_CONFIG_DATA 0x0CFC #define GET_PRIVATE(o) (fu_pci_device_get_instance_private(o)) guint32 fu_pci_device_read_config(FuPciDevice *self, guint32 addr) { FuPciDevicePrivate *priv = GET_PRIVATE(self); guint32 val = 0x80000000; /* we have to do this horrible port access as the PCI device is not * visible to even the kernel as the vendor ID is set as 0xFFFF */ val |= priv->bus << 16; val |= priv->dev << 11; val |= priv->fun << 8; val |= addr; /* we do this multiple times until we get the same result for every * request as the port is shared between the kernel and all processes */ for (guint cnt = 0; cnt < 0xff; cnt++) { guint32 results[0x20] = {0x0}; gboolean consistent = TRUE; /* fill up array */ for (guint i = 0; i < G_N_ELEMENTS(results); i++) { outl(val, PCI_CONFIG_ADDRESS); results[i] = inl(PCI_CONFIG_DATA); } /* check they are all the same */ for (guint i = 0; i < G_N_ELEMENTS(results); i++) { if (results[0] != results[i]) { consistent = FALSE; break; } } /* success */ if (consistent) return results[0]; } /* failed */ return G_MAXUINT32; } static gboolean fu_pci_device_open(FuDevice *device, GError **error) { /* this will fail if userspace is locked down */ if (ioperm(PCI_CONFIG_ADDRESS, 64, 1) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open port: %s", strerror(errno)); return FALSE; } /* success */ return TRUE; } static gboolean fu_pci_device_close(FuDevice *device, GError **error) { /* this might fail if userspace is locked down */ if (ioperm(PCI_CONFIG_ADDRESS, 64, 0) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open port: %s", strerror(errno)); return FALSE; } /* success */ return TRUE; } static void fu_pci_device_to_string(FuDevice *device, guint idt, GString *str) { FuPciDevice *self = FU_PCI_DEVICE(device); FuPciDevicePrivate *priv = GET_PRIVATE(self); fu_common_string_append_kx(str, idt, "Bus", priv->bus); fu_common_string_append_kx(str, idt, "Dev", priv->dev); fu_common_string_append_kx(str, idt, "Fun", priv->fun); } static gboolean fu_pci_device_parse_bdf(FuPciDevice *self, const gchar *bdf, GError **error) { FuPciDevicePrivate *priv = GET_PRIVATE(self); guint64 bus_tmp; guint64 dev_tmp; guint64 fun_tmp; g_auto(GStrv) split = g_strsplit_set(bdf, ":.", 0); /* parse the BDF */ if (g_strv_length(split) != 3) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s invalid, expected '00:1f.5'", bdf); return FALSE; } bus_tmp = g_ascii_strtoull(split[0], NULL, 16); dev_tmp = g_ascii_strtoull(split[1], NULL, 16); fun_tmp = g_ascii_strtoull(split[2], NULL, 16); if (bus_tmp > 0xff || dev_tmp > 0x1f || fun_tmp > 0x7) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s invalid, expected '00:1f.5'", bdf); return FALSE; } /* success */ priv->bus = bus_tmp; priv->dev = dev_tmp; priv->fun = fun_tmp; return TRUE; } static void fu_pci_device_init(FuPciDevice *self) { fu_device_set_physical_id(FU_DEVICE(self), "PCI"); } static void fu_pci_device_class_init(FuPciDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_pci_device_to_string; klass_device->open = fu_pci_device_open; klass_device->close = fu_pci_device_close; } FuDevice * fu_pci_device_new(const gchar *bdf, GError **error) { g_autoptr(FuPciDevice) self = FU_PCI_DEVICE(g_object_new(FU_TYPE_PCI_DEVICE, NULL)); if (!fu_pci_device_parse_bdf(self, bdf, error)) return NULL; return FU_DEVICE(g_steal_pointer(&self)); } fwupd-1.7.5/plugins/intel-spi/fu-pci-device.h000066400000000000000000000007321420024370600210270ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_PCI_DEVICE (fu_pci_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuPciDevice, fu_pci_device, FU, PCI_DEVICE, FuDevice) struct _FuPciDeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_pci_device_new(const gchar *bdf, GError **error); guint32 fu_pci_device_read_config(FuPciDevice *self, guint32 addr); fwupd-1.7.5/plugins/intel-spi/fu-plugin-intel-spi.c000066400000000000000000000020661420024370600222140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-intel-spi-device.h" static void fu_plugin_intel_spi_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_context_add_quirk_key(ctx, "IntelSpiKind"); fu_context_add_quirk_key(ctx, "IntelSpiBar"); fu_context_add_quirk_key(ctx, "IntelSpiBarProxy"); fu_context_add_quirk_key(ctx, "IntelSpiBiosCntl"); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_SPI_DEVICE); } static gboolean fu_plugin_intel_spi_startup(FuPlugin *plugin, GError **error) { if (fu_common_kernel_locked_down()) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported when kernel locked down"); return FALSE; } return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_intel_spi_init; vfuncs->startup = fu_plugin_intel_spi_startup; } fwupd-1.7.5/plugins/intel-spi/generate-quirk.py000077500000000000000000000052741420024370600215440ustar00rootroot00000000000000#!/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, spibar_proxy=None, flags=None): self.bios_cntl = bios_cntl self.spibar_proxy = spibar_proxy 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="pch", bios_cntl=0xDC, spibar_proxy="00:0d.2"), "c620": Chipset(flags="pch", bios_cntl=0xDC, spibar_proxy="00:1f.5"), "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, spibar_proxy="00:1f.5"), "pch300": Chipset(flags="pch", bios_cntl=0xDC, spibar_proxy="00:1f.5"), "pch400": Chipset(flags="pch", bios_cntl=0xDC, spibar_proxy="00:1f.5"), "poulsbo": Chipset(flags="ich", bios_cntl=0xD8), } devices = {"PCI\VEN_8086&DEV_A0A4": "pch100"} 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\\{}]\n".format(kind.upper())) if cs.spibar: out_f.write("IntelSpiBar = 0x{:x}\n".format(cs.spibar)) if cs.spibar_proxy: out_f.write("IntelSpiBarProxy = {}\n".format(cs.spibar_proxy)) 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.7.5/plugins/intel-spi/intel-spi.quirk000066400000000000000000000151271420024370600212230ustar00rootroot00000000000000[PCI\VEN_8086&DEV_A0A4] Plugin = intel_spi IntelSpiKind = pch100 [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_9D24] Plugin = intel_spi IntelSpiKind = pch200 [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_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_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 [INTEL_SPI_CHIPSET\APL] IntelSpiBarProxy = 00:0d.2 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\C620] IntelSpiBarProxy = 00:1f.5 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ICH0] IntelSpiBiosCntl = 0x4E Flags = ich [INTEL_SPI_CHIPSET\ICH2345] IntelSpiBiosCntl = 0x4E Flags = ich [INTEL_SPI_CHIPSET\ICH6] IntelSpiBiosCntl = 0xDC Flags = ich [INTEL_SPI_CHIPSET\PCH100] IntelSpiBarProxy = 00:1f.5 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\PCH200] IntelSpiBarProxy = 00:1f.5 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\PCH300] IntelSpiBarProxy = 00:1f.5 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\PCH400] IntelSpiBarProxy = 00:1f.5 IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\POULSBO] IntelSpiBiosCntl = 0xD8 Flags = ich fwupd-1.7.5/plugins/intel-spi/meson.build000066400000000000000000000011611420024370600203750ustar00rootroot00000000000000if get_option('plugin_intel_spi') cargs = ['-DG_LOG_DOMAIN="FuPluginIntelSpi"'] install_data(['intel-spi.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_intel_spi', fu_hash, sources : [ 'fu-ifd-device.c', 'fu-intel-spi-common.c', 'fu-intel-spi-device.c', 'fu-pci-device.c', 'fu-plugin-intel-spi.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/iommu/000077500000000000000000000000001420024370600154565ustar00rootroot00000000000000fwupd-1.7.5/plugins/iommu/README.md000066400000000000000000000002401420024370600167310ustar00rootroot00000000000000# Linux IOMMU ## Introduction This plugin checks if an IOMMU is available on the system. ## External Interface Access This plugin requires no extra access. fwupd-1.7.5/plugins/iommu/fu-plugin-iommu.c000066400000000000000000000033031420024370600206530ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { gboolean has_iommu; }; static void fu_plugin_iommu_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_udev_subsystem(plugin, "iommu"); } static gboolean fu_plugin_iommu_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data(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; priv->has_iommu = TRUE; return TRUE; } static void fu_plugin_iommu_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_IOMMU); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fu_security_attrs_append(attrs, attr); if (!data->has_iommu) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_iommu_init; vfuncs->backend_device_added = fu_plugin_iommu_backend_device_added; vfuncs->add_security_attrs = fu_plugin_iommu_add_security_attrs; } fwupd-1.7.5/plugins/iommu/iommu.quirk000066400000000000000000000001041420024370600176540ustar00rootroot00000000000000# match all devices with this udev subsystem [IOMMU] Plugin = iommu fwupd-1.7.5/plugins/iommu/meson.build000066400000000000000000000010311420024370600176130ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginIommu"'] install_data([ 'iommu.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_iommu', fu_hash, sources : [ 'fu-plugin-iommu.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, fwupd, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/jabra/000077500000000000000000000000001420024370600154075ustar00rootroot00000000000000fwupd-1.7.5/plugins/jabra/README.md000066400000000000000000000017001420024370600166640ustar00rootroot00000000000000# Jabra ## Introduction This plugin is used to detach the Jabra device 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`. fwupd-1.7.5/plugins/jabra/fu-jabra-device.c000066400000000000000000000114371420024370600205050ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_string_append_kv(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, 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(FuProgress) progress = fu_progress_new(G_STRLOC); 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.7.5/plugins/jabra/fu-jabra-device.h000066400000000000000000000004421420024370600205040ustar00rootroot00000000000000/* * 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.7.5/plugins/jabra/fu-plugin-jabra.c000066400000000000000000000033541420024370600205430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-jabra-device.h" static void fu_plugin_jabra_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_JABRA_DEVICE); fu_context_add_quirk_key(ctx, "JabraMagic"); } /* slightly weirdly, this takes us from appIDLE back into the actual * runtime mode where the device actually works */ static gboolean fu_plugin_jabra_cleanup(FuPlugin *plugin, FuDevice *device, FwupdInstallFlags flags, GError **error) { GUsbDevice *usb_device; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); 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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_jabra_init; vfuncs->cleanup = fu_plugin_jabra_cleanup; } fwupd-1.7.5/plugins/jabra/jabra.quirk000066400000000000000000000007371420024370600175520ustar00rootroot00000000000000# 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.7.5/plugins/jabra/meson.build000066400000000000000000000010071420024370600175470ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginJabra"'] install_data(['jabra.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_jabra', fu_hash, sources : [ 'fu-plugin-jabra.c', 'fu-jabra-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/lenovo-thinklmi/000077500000000000000000000000001420024370600174475ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/README.md000066400000000000000000000004301420024370600207230ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/lenovo-thinklmi/fu-plugin-lenovo-thinklmi.c000066400000000000000000000060221420024370600246360ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include static gboolean fu_plugin_lenovo_thinklmi_startup(FuPlugin *plugin, GError **error) { g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *thinklmidir = NULL; /* already exists */ sysfsfwdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW_ATTRIB); thinklmidir = g_build_filename(sysfsfwdir, "thinklmi", NULL); if (!g_file_test(thinklmidir, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "thinklmi not available"); return FALSE; } return TRUE; } static gboolean fu_plugin_lenovo_firmware_pending_change(gboolean *result, GError **error) { g_autofree gchar *buf = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *pending = NULL; sysfsfwdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW_ATTRIB); pending = g_build_filename(sysfsfwdir, "thinklmi", "attributes", "pending_reboot", NULL); /* we can't check, assume not locked */ if (!g_file_test(pending, G_FILE_TEST_EXISTS)) return TRUE; if (!g_file_get_contents(pending, &buf, NULL, error)) { g_prefix_error(error, "failed to get %s: ", pending); return FALSE; } *result = fu_common_strtoull(buf) > 0; return TRUE; } static gboolean fu_plugin_lenovo_firmware_locked(gboolean *locked, GError **error) { g_autofree gchar *buf = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *thinklmi = NULL; sysfsfwdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW_ATTRIB); thinklmi = g_build_filename(sysfsfwdir, "thinklmi", "attributes", "BootOrderLock", "current_value", NULL); /* we can't check, assume not locked */ if (!g_file_test(thinklmi, G_FILE_TEST_EXISTS)) return TRUE; if (!g_file_get_contents(thinklmi, &buf, NULL, error)) { g_prefix_error(error, "failed to get %s: ", thinklmi); return FALSE; } *locked = g_strcmp0(g_strchomp(buf), "Enable") == 0; return TRUE; } static void fu_plugin_lenovo_thinklmi_device_registered(FuPlugin *plugin, FuDevice *device) { gboolean locked = FALSE; gboolean pending = FALSE; g_autoptr(GError) error_locked = NULL; g_autoptr(GError) error_pending = NULL; if (g_strcmp0(fu_device_get_plugin(device), "uefi_capsule") != 0) return; if (!fu_plugin_lenovo_firmware_locked(&locked, &error_locked)) { g_debug("%s", error_locked->message); return; } if (!fu_plugin_lenovo_firmware_pending_change(&pending, &error_pending)) { g_debug("%s", error_pending->message); return; } if (locked) fu_device_inhibit(device, "uefi-capsule-bootorder", "BootOrder is locked in firmware setup"); if (pending) fu_device_inhibit(device, "uefi-capsule-pending-reboot", "UEFI BIOS settings update pending reboot"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->startup = fu_plugin_lenovo_thinklmi_startup; vfuncs->device_registered = fu_plugin_lenovo_thinklmi_device_registered; } fwupd-1.7.5/plugins/lenovo-thinklmi/fu-self-test.c000066400000000000000000000121361420024370600221340ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-plugin-private.h" typedef struct { 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 void fu_test_self_init(FuTest *self) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; g_autofree gchar *pluginfn_uefi = NULL; g_autofree gchar *pluginfn_lenovo = 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); self->plugin_uefi_capsule = fu_plugin_new(ctx); pluginfn_uefi = g_test_build_filename(G_TEST_BUILT, "..", "uefi-capsule", "libfu_plugin_uefi_capsule." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(self->plugin_uefi_capsule, pluginfn_uefi, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin_uefi_capsule, &error); g_assert_no_error(error); g_assert_true(ret); self->plugin_lenovo_thinklmi = fu_plugin_new(ctx); pluginfn_lenovo = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_lenovo_thinklmi." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(self->plugin_lenovo_thinklmi, pluginfn_lenovo, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin_lenovo_thinklmi, &error); g_assert_no_error(error); g_assert_true(ret); } static FuDevice * fu_test_probe_fake_esrt(FuTest *self) { gboolean ret; gulong added_id; FuDevice *dev = NULL; 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, &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; g_autoptr(FuDevice) dev = NULL; g_autofree gchar *test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "locked", NULL); g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); 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; g_autoptr(FuDevice) dev = NULL; g_autofree gchar *test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "unlocked", NULL); g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); 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_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 *test_dir = NULL; g_autoptr(FuTest) self = g_new0(FuTest, 1); 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); g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); /* starting ESRT path */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); /* change behavior of UEFI plugin for test mode */ sysfsdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW); g_setenv("FWUPD_UEFI_ESP_PATH", sysfsdir, TRUE); 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); g_assert_cmpint(g_mkdir_with_parents("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); /* tests go here */ fu_test_self_init(self); 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); return g_test_run(); } fwupd-1.7.5/plugins/lenovo-thinklmi/meson.build000066400000000000000000000021011420024370600216030ustar00rootroot00000000000000if get_option('plugin_uefi_capsule') cargs = ['-DG_LOG_DOMAIN="FuPluginLenovoThinkLmi"'] shared_module('fu_plugin_lenovo_thinklmi', fu_hash, sources : [ 'fu-plugin-lenovo-thinklmi.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], ) test('lenovo-thinklmi-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/lenovo-thinklmi/tests/000077500000000000000000000000001420024370600206115ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/000077500000000000000000000000001420024370600213545ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/efivars/000077500000000000000000000000001420024370600230135ustar00rootroot00000000000000fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461420024370600361500ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/000077500000000000000000000000001420024370600223315ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/000077500000000000000000000000001420024370600240025ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/000077500000000000000000000000001420024370600252235ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051420024370600277510ustar00rootroot000000000000000xfe fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451420024370600267460ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021420024370600266130ustar00rootroot000000000000001 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061420024370600273230ustar00rootroot0000000000000065586 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021420024370600312420ustar00rootroot000000000000001 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111420024370600314040ustar00rootroot0000000000000018472960 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061420024370600330250ustar00rootroot0000000000000065582 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/efi/fw_platform_size000066400000000000000000000000031420024370600246420ustar00rootroot0000000000000064 fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/000077500000000000000000000000001420024370600246115ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/000077500000000000000000000000001420024370600260525ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/000077500000000000000000000000001420024370600276715ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes/000077500000000000000000000000001420024370600320575ustar00rootroot00000000000000BootOrderLock/000077500000000000000000000000001420024370600345105ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributescurrent_value000066400000000000000000000000071420024370600373060ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes/BootOrderLockEnable fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/000077500000000000000000000000001420024370600264155ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/000077500000000000000000000000001420024370600302345ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes/000077500000000000000000000000001420024370600324225ustar00rootroot00000000000000BootOrderLock/000077500000000000000000000000001420024370600350535ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributescurrent_value000066400000000000000000000000101420024370600376430ustar00rootroot00000000000000fwupd-1.7.5/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes/BootOrderLockDisable fwupd-1.7.5/plugins/linux-lockdown/000077500000000000000000000000001420024370600173055ustar00rootroot00000000000000fwupd-1.7.5/plugins/linux-lockdown/README.md000066400000000000000000000004101420024370600205570ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/linux-lockdown/fu-plugin-linux-lockdown.c000066400000000000000000000106351420024370600243370ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include typedef enum { FU_PLUGIN_LINUX_LOCKDOWN_UNKNOWN, FU_PLUGIN_LINUX_LOCKDOWN_INVALID, FU_PLUGIN_LINUX_LOCKDOWN_NONE, FU_PLUGIN_LINUX_LOCKDOWN_INTEGRITY, FU_PLUGIN_LINUX_LOCKDOWN_CONFIDENTIALITY, } FuPluginLinuxLockdown; struct FuPluginData { GFile *file; GFileMonitor *monitor; FuPluginLinuxLockdown lockdown; }; static void fu_plugin_linux_lockdown_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_linux_lockdown_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->file != NULL) g_object_unref(data->file); if (data->monitor != NULL) { g_file_monitor_cancel(data->monitor); g_object_unref(data->monitor); } } static const gchar * fu_plugin_linux_lockdown_to_string(FuPluginLinuxLockdown lockdown) { if (lockdown == FU_PLUGIN_LINUX_LOCKDOWN_NONE) return "none"; if (lockdown == FU_PLUGIN_LINUX_LOCKDOWN_INTEGRITY) return "integrity"; if (lockdown == FU_PLUGIN_LINUX_LOCKDOWN_CONFIDENTIALITY) return "confidentiality"; if (lockdown == FU_PLUGIN_LINUX_LOCKDOWN_INVALID) return "invalid"; return NULL; } static void fu_plugin_linux_lockdown_rescan(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; /* load file */ if (!g_file_load_contents(data->file, NULL, &buf, &bufsz, NULL, NULL)) { data->lockdown = FU_PLUGIN_LINUX_LOCKDOWN_INVALID; } else if (g_strstr_len(buf, bufsz, "[none]") != NULL) { data->lockdown = FU_PLUGIN_LINUX_LOCKDOWN_NONE; } else if (g_strstr_len(buf, bufsz, "[integrity]") != NULL) { data->lockdown = FU_PLUGIN_LINUX_LOCKDOWN_INTEGRITY; } else if (g_strstr_len(buf, bufsz, "[confidentiality]") != NULL) { data->lockdown = FU_PLUGIN_LINUX_LOCKDOWN_CONFIDENTIALITY; } else { data->lockdown = FU_PLUGIN_LINUX_LOCKDOWN_UNKNOWN; } /* update metadata */ fu_plugin_add_report_metadata(plugin, "LinuxLockdown", fu_plugin_linux_lockdown_to_string(data->lockdown)); } static void fu_plugin_linux_lockdown_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_plugin_linux_lockdown_rescan(plugin); fu_context_security_changed(ctx); } static gboolean fu_plugin_linux_lockdown_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *path = NULL; g_autofree gchar *fn = NULL; path = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_SECURITY); fn = g_build_filename(path, "lockdown", NULL); data->file = g_file_new_for_path(fn); data->monitor = g_file_monitor(data->file, G_FILE_MONITOR_NONE, NULL, error); if (data->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(data->monitor), "changed", G_CALLBACK(fu_plugin_linux_lockdown_changed_cb), plugin); fu_plugin_linux_lockdown_rescan(plugin); return TRUE; } static void fu_plugin_linux_lockdown_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fu_security_attrs_append(attrs, attr); /* load file */ if (data->lockdown == FU_PLUGIN_LINUX_LOCKDOWN_INVALID || data->lockdown == FU_PLUGIN_LINUX_LOCKDOWN_UNKNOWN) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } if (data->lockdown == FU_PLUGIN_LINUX_LOCKDOWN_NONE) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_linux_lockdown_init; vfuncs->destroy = fu_plugin_linux_lockdown_destroy; vfuncs->startup = fu_plugin_linux_lockdown_startup; vfuncs->add_security_attrs = fu_plugin_linux_lockdown_add_security_attrs; } fwupd-1.7.5/plugins/linux-lockdown/meson.build000066400000000000000000000007231420024370600214510ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxLockdown"'] shared_module('fu_plugin_linux_lockdown', fu_hash, sources : [ 'fu-plugin-linux-lockdown.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/linux-sleep/000077500000000000000000000000001420024370600165755ustar00rootroot00000000000000fwupd-1.7.5/plugins/linux-sleep/README.md000066400000000000000000000003531420024370600200550ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/linux-sleep/fu-plugin-linux-sleep.c000066400000000000000000000027331420024370600231170ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include static void fu_plugin_linux_sleep_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 = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); 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); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->add_security_attrs = fu_plugin_linux_sleep_add_security_attrs; } fwupd-1.7.5/plugins/linux-sleep/meson.build000066400000000000000000000007121420024370600207370ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxSleep"'] shared_module('fu_plugin_linux_sleep', fu_hash, sources : [ 'fu-plugin-linux-sleep.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/linux-swap/000077500000000000000000000000001420024370600164375ustar00rootroot00000000000000fwupd-1.7.5/plugins/linux-swap/README.md000066400000000000000000000004011420024370600177110ustar00rootroot00000000000000# 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` fwupd-1.7.5/plugins/linux-swap/fu-linux-swap.c000066400000000000000000000072531420024370600213310ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_volume_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_common_get_volume_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_common_strnsplit(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) { self->enabled_cnt++; if (!fu_linux_swap_verify_file(self, fn, 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.7.5/plugins/linux-swap/fu-linux-swap.h000066400000000000000000000007211420024370600213270ustar00rootroot00000000000000/* * 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.7.5/plugins/linux-swap/fu-plugin-linux-swap.c000066400000000000000000000070061420024370600226210ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-linux-swap.h" struct FuPluginData { GFile *file; GFileMonitor *monitor; }; static void fu_plugin_linux_swap_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_linux_swap_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->file != NULL) g_object_unref(data->file); if (data->monitor != NULL) { g_file_monitor_cancel(data->monitor); g_object_unref(data->monitor); } } static void fu_plugin_linux_swap_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_plugin_linux_swap_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *fn = NULL; g_autofree gchar *procfs = NULL; procfs = fu_common_get_path(FU_PATH_KIND_PROCFS); fn = g_build_filename(procfs, "swaps", NULL); data->file = g_file_new_for_path(fn); data->monitor = g_file_monitor(data->file, G_FILE_MONITOR_NONE, NULL, error); if (data->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(data->monitor), "changed", G_CALLBACK(fu_plugin_linux_swap_changed_cb), plugin); return TRUE; } static void fu_plugin_linux_swap_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(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; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fu_security_attrs_append(attrs, attr); /* load list of swaps */ if (!g_file_load_contents(data->file, NULL, &buf, &bufsz, NULL, &error_local)) { g_autofree gchar *fn = g_file_get_path(data->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(data->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_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* add security attribute */ if (!fu_linux_swap_get_encrypted(swap)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_linux_swap_init; vfuncs->destroy = fu_plugin_linux_swap_destroy; vfuncs->startup = fu_plugin_linux_swap_startup; vfuncs->add_security_attrs = fu_plugin_linux_swap_add_security_attrs; } fwupd-1.7.5/plugins/linux-swap/fu-self-test.c000066400000000000000000000043631420024370600211270ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_test_skip(error->message); return; } if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) { 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_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) { 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); 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.7.5/plugins/linux-swap/meson.build000066400000000000000000000017121420024370600206020ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxSwap"'] shared_module('fu_plugin_linux_swap', fu_hash, sources : [ 'fu-plugin-linux-swap.c', 'fu-linux-swap.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'linux-swap-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-linux-swap.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('linux-swap-self-test', e) # added to installed-tests endif endif fwupd-1.7.5/plugins/linux-tainted/000077500000000000000000000000001420024370600171155ustar00rootroot00000000000000fwupd-1.7.5/plugins/linux-tainted/README.md000066400000000000000000000003761420024370600204020ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/linux-tainted/fu-plugin-linux-tainted.c000066400000000000000000000057171420024370600237640ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { GFile *file; GFileMonitor *monitor; }; static void fu_plugin_linux_tainted_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_linux_tainted_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->file != NULL) g_object_unref(data->file); if (data->monitor != NULL) { g_file_monitor_cancel(data->monitor); g_object_unref(data->monitor); } } static void fu_plugin_linux_tainted_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_plugin_linux_tainted_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *fn = NULL; g_autofree gchar *procfs = NULL; procfs = fu_common_get_path(FU_PATH_KIND_PROCFS); fn = g_build_filename(procfs, "sys", "kernel", "tainted", NULL); data->file = g_file_new_for_path(fn); data->monitor = g_file_monitor(data->file, G_FILE_MONITOR_NONE, NULL, error); if (data->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(data->monitor), "changed", G_CALLBACK(fu_plugin_linux_tainted_changed_cb), plugin); return TRUE; } static void fu_plugin_linux_tainted_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fu_security_attrs_append(attrs, attr); /* load file */ if (!g_file_load_contents(data->file, NULL, &buf, &bufsz, NULL, &error_local)) { g_autofree gchar *fn = g_file_get_path(data->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_strcmp0(buf, "0\n") != 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_TAINTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_linux_tainted_init; vfuncs->destroy = fu_plugin_linux_tainted_destroy; vfuncs->startup = fu_plugin_linux_tainted_startup; vfuncs->add_security_attrs = fu_plugin_linux_tainted_add_security_attrs; } fwupd-1.7.5/plugins/linux-tainted/meson.build000066400000000000000000000007201420024370600212560ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxTainted"'] shared_module('fu_plugin_linux_tainted', fu_hash, sources : [ 'fu-plugin-linux-tainted.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/logind/000077500000000000000000000000001420024370600156045ustar00rootroot00000000000000fwupd-1.7.5/plugins/logind/README.md000066400000000000000000000005321420024370600170630ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/logind/fu-plugin-logind.c000066400000000000000000000066731420024370600211440ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include struct FuPluginData { GDBusProxy *logind_proxy; gint logind_fd; }; static void fu_plugin_logind_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_logind_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->logind_fd != 0) g_close(data->logind_fd, NULL); if (data->logind_proxy != NULL) g_object_unref(data->logind_proxy); } static gboolean fu_plugin_logind_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *name_owner = NULL; data->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 (data->logind_proxy == NULL) { g_prefix_error(error, "failed to connect to logind: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(data->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(data->logind_proxy)); return FALSE; } return TRUE; } static gboolean fu_plugin_logind_prepare(FuPlugin *plugin, FuDevice *device, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data(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 (data->logind_fd != 0) return TRUE; /* not yet connected */ if (data->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( data->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; } data->logind_fd = g_unix_fd_list_get(out_fd_list, 0, NULL); g_debug("opened logind fd %i", data->logind_fd); return TRUE; } static gboolean fu_plugin_logind_cleanup(FuPlugin *plugin, FuDevice *device, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->logind_fd == 0) return TRUE; g_debug("closed logind fd %i", data->logind_fd); if (!g_close(data->logind_fd, error)) return FALSE; data->logind_fd = 0; return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_logind_init; vfuncs->destroy = fu_plugin_logind_destroy; vfuncs->startup = fu_plugin_logind_startup; vfuncs->cleanup = fu_plugin_logind_cleanup; vfuncs->prepare = fu_plugin_logind_prepare; } fwupd-1.7.5/plugins/logind/meson.build000066400000000000000000000010071420024370600177440ustar00rootroot00000000000000if get_option('systemd') or get_option('elogind') if host_machine.system() != 'linux' error('linux is required for systemd') endif cargs = ['-DG_LOG_DOMAIN="FuPluginLogind"'] shared_module('fu_plugin_logind', fu_hash, sources : [ 'fu-plugin-logind.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/logitech-bulkcontroller/000077500000000000000000000000001420024370600211655ustar00rootroot00000000000000fwupd-1.7.5/plugins/logitech-bulkcontroller/README.md000066400000000000000000000017121420024370600224450ustar00rootroot00000000000000# Logitech Video Collaboration ## Introduction This plugin can upgrade the firmware on Logitech Video Collaboration products (Rally Bar and Rally Bar 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` * `USB\VID_046D&PID_08D3` ## 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`. fwupd-1.7.5/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-common.c000066400000000000000000000200141420024370600300610ustar00rootroot00000000000000/* * 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)); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); 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)); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); return buf; } GByteArray * proto_manager_generate_set_device_time_request(void) { GByteArray *buf = g_byte_array_new(); #if GLIB_CHECK_VERSION(2, 57, 1) g_autoptr(GTimeZone) tz = g_time_zone_new_local(); #else g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); #endif 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; request_msg.payload_case = LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_SET_DEVICE_TIME_REQUEST; request_msg.set_device_time_request = &set_devicetime_msg; #if GLIB_CHECK_VERSION(2, 57, 1) set_devicetime_msg.ts = (g_get_real_time() / 1000) + SET_TIME_DELAY_MS; set_devicetime_msg.time_zone = g_strdup_printf("%s", g_time_zone_get_identifier(tz)); #else set_devicetime_msg.ts = (g_date_time_to_unix(dt) * 1000) + SET_TIME_DELAY_MS; set_devicetime_msg.time_zone = g_strdup_printf("%s", "UTC"); #endif 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)); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); return 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(); guint32 success = 0; guint32 error_code = 0; 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; success = usb_msg->response->transition_to_devicemode_response->success ? 1 : 0; error_code = usb_msg->response->transition_to_devicemode_response->error; fu_byte_array_append_uint32(buf_decoded, success, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf_decoded, error_code, G_LITTLE_ENDIAN); } 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); } const gchar * fu_logitech_bulkcontroller_device_status_to_string(FuLogitechBulkcontrollerDeviceStatus status) { if (status == kDeviceStateUnknown) return "Unknown"; if (status == kDeviceStateOffline) return "Offline"; if (status == kDeviceStateOnline) return "Online"; if (status == kDeviceStateIdle) return "Idle"; if (status == kDeviceStateInUse) return "InUse"; if (status == kDeviceStateAudioOnly) return "AudioOnly"; if (status == kDeviceStateEnumerating) return "Enumerating"; return NULL; } const gchar * fu_logitech_bulkcontroller_device_update_state_to_string( FuLogitechBulkcontrollerDeviceUpdateState update_state) { if (update_state == kUpdateStateUnknown) return "Unknown"; if (update_state == kUpdateStateCurrent) return "Current"; if (update_state == kUpdateStateAvailable) return "Available"; if (update_state == kUpdateStateStarting) return "Starting"; if (update_state == kUpdateStateDownloading) return "Downloading"; if (update_state == kUpdateStateReady) return "Ready"; if (update_state == kUpdateStateUpdating) return "Updating"; if (update_state == kUpdateStateScheduled) return "Scheduled"; if (update_state == kUpdateStateError) return "Error"; return NULL; } fwupd-1.7.5/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-common.h000066400000000000000000000031221420024370600300670ustar00rootroot00000000000000/* * 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 { kDeviceStateUnknown = -1, kDeviceStateOffline, kDeviceStateOnline, kDeviceStateIdle, kDeviceStateInUse, kDeviceStateAudioOnly, kDeviceStateEnumerating } FuLogitechBulkcontrollerDeviceStatus; typedef enum { kUpdateStateUnknown = -1, kUpdateStateCurrent, kUpdateStateAvailable, kUpdateStateStarting = 3, kUpdateStateDownloading, kUpdateStateReady, kUpdateStateUpdating, kUpdateStateScheduled, kUpdateStateError } FuLogitechBulkcontrollerDeviceUpdateState; typedef enum { kProtoId_UnknownId, kProtoId_GetDeviceInfoResponse, kProtoId_TransitionToDeviceModeResponse, kProtoId_Ack, kProtoId_KongEvent, kProtoId_HandshakeEvent, kProtoId_CrashDumpAvailableEvent } FuLogitechBulkcontrollerProtoId; const gchar * fu_logitech_bulkcontroller_device_status_to_string(FuLogitechBulkcontrollerDeviceStatus status); const gchar * fu_logitech_bulkcontroller_device_update_state_to_string( FuLogitechBulkcontrollerDeviceUpdateState update_state); 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(void); GByteArray * proto_manager_decode_message(const guint8 *data, guint32 len, FuLogitechBulkcontrollerProtoId *proto_id, GError **error); fwupd-1.7.5/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-device.c000066400000000000000000001122211420024370600300320ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-logitech-bulkcontroller-common.h" #include "fu-logitech-bulkcontroller-device.h" /* SYNC interface follows TLSV (Type, Length, SequenceID, Value) protocol */ /* UPD interface follows TLV (Type, Length, Value) protocol */ /* Payload size limited to 8k for both interfaces */ #define UPD_PACKET_HEADER_SIZE (2 * sizeof(guint32)) #define SYNC_PACKET_HEADER_SIZE (3 * sizeof(guint32)) #define HASH_TIMEOUT 30000 #define MAX_DATA_SIZE 8192 /* 8k */ #define PAYLOAD_SIZE MAX_DATA_SIZE - UPD_PACKET_HEADER_SIZE #define UPD_INTERFACE_SUBPROTOCOL_ID 117 #define SYNC_INTERFACE_SUBPROTOCOL_ID 118 #define BULK_TRANSFER_TIMEOUT 1000 #define HASH_VALUE_SIZE 16 #define LENGTH_OFFSET 0x4 #define COMMAND_OFFSET 0x0 #define SYNC_ACK_PAYLOAD_LENGTH 5 #define MAX_RETRIES 5 #define MAX_HANDSHAKE_RETRIES 3 #define MAX_WAIT_COUNT 150 enum { SHA_256, SHA_512, MD5 }; enum { EP_OUT, EP_IN, EP_LAST }; enum { BULK_INTERFACE_UPD, BULK_INTERFACE_SYNC }; 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; struct _FuLogitechBulkcontrollerDevice { FuUsbDevice parent_instance; guint sync_ep[EP_LAST]; guint update_ep[EP_LAST]; guint sync_iface; guint update_iface; FuLogitechBulkcontrollerDeviceStatus status; FuLogitechBulkcontrollerDeviceUpdateState update_status; guint update_progress; /* percentage value */ gboolean is_sync_transfer_in_progress; }; typedef struct { FuLogitechBulkcontrollerDevice *self; /* no-ref */ GByteArray *device_response; GByteArray *buf_pkt; GMainLoop *loop; GError *error; } FuLogitechBulkcontrollerHelper; G_DEFINE_TYPE(FuLogitechBulkcontrollerDevice, fu_logitech_bulkcontroller_device, FU_TYPE_USB_DEVICE) static void fu_logitech_bulkcontroller_helper_free(FuLogitechBulkcontrollerHelper *helper) { if (helper->error != NULL) g_error_free(helper->error); g_byte_array_unref(helper->buf_pkt); g_byte_array_unref(helper->device_response); g_main_loop_unref(helper->loop); g_slice_free(FuLogitechBulkcontrollerHelper, helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechBulkcontrollerHelper, fu_logitech_bulkcontroller_helper_free) #pragma clang diagnostic pop static void fu_logitech_bulkcontroller_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); fu_common_string_append_kx(str, idt, "SyncIface", self->sync_iface); fu_common_string_append_kx(str, idt, "UpdateIface", self->update_iface); fu_common_string_append_kv( str, idt, "Status", fu_logitech_bulkcontroller_device_status_to_string(self->status)); fu_common_string_append_kv( str, idt, "UpdateState", fu_logitech_bulkcontroller_device_update_state_to_string(self->update_status)); } static gboolean fu_logitech_bulkcontroller_device_probe(FuDevice *device, GError **error) { #if G_USB_CHECK_VERSION(0, 3, 3) 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; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif } static gboolean fu_logitech_bulkcontroller_device_send(FuLogitechBulkcontrollerDevice *self, 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_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; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), 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_bulkcontroller_device_recv(FuLogitechBulkcontrollerDevice *self, 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_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; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), 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_bulkcontroller_device_send_upd_cmd(FuLogitechBulkcontrollerDevice *self, 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_bulkcontroller_device_send(self, buf_pkt, BULK_INTERFACE_UPD, error)) return FALSE; /* receiving INIT ACK */ fu_byte_array_set_size(buf_ack, MAX_DATA_SIZE); /* 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_bulkcontroller_device_recv(self, buf_ack, BULK_INTERFACE_UPD, timeout, error)) return FALSE; if (!fu_common_read_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_common_read_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 gboolean fu_logitech_bulkcontroller_device_send_sync_cmd(FuLogitechBulkcontrollerDevice *self, guint32 cmd, GByteArray *buf, GError **error) { g_autoptr(GByteArray) buf_pkt = 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 */ fu_byte_array_append_uint32(buf_pkt, g_random_int_range(0, G_MAXUINT16), G_LITTLE_ENDIAN); /*Sequence(S) : Sequence ID of the data */ if (buf != NULL) { g_byte_array_append(buf_pkt, buf->data, buf->len); /* Value(V) : Actual payload data */ } if (!fu_logitech_bulkcontroller_device_send(self, buf_pkt, BULK_INTERFACE_SYNC, error)) 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 gboolean fu_logitech_bulkcontroller_device_json_parser(FuDevice *device, GByteArray *decoded_pkt, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); 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(device, json_object_get_string_member(json_device, "name")); if (json_object_has_member(json_device, "sw")) fu_device_set_version(device, json_object_get_string_member(json_device, "sw")); if (json_object_has_member(json_device, "type")) fu_device_add_instance_id(device, json_object_get_string_member(json_device, "type")); 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; } /* async callback handler : read data from sync endpoint continuously */ static void fu_logitech_bulkcontroller_device_sync_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { FuLogitechBulkcontrollerHelper *helper = (FuLogitechBulkcontrollerHelper *)user_data; FuLogitechBulkcontrollerDevice *self = helper->self; guint32 cmd_tmp = 0x0; guint64 cmd_tmp_64 = 0x0; guint32 response_length = 0; guint8 ack_payload[SYNC_ACK_PAYLOAD_LENGTH] = {0}; g_autoptr(GByteArray) buf_ack = g_byte_array_new(); g_autoptr(GError) error_local = NULL; if (!g_usb_device_bulk_transfer_finish(G_USB_DEVICE(source_object), res, &error_local)) { g_propagate_prefixed_error(&helper->error, g_steal_pointer(&error_local), "failed to finish using bulk transfer: "); g_main_loop_quit(helper->loop); return; } if (!fu_common_read_uint32_safe(helper->buf_pkt->data, helper->buf_pkt->len, COMMAND_OFFSET, &cmd_tmp, G_LITTLE_ENDIAN, &helper->error)) { g_prefix_error(&helper->error, "failed to retrieve payload command: "); g_main_loop_quit(helper->loop); return; } if (!fu_common_read_uint32_safe(helper->buf_pkt->data, helper->buf_pkt->len, LENGTH_OFFSET, &response_length, G_LITTLE_ENDIAN, &helper->error)) { g_prefix_error(&helper->error, "failed to retrieve payload length: "); g_main_loop_quit(helper->loop); return; } if (!fu_common_read_uint64_safe(helper->buf_pkt->data, helper->buf_pkt->len, SYNC_PACKET_HEADER_SIZE, &cmd_tmp_64, G_LITTLE_ENDIAN, &helper->error)) { g_prefix_error(&helper->error, "failed to retrieve payload data: "); g_main_loop_quit(helper->loop); return; } if (!fu_memcpy_safe((guint8 *)ack_payload, sizeof(ack_payload), 0x0, (guint8 *)&cmd_tmp_64, sizeof(cmd_tmp_64), 0x0, SYNC_ACK_PAYLOAD_LENGTH, &helper->error)) { g_prefix_error(&helper->error, "failed to copy payload data: "); g_main_loop_quit(helper->loop); return; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) g_debug("Received 0x%x message on sync interface", cmd_tmp); switch (cmd_tmp) { case CMD_ACK: if (CMD_BUFFER_WRITE == fu_common_strtoull((const char *)ack_payload)) { if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_UNINIT_BUFFER, NULL, &helper->error)) { g_prefix_error(&helper->error, "failed to send %d while processing %d: ", CMD_UNINIT_BUFFER, CMD_BUFFER_WRITE); g_main_loop_quit(helper->loop); return; } } else if (CMD_UNINIT_BUFFER != fu_common_strtoull((const char *)ack_payload)) { g_set_error(&helper->error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid message received: expected %s, but received %d: ", (const gchar *)ack_payload, CMD_UNINIT_BUFFER); g_main_loop_quit(helper->loop); return; } break; case CMD_BUFFER_READ: g_byte_array_append(helper->device_response, helper->buf_pkt->data + SYNC_PACKET_HEADER_SIZE, response_length); if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_autofree gchar *strsafe = fu_common_strsafe((const gchar *)helper->device_response->data, helper->device_response->len); g_debug("Received data on sync interface. length: %u, buffer: %s", helper->device_response->len, strsafe); } fu_byte_array_append_uint32(buf_ack, cmd_tmp, G_LITTLE_ENDIAN); if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_ACK, buf_ack, &helper->error)) { g_prefix_error(&helper->error, "failed to send %d while processing %d: ", CMD_ACK, CMD_BUFFER_READ); g_main_loop_quit(helper->loop); return; } break; case CMD_UNINIT_BUFFER: fu_byte_array_append_uint32(buf_ack, cmd_tmp, G_LITTLE_ENDIAN); if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_ACK, buf_ack, &helper->error)) { g_prefix_error(&helper->error, "failed to send %d while processing %d: ", CMD_ACK, CMD_UNINIT_BUFFER); g_main_loop_quit(helper->loop); return; } self->is_sync_transfer_in_progress = FALSE; break; default: break; } g_main_loop_quit(helper->loop); return; } static gboolean fu_logitech_bulkcontroller_device_startlistening_sync(FuLogitechBulkcontrollerDevice *self, GByteArray *device_response, GError **error) { gint max_retry = MAX_RETRIES * 2; self->is_sync_transfer_in_progress = TRUE; while (self->is_sync_transfer_in_progress) { g_autoptr(FuLogitechBulkcontrollerHelper) helper = g_slice_new0(FuLogitechBulkcontrollerHelper); max_retry--; helper->self = self; helper->buf_pkt = g_byte_array_new(); helper->loop = g_main_loop_new(NULL, FALSE); helper->device_response = g_byte_array_ref(device_response); fu_byte_array_set_size(helper->buf_pkt, MAX_DATA_SIZE); g_usb_device_bulk_transfer_async(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->sync_ep[EP_IN], helper->buf_pkt->data, helper->buf_pkt->len, BULK_TRANSFER_TIMEOUT, NULL, /* cancellable */ fu_logitech_bulkcontroller_device_sync_cb, helper); g_main_loop_run(helper->loop); /* handle error scenario, e.g. device no longer responding */ if (max_retry == 0) { self->is_sync_transfer_in_progress = FALSE; if (helper->error != NULL) { g_propagate_prefixed_error(error, g_steal_pointer(&helper->error), "failed after %i retries: ", MAX_RETRIES); } else { g_set_error(&helper->error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed after %i retries: ", MAX_RETRIES); } return FALSE; } /* just show to console */ if (helper->error != NULL) g_warning("async error %s", helper->error->message); } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_get_data(FuDevice *device, gboolean send_req, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GByteArray) decoded_pkt = g_byte_array_new(); g_autoptr(GByteArray) device_response = g_byte_array_new(); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; /* 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 = g_byte_array_new(); device_request = proto_manager_generate_get_device_info_request(); if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_BUFFER_WRITE, device_request, error)) { g_prefix_error( error, "failed to send write buffer packet for device info request: "); return FALSE; } } if (!fu_logitech_bulkcontroller_device_startlistening_sync(self, device_response, error)) { g_prefix_error(error, "failed to receive data packet for device info request: "); return FALSE; } /* handle error scenario, e.g. CMD_UNINIT_BUFFER arrived before CMD_BUFFER_READ */ if (device_response->len == 0) { g_prefix_error(error, "failed to receive expected packet for device info request: "); return FALSE; } decoded_pkt = proto_manager_decode_message(device_response->data, device_response->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet for device info request: "); return FALSE; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_autofree gchar *strsafe = fu_common_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("Received device response: id: %u, length %u, data: %s", proto_id, device_response->len, strsafe); } 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(device, decoded_pkt, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_send_upd_init_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); return fu_logitech_bulkcontroller_device_send_upd_cmd(self, CMD_INIT, NULL, error); } 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); gboolean query_device = FALSE; /* query or listen for events, periodically broadcasted */ gint max_wait = MAX_WAIT_COUNT; /* if firmware upgrade is taking forever to finish */ guint max_no_response_count = MAX_RETRIES; /* device doesn't respond */ guint no_response_count = 0; 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(GPtrArray) chunks = NULL; g_autofree gchar *old_firmware_version = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 49); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 49); /* 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_send_upd_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); if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self, 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 */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, PAYLOAD_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_bulkcontroller_device_send_upd_cmd(self, CMD_DATA_TRANSFER, data_pkt, error)) { g_prefix_error(error, "failed to send data packet 0x%x: ", i); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, chunks->len); } fu_progress_step_done(progress); /* sending end transfer */ 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, MD5, G_LITTLE_ENDIAN); /* checksum type */ g_byte_array_append(end_pkt, (const guint8 *)base64hash, strlen(base64hash)); if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self, CMD_END_TRANSFER, end_pkt, error)) { g_prefix_error(error, "failed to write end transfer transfer packet: "); return FALSE; } /* send uninit */ if (!fu_logitech_bulkcontroller_device_send_upd_cmd(self, CMD_UNINIT, NULL, 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 */ g_usleep(G_TIME_SPAN_SECOND); /* save the current firmware version for troubleshooting purpose */ old_firmware_version = g_strdup(fu_device_get_version(device)); do { g_autoptr(GError) error_local = NULL; /* skip explicit device query as long as device is publishing update events * (kProtoId_KongEvent) */ if (self->update_progress == 100) { query_device = TRUE; } else { query_device = (no_response_count == 0) ? FALSE : TRUE; } g_usleep(500 * G_TIME_SPAN_MILLISECOND); /* lost Success/Failure message, device rebooting */ if (no_response_count == max_no_response_count) { g_debug("device not responding, rebooting..."); break; } /* update device obj with latest info from the device */ if (!fu_logitech_bulkcontroller_device_get_data(device, query_device, &error_local)) { no_response_count++; g_debug("no response for device info request %u", no_response_count); continue; } /* device responsive, no error and not rebooting yet */ no_response_count = 0; if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_debug("firmware update status: %s. progress: %u", fu_logitech_bulkcontroller_device_update_state_to_string( self->update_status), self->update_progress); } /* existing device image version is same as newly pushed image */ if (self->update_status == kUpdateStateError) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware upgrade failed"); return FALSE; } if (self->update_status == kUpdateStateCurrent) { if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_debug("new firmware version: %s, old firmware version: %s, " "rebooting...", fu_device_get_version(device), old_firmware_version); } break; } if (self->update_progress == 100) { /* wait for state change: kUpdateStateUpdating->kUpdateStateCurrent * device no longer broadcast fu related events, need to query device * explicitly now */ g_usleep(G_USEC_PER_SEC); continue; } fu_progress_set_percentage_full(fu_progress_get_child(progress), self->update_progress, 100); } while (max_wait--); if (max_wait <= 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware upgrade timeout: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_get_handshake_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; g_autoptr(GByteArray) decoded_pkt = g_byte_array_new(); g_autoptr(GByteArray) device_response = g_byte_array_new(); g_autoptr(GError) error_local = NULL; if (!fu_logitech_bulkcontroller_device_startlistening_sync(self, device_response, &error_local)) { if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) g_debug("failed to receive data packet for handshake request"); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to receive data packet for handshake request"); return FALSE; } /* handle error scenario, e.g. CMD_UNINIT_BUFFER arrived before CMD_BUFFER_READ */ if (device_response->len == 0) { if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) g_debug("failed to receive expected packet for handshake request"); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to receive expected packet for handshake request"); return FALSE; } decoded_pkt = proto_manager_decode_message(device_response->data, device_response->len, &proto_id, &error_local); if (decoded_pkt == NULL) { if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) g_debug("failed to unpack packet for handshake request"); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to unpack packet for handshake request"); return FALSE; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_autofree gchar *strsafe = fu_common_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("Received initialization response: id: %u, length %u, data: %s", proto_id, device_response->len, strsafe); } /* skip optional initialization events -- not an error if these events are missed */ if (proto_id != kProtoId_HandshakeEvent) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid initialization message received: %u", proto_id); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_set_time(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GByteArray) device_request = g_byte_array_new(); g_autoptr(GByteArray) decoded_pkt = g_byte_array_new(); g_autoptr(GByteArray) device_response = g_byte_array_new(); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; /* send SetDeviceTimeRequest to sync device clock with host */ device_request = proto_manager_generate_set_device_time_request(); if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_BUFFER_WRITE, device_request, error)) { g_prefix_error(error, "failed to send write buffer packet for set device time request: "); return FALSE; } if (!fu_logitech_bulkcontroller_device_startlistening_sync(self, device_response, error)) { g_prefix_error(error, "failed to receive data packet for set device time request: "); return FALSE; } /* handle error scenario, e.g. CMD_UNINIT_BUFFER arrived before CMD_BUFFER_READ */ if (device_response->len == 0) { g_prefix_error(error, "failed to receive expected packet for set device time request: "); return FALSE; } decoded_pkt = proto_manager_decode_message(device_response->data, device_response->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet for set device time request: "); return FALSE; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_autofree gchar *strsafe = fu_common_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("Received device response while processing set device time request: id: " "%u, length %u, data: %s", proto_id, device_response->len, strsafe); } if (proto_id != kProtoId_Ack) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect response for set device time request"); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_setup(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GByteArray) device_request = g_byte_array_new(); g_autoptr(GByteArray) decoded_pkt = g_byte_array_new(); g_autoptr(GByteArray) device_response = g_byte_array_new(); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; guint32 success = 0; guint32 error_code = 0; g_autoptr(GError) error_local = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class)->setup(device, error)) return FALSE; /* check for initialization events generated by the device * no error check needed here, possibly missed */ if (!fu_device_retry(device, fu_logitech_bulkcontroller_device_get_handshake_cb, MAX_HANDSHAKE_RETRIES, NULL, &error_local)) { g_warning("failed to receive initialization events: %s", error_local->message); } /* * device supports USB_Device mode, Appliance mode and BYOD mode. * Only USB_Device mode is supported here. * Ensure it is running in USB_Device mode * Response has two data: Request succeeded or failed, and error code in case of failure */ device_request = proto_manager_generate_transition_to_device_mode_request(); if (!fu_logitech_bulkcontroller_device_send_sync_cmd(self, CMD_BUFFER_WRITE, device_request, error)) { g_prefix_error(error, "failed to send buffer write packet for transition mode request: "); return FALSE; } if (!fu_logitech_bulkcontroller_device_startlistening_sync(self, device_response, error)) { g_prefix_error(error, "failed to receive data packet for transition mode request: "); return FALSE; } /* handle error scenario, e.g. CMD_UNINIT_BUFFER arrived before CMD_BUFFER_READ */ if (device_response->len == 0) { g_prefix_error(error, "failed to receive expected packet for transition mode request: "); return FALSE; } decoded_pkt = proto_manager_decode_message(device_response->data, device_response->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet for transition mode request: "); return FALSE; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_autofree gchar *strsafe = fu_common_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("Received transition mode response: id: %u, length %u, data: %s", proto_id, device_response->len, strsafe); } if (proto_id != kProtoId_TransitionToDeviceModeResponse) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect response for transition mode request"); return FALSE; } if (!fu_common_read_uint32_safe(decoded_pkt->data, decoded_pkt->len, COMMAND_OFFSET, &success, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to retrieve result for transition mode request: "); return FALSE; } if (!fu_common_read_uint32_safe(decoded_pkt->data, decoded_pkt->len, LENGTH_OFFSET, &error_code, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to retrieve error code for transition mode request: "); return FALSE; } if (g_getenv("FWUPD_LOGITECH_BULKCONTROLLER_VERBOSE") != NULL) { g_debug("Received transition mode response. Success: %u, Error: %u", success, error_code); } if (!success) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "transition mode request failed. error: %u", error_code); return FALSE; } /* set device time */ if (!fu_logitech_bulkcontroller_device_set_time(device, error)) return FALSE; /* load current device data */ if (!fu_logitech_bulkcontroller_device_get_data(device, TRUE, error)) 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, 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_logitech_bulkcontroller_device_init(FuLogitechBulkcontrollerDevice *self) { 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_retry_set_delay(FU_DEVICE(self), 1000); fu_device_set_remove_delay(FU_DEVICE(self), 100000); /* >1 min to finish init */ } static void fu_logitech_bulkcontroller_device_class_init(FuLogitechBulkcontrollerDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); 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.7.5/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-device.h000066400000000000000000000006271420024370600300450ustar00rootroot00000000000000/* * 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) fwupd-1.7.5/plugins/logitech-bulkcontroller/fu-plugin-logitech-bulkcontroller.c000066400000000000000000000007451420024370600301000ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-bulkcontroller-device.h" static void fu_plugin_logitech_bulkcontroller_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_BULKCONTROLLER_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_logitech_bulkcontroller_init; } fwupd-1.7.5/plugins/logitech-bulkcontroller/logitech-bulkcontroller.quirk000066400000000000000000000003221420024370600270740ustar00rootroot00000000000000# 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 fwupd-1.7.5/plugins/logitech-bulkcontroller/meson.build000066400000000000000000000014621420024370600233320ustar00rootroot00000000000000if get_option('plugin_logitech_bulkcontroller') if not get_option('gusb') error('gusb is required for plugin_logitech_bulkcontroller') endif cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechBulkController"'] install_data(['logitech-bulkcontroller.quirk'], install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d') ) subdir('proto') shared_module('fu_plugin_logitech_bulkcontroller', fu_hash, sources : [ generated, 'fu-logitech-bulkcontroller-common.c', 'fu-logitech-bulkcontroller-device.c', 'fu-plugin-logitech-bulkcontroller.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/000077500000000000000000000000001420024370600223305ustar00rootroot00000000000000fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/antiflicker.proto000066400000000000000000000011761420024370600257150ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/ble_cfg.proto000066400000000000000000000006751420024370600250060ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/crash_info.proto000066400000000000000000000124311420024370600255310ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.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; } /** * 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; } fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/device_attestation.proto000066400000000000000000000011641420024370600272750ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/device_common.proto000066400000000000000000000022571420024370600262320ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/device_info.proto000066400000000000000000000011051420024370600256640ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/device_mode.proto000066400000000000000000000066261420024370600256720ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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; } /** * 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.7.5/plugins/logitech-bulkcontroller/proto/device_request.proto000066400000000000000000000053101420024370600264230ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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 reboot device * This is to be included in UsbMsg * 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; } /** * 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; } fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/device_time.proto000066400000000000000000000007131420024370600256730ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/firmware_update.proto000066400000000000000000000010551420024370600265740ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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.7.5/plugins/logitech-bulkcontroller/proto/meson.build000066400000000000000000000011161420024370600244710ustar00rootroot00000000000000 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.7.5/plugins/logitech-bulkcontroller/proto/ota_manifest.proto000066400000000000000000000022111420024370600260620ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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; } /** * 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; } fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/rightsight.proto000066400000000000000000000057001420024370600255730ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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. * */ message RightSightConfiguration { /** * 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; } /** * 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 * */ 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; } fwupd-1.7.5/plugins/logitech-bulkcontroller/proto/usb_msg.proto000066400000000000000000000120121420024370600250500ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 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; } } /** * 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; } } /** * 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; } } fwupd-1.7.5/plugins/logitech-hidpp/000077500000000000000000000000001420024370600172305ustar00rootroot00000000000000fwupd-1.7.5/plugins/logitech-hidpp/README.md000066400000000000000000000125521420024370600205140ustar00rootroot00000000000000# Logitech HID ## Introduction This plugin can flash the firmware on: * Logitech Unifying dongles, both the Nordic (U0007) device and the Texas Instruments (U0008) versions * Logitech Bolt dongles * Unifying peripherals through the Unifying receiver * Peripherals through the Bolt receiver and directly through BLE This plugin will not work with the different "Nano" dongle (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&REV_0001` * `USB\VID_046D&PID_AAAA` * `USB\VID_046D` 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` * `HIDRAW\VEN_046D` The Bolt dongle 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 dongle 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`. fwupd-1.7.5/plugins/logitech-hidpp/data/000077500000000000000000000000001420024370600201415ustar00rootroot00000000000000fwupd-1.7.5/plugins/logitech-hidpp/data/dump.csv.gz000066400000000000000000003104571420024370600222540ustar00rootroot00000000000000ɺ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.7.5/plugins/logitech-hidpp/data/dump.tdc000066400000000000000000025021401420024370600216060ustar00rootroot00000000000000TPDC?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.7.5/plugins/logitech-hidpp/data/lsusb-U0007-bootloader.txt000066400000000000000000000034761420024370600247050ustar00rootroot00000000000000Bus 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.7.5/plugins/logitech-hidpp/data/lsusb-U0007.txt000066400000000000000000000101341420024370600225420ustar00rootroot00000000000000Bus 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.7.5/plugins/logitech-hidpp/data/lsusb-U0008-bootloader-old.txt000066400000000000000000000057551420024370600254640ustar00rootroot00000000000000 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.7.5/plugins/logitech-hidpp/data/lsusb-U0008-bootloader.txt000066400000000000000000000057551420024370600247100ustar00rootroot00000000000000 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.7.5/plugins/logitech-hidpp/data/lsusb-U0008-old.txt000066400000000000000000000426521420024370600233310ustar00rootroot00000000000000 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.7.5/plugins/logitech-hidpp/data/lsusb-U0008.txt000066400000000000000000000426511420024370600225540ustar00rootroot00000000000000Bus 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.c000066400000000000000000000227551420024370600263230ustar00rootroot00000000000000/* * 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" struct _FuLogitechHidPpBootloaderNordic { FuLogitechHidPpBootloader parent_instance; }; G_DEFINE_TYPE(FuLogitechHidPpBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU_TYPE_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 13); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); /* block 0 */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 82); /* reset vector */ } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 22); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 72); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); /* block 0 */ 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_UNIFYING_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.h000066400000000000000000000006631420024370600263220ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_UNIFYING_BOOTLOADER_NORDIC (fu_logitech_hidpp_bootloader_nordic_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidPpBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU, UNIFYING_BOOTLOADER_NORDIC, FuLogitechHidPpBootloader) fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.c000066400000000000000000000177611420024370600261720ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" struct _FuLogitechHidPpBootloaderTexas { FuLogitechHidPpBootloader parent_instance; }; G_DEFINE_TYPE(FuLogitechHidPpBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU_TYPE_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); /* clear */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 18); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 79); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 11); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); /* clear */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 12); } /* 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_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) req->addr = payload->addr; else req->addr = payload->addr % 0x80; req->len = payload->len; memcpy(req->data, payload->data, payload->len); 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_UNIFYING_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_UNIFYING_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_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE) { guint16 addr_start = payload->addr - (7 * 0x10); if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.h000066400000000000000000000006561420024370600261720ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_UNIFYING_BOOTLOADER_TEXAS (fu_logitech_hidpp_bootloader_texas_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidPpBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU, UNIFYING_BOOTLOADER_TEXAS, FuLogitechHidPpBootloader) fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.c000066400000000000000000000331031420024370600250340ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-logitech-hidpp-bootloader.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" typedef struct { guint16 flash_addr_lo; guint16 flash_addr_hi; guint16 flash_blocksize; } FuLogitechHidPpBootloaderPrivate; #define FU_UNIFYING_DEVICE_EP1 0x81 #define FU_UNIFYING_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_UNIFYING_BOOTLOADER(device); FuLogitechHidPpBootloaderPrivate *priv = GET_PRIVATE(self); fu_common_string_append_kx(str, idt, "FlashAddrHigh", priv->flash_addr_hi); fu_common_string_append_kx(str, idt, "FlashAddrLow", priv->flash_addr_lo); fu_common_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_BOOTLOADER(self), 0x0000); return priv->flash_blocksize; } static gboolean fu_logitech_hidpp_bootloader_attach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidPpBootloader *self = FU_UNIFYING_BOOTLOADER(device); g_autoptr(FuLogitechHidPpBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_common_read_uint16(req->data + 0, G_BIG_ENDIAN); priv->flash_addr_hi = fu_common_read_uint16(req->data + 2, G_BIG_ENDIAN); priv->flash_blocksize = fu_common_read_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 */ if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { fu_common_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_UNIFYING_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_UNIFYING_BOOTLOADER_CMD_REBOOT) { g_autoptr(GError) error_ignore = NULL; if (!g_usb_device_interrupt_transfer(usb_device, FU_UNIFYING_DEVICE_EP1, buf_response, sizeof(buf_response), &actual_length, FU_UNIFYING_DEVICE_TIMEOUT_MS, NULL, &error_ignore)) { g_debug("ignoring: %s", error_ignore->message); } else { if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { fu_common_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_UNIFYING_DEVICE_EP1, buf_response, sizeof(buf_response), &actual_length, FU_UNIFYING_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_UNIFYING_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); } if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { fu_common_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_UNIFYING_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.h000066400000000000000000000060531420024370600250450ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UNIFYING_BOOTLOADER (fu_logitech_hidpp_bootloader_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLogitechHidPpBootloader, fu_logitech_hidpp_bootloader, FU, UNIFYING_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) typedef enum { FU_UNIFYING_BOOTLOADER_CMD_GENERAL_ERROR = 0x01, FU_UNIFYING_BOOTLOADER_CMD_READ = 0x10, FU_UNIFYING_BOOTLOADER_CMD_WRITE = 0x20, FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_ADDR = 0x21, FU_UNIFYING_BOOTLOADER_CMD_WRITE_VERIFY_FAIL = 0x22, FU_UNIFYING_BOOTLOADER_CMD_WRITE_NONZERO_START = 0x23, FU_UNIFYING_BOOTLOADER_CMD_WRITE_INVALID_CRC = 0x24, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE = 0x30, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_INVALID_ADDR = 0x31, FU_UNIFYING_BOOTLOADER_CMD_ERASE_PAGE_NONZERO_START = 0x33, FU_UNIFYING_BOOTLOADER_CMD_GET_HW_PLATFORM_ID = 0x40, FU_UNIFYING_BOOTLOADER_CMD_GET_FW_VERSION = 0x50, FU_UNIFYING_BOOTLOADER_CMD_GET_CHECKSUM = 0x60, FU_UNIFYING_BOOTLOADER_CMD_REBOOT = 0x70, FU_UNIFYING_BOOTLOADER_CMD_GET_MEMINFO = 0x80, FU_UNIFYING_BOOTLOADER_CMD_GET_BL_VERSION = 0x90, FU_UNIFYING_BOOTLOADER_CMD_GET_INIT_FW_VERSION = 0xa0, FU_UNIFYING_BOOTLOADER_CMD_READ_SIGNATURE = 0xb0, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER = 0xc0, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR = 0xc1, FU_UNIFYING_BOOTLOADER_CMD_WRITE_RAM_BUFFER_OVERFLOW = 0xc2, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM = 0xd0, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ADDR = 0xd1, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_WRONG_CRC = 0xd2, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_PAGE0_INVALID = 0xd3, FU_UNIFYING_BOOTLOADER_CMD_FLASH_RAM_INVALID_ORDER = 0xd4, FU_UNIFYING_BOOTLOADER_CMD_WRITE_SIGNATURE = 0xe0, FU_UNIFYING_BOOTLOADER_CMD_LAST } FuLogitechHidPpBootloaderCmd; /* 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-common.c000066400000000000000000000017501420024370600241750ustar00rootroot00000000000000/* * 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-common.h000066400000000000000000000020251420024370600241760ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_UNIFYING_DEVICE_VID 0x046d #define FU_UNIFYING_DEVICE_PID_RUNTIME 0xc52b #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC 0xaaaa #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_NORDIC_PICO 0xaaae #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS 0xaaac #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_TEXAS_PICO 0xaaad #define FU_UNIFYING_DEVICE_PID_BOOTLOADER_BOLT 0xab07 /* Signed firmware are very long to verify on the device */ #define FU_UNIFYING_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-device.c000066400000000000000000001336351420024370600241540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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" typedef struct { guint8 cached_fw_entity; /* * Device index: * - HIDPP_DEVICE_IDX_RECEIVER for the receiver * - HIDPP_DEVICE_IDX_BLE for 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; } const gchar * fu_logitech_hidpp_device_get_model_id(FuLogitechHidPpDevice *self) { FuLogitechHidPpDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_HIDPP_DEVICE(self), NULL); return priv->model_id; } 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 const gchar * fu_logitech_hidpp_feature_to_string(guint16 feature) { if (feature == HIDPP_FEATURE_ROOT) return "Root"; if (feature == HIDPP_FEATURE_I_FIRMWARE_INFO) return "IFirmwareInfo"; if (feature == HIDPP_FEATURE_GET_DEVICE_NAME_TYPE) return "GetDevicenameType"; if (feature == HIDPP_FEATURE_BATTERY_LEVEL_STATUS) return "BatteryLevelStatus"; if (feature == HIDPP_FEATURE_UNIFIED_BATTERY) return "UnifiedBattery"; if (feature == HIDPP_FEATURE_DFU_CONTROL) return "DfuControl"; if (feature == HIDPP_FEATURE_DFU_CONTROL_SIGNED) return "DfuControlSigned"; if (feature == HIDPP_FEATURE_DFU_CONTROL_BOLT) return "DfuControlBolt"; if (feature == HIDPP_FEATURE_DFU) return "Dfu"; 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 = 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); fu_device_inhibit(FU_DEVICE(self), "unreachable", "device is 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); fu_device_uninhibit(FU_DEVICE(self), "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); fu_device_uninhibit(radio, "unreachable"); } /* if the device index is unset, grab it from the reply */ if (priv->device_idx == HIDPP_DEVICE_IDX_UNSET && msg->device_id != HIDPP_DEVICE_IDX_UNSET) { 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); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); const gchar *devpath = g_udev_device_get_device_file(udev_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_common_string_append_kv(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_common_string_append_ku(str, idt, "HidppVersion", priv->hidpp_version); fu_common_string_append_ku(str, idt, "HidppPid", priv->hidpp_pid); fu_common_string_append_kx(str, idt, "DeviceIdx", priv->device_idx); fu_common_string_append_kv(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_UNIFYING_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, HIDPP_FEATURE_I_FIRMWARE_INFO); if (idx == 0x00) return TRUE; /* get the entity count */ msg->report_id = 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 = 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_autofree gchar *devid = NULL; 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, HIDPP_FEATURE_I_FIRMWARE_INFO); if (idx == 0x00) return TRUE; msg->report_id = 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 */ devid = g_strdup_printf("HIDRAW\\VEN_%04X&MOD_%s", (guint)FU_UNIFYING_DEVICE_VID, priv->model_id); fu_device_add_instance_id(FU_DEVICE(self), devid); return TRUE; } 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, 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 = 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; } else { /* fall back to the legacy Battery Level feature */ idx = fu_logitech_hidpp_device_feature_get_idx( self, HIDPP_FEATURE_BATTERY_LEVEL_STATUS); if (idx != 0x00) { g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = 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 = HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = HIDPP_SUBID_GET_REGISTER; msg->function_id = 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 = 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 = 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); /* * FuUdevDevice->probe except for paired devices. We don't want * paired devices to inherit the logical ids of the receiver. */ if (priv->device_idx == HIDPP_DEVICE_IDX_UNSET || priv->device_idx == HIDPP_DEVICE_IDX_BLE) { if (!FU_DEVICE_CLASS(fu_logitech_hidpp_device_parent_class)->probe(device, error)) 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 != HIDPP_DEVICE_IDX_UNSET && priv->device_idx != HIDPP_DEVICE_IDX_BLE) { 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); } return TRUE; } 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[] = {HIDPP_FEATURE_GET_DEVICE_NAME_TYPE, HIDPP_FEATURE_I_FIRMWARE_INFO, HIDPP_FEATURE_BATTERY_LEVEL_STATUS, HIDPP_FEATURE_UNIFIED_BATTERY, HIDPP_FEATURE_DFU_CONTROL, HIDPP_FEATURE_DFU_CONTROL_SIGNED, HIDPP_FEATURE_DFU_CONTROL_BOLT, HIDPP_FEATURE_DFU, 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 = HIDPP_DEVICE_IDX_BLE; /* * 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. */ g_usleep(G_USEC_PER_SEC); } if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID)) priv->device_idx = 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 == HIDPP_DEVICE_IDX_UNSET) { 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 = 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] != 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; /* 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; /* try using HID++2.0 */ idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); if (idx != 0x00) { const gchar *tmp; g_autoptr(FuLogitechHidPpHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = 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, HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { 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, HIDPP_FEATURE_DFU_CONTROL_BOLT); if (idx == 0x00) idx = fu_logitech_hidpp_device_feature_get_idx(self, 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 = 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"); } idx = fu_logitech_hidpp_device_feature_get_idx(self, 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_debug("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"); } /* 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, HIDPP_FEATURE_DFU_CONTROL_BOLT); if (idx == 0x00) idx = fu_logitech_hidpp_device_feature_get_idx(self, 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 = 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_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_UNIFYING_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_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fu_device_emit_request(device, request); } return TRUE; } /* this can reboot all by itself */ idx = fu_logitech_hidpp_device_feature_get_idx(self, HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { msg->report_id = 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_UNIFYING_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; } g_usleep(200 * 1000); 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 = 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 != HIDPP_DEVICE_IDX_UNSET && priv->device_idx != HIDPP_DEVICE_IDX_BLE) msg->flags = FU_UNIFYING_HIDPP_MSG_FLAG_RETRY_STUCK; memcpy(msg->data, data, 16); 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_common_read_uint32_safe(msg->data, sizeof(msg->data), 0x0, &packet_cnt, G_BIG_ENDIAN, error)) return FALSE; if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) 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_UNIFYING_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; } else { 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, 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_warning("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 */ if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) 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, 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 = 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_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID | // inferred? FU_UNIFYING_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_progress_sleep(progress, 1000); /* 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_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_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, 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 = HIDPP_DEVICE_IDX_UNSET; 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_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-device.h000066400000000000000000000037211420024370600241510ustar00rootroot00000000000000/* * 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); const gchar * fu_logitech_hidpp_device_get_model_id(FuLogitechHidPpDevice *self); gboolean fu_logitech_hidpp_device_attach(FuLogitechHidPpDevice *self, guint8 entity, FuProgress *progress, GError **error); FuLogitechHidPpDevice * fu_logitech_hidpp_device_new(FuUdevDevice *parent); fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c000066400000000000000000000272101420024370600245740ustar00rootroot00000000000000/* * 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" FuLogitechHidPpHidppMsg * fu_logitech_hidpp_msg_new(void) { return g_new0(FuLogitechHidPpHidppMsg, 1); } const gchar * fu_logitech_hidpp_msg_dev_id_to_string(FuLogitechHidPpHidppMsg *msg) { g_return_val_if_fail(msg != NULL, NULL); if (msg->device_id == HIDPP_DEVICE_IDX_WIRED) return "wired"; if (msg->device_id == HIDPP_DEVICE_IDX_RECEIVER) return "receiver"; if (msg->device_id == HIDPP_DEVICE_IDX_UNSET) return "unset"; return NULL; } const gchar * fu_logitech_hidpp_msg_rpt_id_to_string(FuLogitechHidPpHidppMsg *msg) { g_return_val_if_fail(msg != NULL, NULL); if (msg->report_id == HIDPP_REPORT_ID_SHORT) return "short"; if (msg->report_id == HIDPP_REPORT_ID_LONG) return "long"; if (msg->report_id == HIDPP_REPORT_ID_VERY_LONG) return "very-long"; return NULL; } gsize fu_logitech_hidpp_msg_get_payload_length(FuLogitechHidPpHidppMsg *msg) { if (msg->report_id == HIDPP_REPORT_ID_SHORT) return 0x07; if (msg->report_id == HIDPP_REPORT_ID_LONG) return 0x14; if (msg->report_id == 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 HIDPP_SUBID_SET_REGISTER: case HIDPP_SUBID_GET_REGISTER: case HIDPP_SUBID_SET_LONG_REGISTER: case HIDPP_SUBID_GET_LONG_REGISTER: case HIDPP_SUBID_SET_VERY_LONG_REGISTER: case HIDPP_SUBID_GET_VERY_LONG_REGISTER: if (msg->function_id == HIDPP_REGISTER_HIDPP_NOTIFICATIONS) return "hidpp-notifications"; if (msg->function_id == HIDPP_REGISTER_ENABLE_INDIVIDUAL_FEATURES) return "individual-features"; if (msg->function_id == HIDPP_REGISTER_BATTERY_STATUS) return "battery-status"; if (msg->function_id == HIDPP_REGISTER_BATTERY_MILEAGE) return "battery-mileage"; if (msg->function_id == HIDPP_REGISTER_PROFILE) return "profile"; if (msg->function_id == HIDPP_REGISTER_LED_STATUS) return "led-status"; if (msg->function_id == HIDPP_REGISTER_LED_INTENSITY) return "led-intensity"; if (msg->function_id == HIDPP_REGISTER_LED_COLOR) return "led-color"; if (msg->function_id == HIDPP_REGISTER_OPTICAL_SENSOR_SETTINGS) return "optical-sensor-settings"; if (msg->function_id == HIDPP_REGISTER_CURRENT_RESOLUTION) return "current-resolution"; if (msg->function_id == HIDPP_REGISTER_USB_REFRESH_RATE) return "usb-refresh-rate"; if (msg->function_id == HIDPP_REGISTER_GENERIC_MEMORY_MANAGEMENT) return "generic-memory-management"; if (msg->function_id == HIDPP_REGISTER_HOT_CONTROL) return "hot-control"; if (msg->function_id == HIDPP_REGISTER_READ_MEMORY) return "read-memory"; if (msg->function_id == HIDPP_REGISTER_DEVICE_CONNECTION_DISCONNECTION) return "device-connection-disconnection"; if (msg->function_id == HIDPP_REGISTER_PAIRING_INFORMATION) return "pairing-information"; if (msg->function_id == HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE) return "device-firmware-update-mode"; if (msg->function_id == HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION) return "device-firmware-information"; if (msg->function_id == BOLT_REGISTER_RECEIVER_FW_INFORMATION) return "receiver-fw-information"; break; default: break; } return NULL; } const gchar * fu_logitech_hidpp_msg_sub_id_to_string(FuLogitechHidPpHidppMsg *msg) { g_return_val_if_fail(msg != NULL, NULL); if (msg->sub_id == HIDPP_SUBID_VENDOR_SPECIFIC_KEYS) return "vendor-specific-keys"; if (msg->sub_id == HIDPP_SUBID_POWER_KEYS) return "power-keys"; if (msg->sub_id == HIDPP_SUBID_ROLLER) return "roller"; if (msg->sub_id == HIDPP_SUBID_MOUSE_EXTRA_BUTTONS) return "mouse-extra-buttons"; if (msg->sub_id == HIDPP_SUBID_BATTERY_CHARGING_LEVEL) return "battery-charging-level"; if (msg->sub_id == HIDPP_SUBID_USER_INTERFACE_EVENT) return "user-interface-event"; if (msg->sub_id == HIDPP_SUBID_F_LOCK_STATUS) return "f-lock-status"; if (msg->sub_id == HIDPP_SUBID_CALCULATOR_RESULT) return "calculator-result"; if (msg->sub_id == HIDPP_SUBID_MENU_NAVIGATE) return "menu-navigate"; if (msg->sub_id == HIDPP_SUBID_FN_KEY) return "fn-key"; if (msg->sub_id == HIDPP_SUBID_BATTERY_MILEAGE) return "battery-mileage"; if (msg->sub_id == HIDPP_SUBID_UART_RX) return "uart-rx"; if (msg->sub_id == HIDPP_SUBID_BACKLIGHT_DURATION_UPDATE) return "backlight-duration-update"; if (msg->sub_id == HIDPP_SUBID_DEVICE_DISCONNECTION) return "device-disconnection"; if (msg->sub_id == HIDPP_SUBID_DEVICE_CONNECTION) return "device-connection"; if (msg->sub_id == HIDPP_SUBID_DEVICE_DISCOVERY) return "device-discovery"; if (msg->sub_id == HIDPP_SUBID_PIN_CODE_REQUEST) return "pin-code-request"; if (msg->sub_id == HIDPP_SUBID_RECEIVER_WORKING_MODE) return "receiver-working-mode"; if (msg->sub_id == HIDPP_SUBID_ERROR_MESSAGE) return "error-message"; if (msg->sub_id == HIDPP_SUBID_RF_LINK_CHANGE) return "rf-link-change"; if (msg->sub_id == HIDPP_SUBID_HCI) return "hci"; if (msg->sub_id == HIDPP_SUBID_LINK_QUALITY) return "link-quality"; if (msg->sub_id == HIDPP_SUBID_DEVICE_LOCKING_CHANGED) return "device-locking-changed"; if (msg->sub_id == HIDPP_SUBID_WIRELESS_DEVICE_CHANGE) return "wireless-device-change"; if (msg->sub_id == HIDPP_SUBID_ACL) return "acl"; if (msg->sub_id == HIDPP_SUBID_VOIP_TELEPHONY_EVENT) return "voip-telephony-event"; if (msg->sub_id == HIDPP_SUBID_LED) return "led"; if (msg->sub_id == HIDPP_SUBID_GESTURE_AND_AIR) return "gesture-and-air"; if (msg->sub_id == HIDPP_SUBID_TOUCHPAD_MULTI_TOUCH) return "touchpad-multi-touch"; if (msg->sub_id == HIDPP_SUBID_TRACEABILITY) return "traceability"; if (msg->sub_id == HIDPP_SUBID_SET_REGISTER) return "set-register"; if (msg->sub_id == HIDPP_SUBID_GET_REGISTER) return "get-register"; if (msg->sub_id == HIDPP_SUBID_SET_LONG_REGISTER) return "set-long-register"; if (msg->sub_id == HIDPP_SUBID_GET_LONG_REGISTER) return "get-long-register"; if (msg->sub_id == HIDPP_SUBID_SET_VERY_LONG_REGISTER) return "set-very-long-register"; if (msg->sub_id == HIDPP_SUBID_GET_VERY_LONG_REGISTER) return "get-very-long-register"; if (msg->sub_id == HIDPP_SUBID_ERROR_MSG) return "error-msg"; if (msg->sub_id == HIDPP_SUBID_ERROR_MSG_20) return "error-msg-v2"; 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 != HIDPP_DEVICE_IDX_UNSET && msg2->device_id != HIDPP_DEVICE_IDX_UNSET) return FALSE; if (msg1->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID || msg2->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID) return TRUE; if (msg1->sub_id != msg2->sub_id) return FALSE; if (msg1->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID || msg2->flags & FU_UNIFYING_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 == 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 == 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_UNIFYING_HIDPP_MSG_SW_ID) return FALSE; return TRUE; } fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.h000066400000000000000000000037671420024370600246140ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_UNIFYING_HIDPP_MSG_FLAG_NONE, FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT = 1 << 0, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID = 1 << 1, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID = 1 << 2, FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SWID = 1 << 3, FU_UNIFYING_HIDPP_MSG_FLAG_RETRY_STUCK = 1 << 4, /*< private >*/ FU_UNIFYING_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_UNIFYING_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_dev_id_to_string(FuLogitechHidPpHidppMsg *msg); const gchar * fu_logitech_hidpp_msg_rpt_id_to_string(FuLogitechHidPpHidppMsg *msg); const gchar * fu_logitech_hidpp_msg_sub_id_to_string(FuLogitechHidPpHidppMsg *msg); const gchar * fu_logitech_hidpp_msg_fcn_id_to_string(FuLogitechHidPpHidppMsg *msg); fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.c000066400000000000000000000156551420024370600240220ustar00rootroot00000000000000/* * 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" static gchar * fu_logitech_hidpp_msg_to_string(FuLogitechHidPpHidppMsg *msg) { GString *str = g_string_new(NULL); const gchar *tmp; g_autoptr(GError) error = NULL; g_autoptr(GString) flags_str = g_string_new(NULL); g_return_val_if_fail(msg != NULL, NULL); if (msg->flags == FU_UNIFYING_HIDPP_MSG_FLAG_NONE) { g_string_append(flags_str, "none"); } else { if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT) g_string_append(flags_str, "longer-timeout,"); if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_SUB_ID) g_string_append(flags_str, "ignore-sub-id,"); if (msg->flags & FU_UNIFYING_HIDPP_MSG_FLAG_IGNORE_FNCT_ID) g_string_append(flags_str, "ignore-fnct-id,"); if (msg->flags & FU_UNIFYING_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_msg_rpt_id_to_string(msg)); tmp = fu_logitech_hidpp_msg_dev_id_to_string(msg); g_string_append_printf(str, "device-id: %02x [%s]\n", msg->device_id, tmp); g_string_append_printf(str, "sub-id: %02x [%s]\n", msg->sub_id, fu_logitech_hidpp_msg_sub_id_to_string(msg)); 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); } return g_string_free(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; /* only for HID++2.0 */ if (msg->hidpp_version >= 2.f) msg->function_id |= FU_UNIFYING_HIDPP_MSG_SW_ID; /* force long reports for BLE-direct devices */ if (msg->hidpp_version == FU_HIDPP_VERSION_BLE) { msg->report_id = HIDPP_REPORT_ID_LONG; len = 20; } /* detailed debugging */ if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { g_autofree gchar *str = fu_logitech_hidpp_msg_to_string(msg); fu_common_dump_raw(G_LOG_DOMAIN, "host->device", (guint8 *)msg, len); g_print("%s", str); } /* only use blocking IO when it will be a short timeout for reboot */ if ((msg->flags & FU_UNIFYING_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; 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 */ if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) fu_common_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; } /* detailed debugging */ if (g_getenv("FWUPD_LOGITECH_HIDPP_VERBOSE") != NULL) { g_autofree gchar *str = fu_logitech_hidpp_msg_to_string(msg); g_print("%s", str); } /* success */ return TRUE; } gboolean fu_logitech_hidpp_transfer(FuIOChannel *io_channel, FuLogitechHidPpHidppMsg *msg, GError **error) { guint timeout = FU_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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_UNIFYING_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h000066400000000000000000000143021420024370600240130ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #define HIDPP_DEVICE_IDX_WIRED 0x00 #define HIDPP_DEVICE_IDX_RECEIVER 0xFF #define HIDPP_DEVICE_IDX_BLE 0xFF #define HIDPP_DEVICE_IDX_UNSET 0x00 #define HIDPP_REPORT_NOTIFICATION 0x01 #define HIDPP_REPORT_ID_SHORT 0x10 #define HIDPP_REPORT_ID_LONG 0x11 #define HIDPP_REPORT_ID_VERY_LONG 0x12 #define HIDPP_SUBID_VENDOR_SPECIFIC_KEYS 0x03 #define HIDPP_SUBID_POWER_KEYS 0x04 #define HIDPP_SUBID_ROLLER 0x05 #define HIDPP_SUBID_MOUSE_EXTRA_BUTTONS 0x06 #define HIDPP_SUBID_BATTERY_CHARGING_LEVEL 0x07 #define HIDPP_SUBID_USER_INTERFACE_EVENT 0x08 #define HIDPP_SUBID_F_LOCK_STATUS 0x09 #define HIDPP_SUBID_CALCULATOR_RESULT 0x0A #define HIDPP_SUBID_MENU_NAVIGATE 0x0B #define HIDPP_SUBID_FN_KEY 0x0C #define HIDPP_SUBID_BATTERY_MILEAGE 0x0D #define HIDPP_SUBID_UART_RX 0x0E #define HIDPP_SUBID_BACKLIGHT_DURATION_UPDATE 0x17 #define HIDPP_SUBID_DEVICE_DISCONNECTION 0x40 #define HIDPP_SUBID_DEVICE_CONNECTION 0x41 #define HIDPP_SUBID_DEVICE_DISCOVERY 0x42 #define HIDPP_SUBID_PIN_CODE_REQUEST 0x43 #define HIDPP_SUBID_RECEIVER_WORKING_MODE 0x44 #define HIDPP_SUBID_ERROR_MESSAGE 0x45 #define HIDPP_SUBID_RF_LINK_CHANGE 0x46 #define HIDPP_SUBID_HCI 0x48 #define HIDPP_SUBID_LINK_QUALITY 0x49 #define HIDPP_SUBID_DEVICE_LOCKING_CHANGED 0x4a #define HIDPP_SUBID_WIRELESS_DEVICE_CHANGE 0x4B #define HIDPP_SUBID_ACL 0x51 #define HIDPP_SUBID_VOIP_TELEPHONY_EVENT 0x5B #define HIDPP_SUBID_LED 0x60 #define HIDPP_SUBID_GESTURE_AND_AIR 0x65 #define HIDPP_SUBID_TOUCHPAD_MULTI_TOUCH 0x66 #define HIDPP_SUBID_TRACEABILITY 0x78 #define HIDPP_SUBID_SET_REGISTER 0x80 #define HIDPP_SUBID_GET_REGISTER 0x81 #define HIDPP_SUBID_SET_LONG_REGISTER 0x82 #define HIDPP_SUBID_GET_LONG_REGISTER 0x83 #define HIDPP_SUBID_SET_VERY_LONG_REGISTER 0x84 #define HIDPP_SUBID_GET_VERY_LONG_REGISTER 0x85 #define HIDPP_SUBID_ERROR_MSG 0x8F #define HIDPP_SUBID_ERROR_MSG_20 0xFF #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 /* * HID++1.0 registers */ #define HIDPP_REGISTER_HIDPP_NOTIFICATIONS 0x00 #define HIDPP_REGISTER_ENABLE_INDIVIDUAL_FEATURES 0x01 #define HIDPP_REGISTER_BATTERY_STATUS 0x07 #define HIDPP_REGISTER_BATTERY_MILEAGE 0x0D #define HIDPP_REGISTER_PROFILE 0x0F #define HIDPP_REGISTER_LED_STATUS 0x51 #define HIDPP_REGISTER_LED_INTENSITY 0x54 #define HIDPP_REGISTER_LED_COLOR 0x57 #define HIDPP_REGISTER_OPTICAL_SENSOR_SETTINGS 0x61 #define HIDPP_REGISTER_CURRENT_RESOLUTION 0x63 #define HIDPP_REGISTER_USB_REFRESH_RATE 0x64 #define HIDPP_REGISTER_GENERIC_MEMORY_MANAGEMENT 0xA0 #define HIDPP_REGISTER_HOT_CONTROL 0xA1 #define HIDPP_REGISTER_READ_MEMORY 0xA2 #define HIDPP_REGISTER_DEVICE_CONNECTION_DISCONNECTION 0xB2 #define HIDPP_REGISTER_PAIRING_INFORMATION 0xB5 #define HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE 0xF0 #define HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION 0xF1 /* * 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 /* * HID++2.0 features */ #define HIDPP_FEATURE_ROOT 0x0000 #define HIDPP_FEATURE_I_FEATURE_SET 0x0001 #define HIDPP_FEATURE_I_FIRMWARE_INFO 0x0003 #define HIDPP_FEATURE_GET_DEVICE_NAME_TYPE 0x0005 #define HIDPP_FEATURE_DFU_CONTROL 0x00c1 #define HIDPP_FEATURE_DFU_CONTROL_SIGNED 0x00c2 #define HIDPP_FEATURE_DFU_CONTROL_BOLT 0x00c3 #define HIDPP_FEATURE_DFU 0x00d0 #define HIDPP_FEATURE_BATTERY_LEVEL_STATUS 0x1000 #define HIDPP_FEATURE_UNIFIED_BATTERY 0x1004 #define HIDPP_FEATURE_KBD_REPROGRAMMABLE_KEYS 0x1b00 #define HIDPP_FEATURE_SPECIAL_KEYS_BUTTONS 0x1b04 #define HIDPP_FEATURE_MOUSE_POINTER_BASIC 0x2200 #define HIDPP_FEATURE_ADJUSTABLE_DPI 0x2201 #define HIDPP_FEATURE_ADJUSTABLE_REPORT_RATE 0x8060 #define HIDPP_FEATURE_COLOR_LED_EFFECTS 0x8070 #define HIDPP_FEATURE_ONBOARD_PROFILES 0x8100 #define HIDPP_FEATURE_MOUSE_BUTTON_SPY 0x8110 #include "fu-logitech-hidpp-hidpp-msg.h" gboolean fu_logitech_hidpp_send(FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_receive(FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_transfer(FuIOChannel *self, FuLogitechHidPpHidppMsg *msg, GError **error); fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-radio.c000066400000000000000000000075711420024370600240120ustar00rootroot00000000000000/* * 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_common_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_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-radio.h000066400000000000000000000006351420024370600240110ustar00rootroot00000000000000/* * 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c000066400000000000000000000371061420024370600253320ustar00rootroot00000000000000/* * 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" 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 = HIDPP_REPORT_ID_LONG; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = 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_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self), msg, FU_UNIFYING_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_common_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 = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = 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); fu_device_inhibit(FU_DEVICE(child), "unreachable", "device is 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); fu_device_inhibit(radio, "unreachable", "device is 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; } 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("Can't query paired device name for slot %u", i); continue; } msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = 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 == HIDPP_REPORT_ID_SHORT) { switch (msg->sub_id) { case HIDPP_SUBID_DEVICE_CONNECTION: case HIDPP_SUBID_DEVICE_DISCONNECTION: case 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 HIDPP_SUBID_LINK_QUALITY: g_debug("ignoring link quality message"); break; case HIDPP_SUBID_ERROR_MSG: g_debug("ignoring link quality 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_msg_dev_id_to_string(msg), msg->sub_id, fu_logitech_hidpp_msg_sub_id_to_string(msg)); 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 = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = 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_autofree gchar *instance_id = NULL; g_autoptr(GString) radio_version = NULL; msg->report_id = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = 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_common_read_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_common_read_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_set_physical_id(FU_DEVICE(radio), fu_device_get_physical_id(device)); fu_device_set_logical_id(FU_DEVICE(radio), "Receiver_SoftDevice"); instance_id = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&ENT_05", fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)), fu_udev_device_get_model(FU_UDEV_DEVICE(device))); fu_device_add_guid(FU_DEVICE(radio), instance_id); if (!fu_common_read_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 */ g_usleep(200 * 1000); 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_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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h000066400000000000000000000007431420024370600253340ustar00rootroot00000000000000/* * 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) void fu_logitech_hidpp_runtime_bolt_poll_peripherals(FuDevice *device); fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-unifying.c000066400000000000000000000132661420024370600262230ustar00rootroot00000000000000/* * 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" 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 = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = HIDPP_SUBID_SET_REGISTER; msg->function_id = 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_UNIFYING_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self), msg, FU_UNIFYING_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 = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = HIDPP_SUBID_GET_REGISTER; msg->function_id = 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 dongle 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_logitech_hidpp_runtime_set_signed_firmware(self, TRUE); fu_device_add_protocol(device, "com.logitech.unifyingsigned"); } } if (!fu_logitech_hidpp_runtime_get_signed_firmware(self)) 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 */ g_usleep(200 * 1000); 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-unifying.h000066400000000000000000000006421420024370600262220ustar00rootroot00000000000000/* * 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c000066400000000000000000000225251420024370600243730ustar00rootroot00000000000000/* * 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" typedef struct { guint8 version_bl_major; gboolean signed_firmware; 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)) gboolean fu_logitech_hidpp_runtime_get_signed_firmware(FuLogitechHidPpRuntime *self) { FuLogitechHidPpRuntimePrivate *priv; g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), FALSE); priv = GET_PRIVATE(self); return priv->signed_firmware; } void fu_logitech_hidpp_runtime_set_signed_firmware(FuLogitechHidPpRuntime *self, gboolean signed_firmware) { FuLogitechHidPpRuntimePrivate *priv; g_return_if_fail(FU_IS_HIDPP_RUNTIME(self)); priv = GET_PRIVATE(self); priv->signed_firmware = signed_firmware; } 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; } void fu_logitech_hidpp_runtime_set_io_channel(FuLogitechHidPpRuntime *self, FuIOChannel *io_channel) { FuLogitechHidPpRuntimePrivate *priv; g_return_if_fail(FU_IS_HIDPP_RUNTIME(self)); priv = GET_PRIVATE(self); priv->io_channel = 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; } void fu_logitech_hidpp_runtime_set_version_bl_major(FuLogitechHidPpRuntime *self, guint8 version_bl_major) { FuLogitechHidPpRuntimePrivate *priv; g_return_if_fail(FU_IS_HIDPP_RUNTIME(self)); priv = GET_PRIVATE(self); priv->version_bl_major = version_bl_major; } static void fu_logitech_hidpp_runtime_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechHidPpRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidPpRuntimePrivate *priv = GET_PRIVATE(self); FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_parent_class)->to_string(device, idt, str); fu_common_string_append_kb(str, idt, "SignedFirmware", priv->signed_firmware); } 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 = HIDPP_REPORT_ID_SHORT; msg->device_id = HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = HIDPP_SUBID_SET_REGISTER; msg->function_id = 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 == HIDPP_REPORT_ID_SHORT) { switch (msg->sub_id) { case HIDPP_SUBID_DEVICE_CONNECTION: case HIDPP_SUBID_DEVICE_DISCONNECTION: case HIDPP_SUBID_DEVICE_LOCKING_CHANGED: g_debug("device connection event, do something"); break; case HIDPP_SUBID_LINK_QUALITY: g_debug("ignoring link quality message"); break; case HIDPP_SUBID_ERROR_MSG: g_debug("ignoring link quality 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); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); const gchar *devpath = g_udev_device_get_device_file(udev_device); /* open, but don't block */ priv->io_channel = fu_io_channel_new_file(devpath, error); if (priv->io_channel == NULL) return FALSE; /* 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; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_parent_class)->probe(device, error)) return FALSE; /* 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_UNIFYING_DEVICE_VID, (guint)FU_UNIFYING_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_UNIFYING_DEVICE_VID, (guint)FU_UNIFYING_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_UNIFYING_DEVICE_VID, (guint)FU_UNIFYING_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), "preferences-desktop-keyboard"); 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.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.h000066400000000000000000000021371420024370600243750ustar00rootroot00000000000000/* * 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); gboolean fu_logitech_hidpp_runtime_get_signed_firmware(FuLogitechHidPpRuntime *self); void fu_logitech_hidpp_runtime_set_signed_firmware(FuLogitechHidPpRuntime *self, gboolean signed_firmware); FuIOChannel * fu_logitech_hidpp_runtime_get_io_channel(FuLogitechHidPpRuntime *self); void fu_logitech_hidpp_runtime_set_io_channel(FuLogitechHidPpRuntime *self, FuIOChannel *io_channel); guint8 fu_logitech_hidpp_runtime_get_version_bl_major(FuLogitechHidPpRuntime *self); void fu_logitech_hidpp_runtime_set_version_bl_major(FuLogitechHidPpRuntime *self, guint8 version_bl_major); fwupd-1.7.5/plugins/logitech-hidpp/fu-logitech-hidpp-self-test.c000066400000000000000000000015761420024370600246210ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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) { 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.7.5/plugins/logitech-hidpp/fu-plugin-logitech-hidpp.c000066400000000000000000000030731420024370600242030ustar00rootroot00000000000000/* * 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-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-runtime-bolt.h" #include "fu-logitech-hidpp-runtime-unifying.h" static gboolean fu_plugin_logitech_hidpp_startup(FuPlugin *plugin, GError **error) { /* 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; } return TRUE; } static void fu_plugin_logitech_hidpp_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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_UNIFYING_BOOTLOADER_NORDIC); fu_plugin_add_device_gtype(plugin, FU_TYPE_UNIFYING_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); fu_context_add_quirk_key(ctx, "LogitechHidppModelId"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_logitech_hidpp_init; vfuncs->startup = fu_plugin_logitech_hidpp_startup; } fwupd-1.7.5/plugins/logitech-hidpp/logitech-hidpp.quirk000066400000000000000000000063361420024370600232150ustar00rootroot00000000000000# 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 # 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 [HIDRAW\VEN_046D&MOD_B37000000000] Plugin = logitech_hidpp GType = FuLogitechHidPpDevice Flags = add-radio,no-request-required InstallDuration = 270 [HIDRAW\VEN_046D&DEV_B370] Plugin = logitech_hidpp GType = FuLogitechHidPpDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 fwupd-1.7.5/plugins/logitech-hidpp/meson.build000066400000000000000000000026331420024370600213760ustar00rootroot00000000000000if get_option('gudev') and get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechHidPp"'] install_data([ 'logitech-hidpp.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_logitech_hidpp', fu_hash, sources : [ 'fu-plugin-logitech-hidpp.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'logitech-hidpp-self-test', fu_hash, sources : [ 'fu-logitech-hidpp-self-test.c', 'fu-logitech-hidpp-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, ) test('logitech-hidpp-self-test', e) endif endif fwupd-1.7.5/plugins/meson.build000066400000000000000000000027611420024370600165000ustar00rootroot00000000000000subdir('acpi-dmar') subdir('acpi-facp') subdir('acpi-phat') subdir('amt') subdir('analogix') subdir('ata') subdir('bcm57xx') subdir('bios') subdir('ccgx') subdir('cfu') subdir('colorhug') subdir('cpu') subdir('cros-ec') subdir('dell') subdir('dell-dock') subdir('dell-esrt') subdir('dfu') subdir('dfu-csr') subdir('ebitdo') subdir('elantp') subdir('elanfp') subdir('emmc') subdir('ep963x') subdir('fastboot') subdir('flashrom') subdir('fresco-pd') subdir('goodix-moc') subdir('hailuck') subdir('intel-spi') subdir('iommu') subdir('jabra') subdir('lenovo-thinklmi') subdir('linux-lockdown') subdir('linux-sleep') subdir('linux-swap') subdir('linux-tainted') subdir('logind') subdir('logitech-hidpp') subdir('logitech-bulkcontroller') subdir('modem-manager') subdir('msr') subdir('mtd') subdir('nitrokey') subdir('nordic-hid') subdir('nvme') subdir('optionrom') subdir('parade-lspcon') subdir('pci-bcr') subdir('pci-mei') subdir('pixart-rf') subdir('platform-integrity') subdir('powerd') subdir('realtek-mst') subdir('redfish') subdir('rts54hid') subdir('rts54hub') subdir('steelseries') subdir('superio') subdir('synaptics-cape') subdir('synaptics-cxaudio') subdir('synaptics-mst') subdir('synaptics-prometheus') subdir('synaptics-rmi') subdir('system76-launch') subdir('test') subdir('thelio-io') subdir('thunderbolt') subdir('tpm') subdir('uefi-capsule') subdir('uefi-dbx') subdir('uefi-pk') subdir('uefi-recovery') subdir('uf2') subdir('upower') subdir('usi-dock') subdir('vli') subdir('wacom-raw') subdir('wacom-usb') fwupd-1.7.5/plugins/modem-manager/000077500000000000000000000000001420024370600170415ustar00rootroot00000000000000fwupd-1.7.5/plugins/modem-manager/README.md000066400000000000000000000056031420024370600203240ustar00rootroot00000000000000# 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` * `USB\VID_413C&PID_81D7&REV_0318` * `USB\VID_413C&PID_81D7` * `USB\VID_413C` * `PCI\VID_105B&PID_E0AB&REV_0000&CARRIER_VODAFONE` * `PCI\VID_105B&PID_E0AB&REV_0000` * `PCI\VID_105B&PID_E0AB` * `PCI\VID_105B` * `PCI\VID_1EAC&PID_1001` * `PCI\VID_1EAC&PID_1002` * `PCI\VID_1EAC` ## 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 ## 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 emergencly 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`. fwupd-1.7.5/plugins/modem-manager/fu-firehose-updater.c000066400000000000000000000600411420024370600230640ustar00rootroot00000000000000/* * Copyright (C) 2020 Aleksander Morgado * Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd. * * 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; 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; if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") == NULL) return; msg_data = (const gchar *)g_bytes_get_data(msg, &msg_size); if (msg_size > G_MAXINT) return; msg_strsafe = fu_common_strsafe(msg_data, msg_size); g_debug("%s: %.*s", action, (gint)msg_size, msg_strsafe); } static gboolean validate_program_action(XbNode *program, FuArchive *archive, GError **error) { const gchar *filename_attr; GBytes *file; gsize file_size; guint64 computed_num_partition_sectors; guint64 num_partition_sectors; guint64 sector_size_in_bytes; 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) { /* 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..."); self->io_channel = fu_io_channel_new_file(self->port, error); return (self->io_channel != NULL); } gboolean fu_firehose_updater_close(FuFirehoseUpdater *self, GError **error) { 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_byte_array_free_to_bytes(take_cmd_bytearray); fu_firehose_updater_log_message("writing", cmd_bytes); if (!fu_io_channel_write_bytes(self->io_channel, 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_io_channel_read_bytes(self->io_channel, -1, 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; rsp_bytes = fu_io_channel_read_bytes( self->io_channel, -1, (i == 0 ? INITIALIZE_INITIAL_TIMEOUT_MS : INITIALIZE_TIMEOUT_MS), 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, guint payload_size, guint sector_size, GError **error) { g_autoptr(GPtrArray) chunks = NULL; FuChunk *chk; chunks = fu_chunk_array_new_from_bytes(program_file, 0, 0, payload_size); /* last block needs to be padded to the next payload_size, * so that we always send full sectors */ chk = g_ptr_array_index(chunks, chunks->len - 1); if (fu_chunk_get_data_sz(chk) != payload_size) { g_autoptr(GBytes) padded_bytes = NULL; g_autofree guint8 *padded_block = g_malloc0(payload_size); g_return_val_if_fail(padded_block != NULL, FALSE); memcpy(padded_block, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); padded_bytes = g_bytes_new(padded_block, payload_size); fu_chunk_set_bytes(chk, padded_bytes); g_return_val_if_fail(fu_chunk_get_data_sz(chk) == payload_size, FALSE); } for (guint i = 0; i < chunks->len; i++) { chk = g_ptr_array_index(chunks, i); /* log only in blocks of 250 plus first/last */ if (i == 0 || i == (chunks->len - 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, chunks->len, program_filename); if (!fu_io_channel_write_bytes(self->io_channel, 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, chunks->len, 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 '%s' command: " "failed to get %s", program_filename, 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 '%s' command: " "failed to get %s", program_filename, 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; g_debug("sending program file '%s' (%zu bytes)", program_filename, 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); #if LIBXMLB_CHECK_VERSION(0, 2, 2) cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, error); #else cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_NONE, error); #endif 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_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) { FuFirehoseUpdater *self = g_object_new(FU_TYPE_FIREHOSE_UPDATER, NULL); self->port = g_strdup(port); return self; } fwupd-1.7.5/plugins/modem-manager/fu-firehose-updater.h000066400000000000000000000016651420024370600231000ustar00rootroot00000000000000/* * Copyright (C) 2020 Aleksander Morgado * Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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); 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.7.5/plugins/modem-manager/fu-mbim-qdu-updater.c000066400000000000000000000317661420024370600230070ustar00rootroot00000000000000/* * Copyright (C) 2021 Jarvis Jiang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-mbim-qdu-updater.h" #include "fu-mm-utils.h" #if MBIM_CHECK_VERSION(1, 25, 3) #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("error: 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("error: 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("error: 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; GPtrArray *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("error: operation failed: %s", ctx->error->message); g_ptr_array_unref(ctx->chunks); g_main_loop_quit(ctx->mainloop); return; } if (!mbim_message_qdu_file_write_response_parse(response, &ctx->error)) { g_debug("error: couldn't parse response message: %s", ctx->error->message); g_ptr_array_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)ctx->chunks->len); if (ctx->chunk_sent < ctx->chunks->len) { FuChunk *chk = g_ptr_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, 10, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_file_write_ready, ctx); return; } g_ptr_array_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; guint32 total_sending_numbers = 0; 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("error: 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("error: couldn't parse response message: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } total_sending_numbers = g_bytes_get_size(ctx->blob) / out_max_transfer_size; if (g_bytes_get_size(ctx->blob) % out_max_transfer_size != 0) total_sending_numbers++; ctx->chunks = fu_chunk_array_new_from_bytes(ctx->blob, 0x00, /* start addr */ 0x00, /* page_sz */ out_max_transfer_size); chk = g_ptr_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("error: 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("error: 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(GPtrArray) 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); } 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 *path) { FuMbimQduUpdater *self = g_object_new(FU_TYPE_MBIM_QDU_UPDATER, NULL); self->mbim_port = g_strdup(path); return self; } #endif /* MBIM_CHECK_VERSION(1,25,3) */ fwupd-1.7.5/plugins/modem-manager/fu-mbim-qdu-updater.h000066400000000000000000000015711420024370600230030ustar00rootroot00000000000000/* * Copyright (C) 2021 Jarvis Jiang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-mm-device.h" #if MBIM_CHECK_VERSION(1, 25, 3) #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); #endif /* MBIM_CHECK_VERSION(1,25,3) */ fwupd-1.7.5/plugins/modem-manager/fu-mm-device.c000066400000000000000000001510271420024370600214710ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-mbim-qdu-updater.h" #include "fu-mm-device.h" #include "fu-mm-utils.h" #include "fu-qmi-pdc-updater.h" #if MM_CHECK_VERSION(1, 17, 2) #include "fu-firehose-updater.h" #endif /* 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 120000 /* ms */ /* Amount of time for the modem to get firmware version */ #define MAX_WAIT_TIME_SECS 150 /* s */ /** * 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) 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; #if MBIM_CHECK_VERSION(1, 25, 3) FuMbimQduUpdater *mbim_qdu_updater; #endif /* MBIM_CHECK_VERSION(1,25,3) */ /* firehose update handling */ gchar *port_qcdm; gchar *port_edl; #if MM_CHECK_VERSION(1, 17, 2) FuFirehoseUpdater *firehose_updater; #endif /* 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_common_string_append_kv(str, idt, "AtPort", self->port_at); if (self->port_qmi != NULL) fu_common_string_append_kv(str, idt, "QmiPort", self->port_qmi); if (self->port_mbim != NULL) fu_common_string_append_kv(str, idt, "MbimPort", self->port_mbim); if (self->port_qcdm != NULL) fu_common_string_append_kv(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; } const gchar * fu_mm_device_get_detach_fastboot_at(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), NULL); return device->detach_fastboot_at; } gint fu_mm_device_get_port_at_ifnum(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), -1); return device->port_at_ifnum; } gint fu_mm_device_get_port_qmi_ifnum(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), -1); return device->port_qmi_ifnum; } gint fu_mm_device_get_port_mbim_ifnum(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), -1); return device->port_mbim_ifnum; } 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, #if MM_CHECK_VERSION(1, 17, 1) MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU, #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1, 17, 2) MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE, #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_debug("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 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 MM_CHECK_VERSION(1, 17, 1) 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"); } #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1, 17, 2) 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) { self->port_qcdm = g_strdup_printf("/dev/%s", ports[i].name); break; } } fu_device_add_protocol(device, "com.qualcomm.firehose"); } #endif 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; } #if MM_CHECK_VERSION(1, 17, 1) /* 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; } #endif /* MM_CHECK_VERSION(1,17,1) */ #if MM_CHECK_VERSION(1, 17, 2) /* a qcdm port is required for firehose */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) && (self->port_qcdm == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QCDM port"); return FALSE; } #endif 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)); fu_device_set_version(device, version); for (guint i = 0; device_ids[i] != NULL; i++) fu_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); } } } 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); } else { return fu_mm_device_probe_udev(device, error); } } #if MM_CHECK_VERSION(1, 17, 2) 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); if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_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; } if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_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; } #endif /* MM_CHECK_VERSION(1,17,2) */ static gboolean fu_mm_device_at_cmd(FuMmDevice *self, const gchar *cmd, gboolean has_response, GError **error) { 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", cmd); /* command */ at_req = g_bytes_new(cmd_cr, strlen(cmd_cr)); if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_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: ", cmd); return FALSE; } /* AT command has no response, return TRUE */ if (!has_response) { g_debug("No response expected for AT command: '%s', assuming succeed", 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: ", cmd); return FALSE; } if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) fu_common_dump_bytes(G_LOG_DOMAIN, "read", at_res); 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", 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", cmd, tmp); return FALSE; } /* set firmware branch if returned */ if (self->branch_at != NULL && g_strcmp0(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_debug("Firmware branch reported as '%s'", parts[j]); break; } } } return TRUE; } 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; } #if MM_CHECK_VERSION(1, 17, 2) static gboolean fu_mm_device_find_edl_port(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); for (guint i = 0; i < 30; i++) { if (fu_mm_utils_find_device_file(fu_device_get_physical_id(FU_DEVICE(self)), "wwan", &self->port_edl, NULL)) return TRUE; g_usleep(250 * 1000); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't find EDL port"); return FALSE; } static gboolean fu_mm_device_qcdm_switch_to_edl(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); static const guint8 emergency_download[] = {0x4b, 0x65, 0x01, 0x00, 0x54, 0x0f, 0x7e}; /* trigger emergency download mode, up to 30s retrying until the QCDM * port goes away; this takes us to the EDL (embedded downloader) execution * environment */ for (guint i = 0; i < 30; i++) { g_autoptr(GError) error_local = NULL; g_autoptr(FuDeviceLocker) locker = NULL; if (i > 0) g_usleep(G_USEC_PER_SEC); 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 (i > 0 && g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) return fu_mm_device_find_edl_port(device, error); g_debug("couldn't open QCDM port to switch to EDL mode: %s", error_local->message); break; } if (!fu_mm_device_qcdm_cmd(self, emergency_download, G_N_ELEMENTS(emergency_download), &error_local)) { g_debug("couldn't send QCDM command to switch to EDL mode: %s", error_local->message); break; } } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't switch device to embedded downloader execution environment"); return FALSE; } #endif /* MM_CHECK_VERSION(1,17,2) */ 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; } #if MM_CHECK_VERSION(1, 17, 1) && MBIM_CHECK_VERSION(1, 25, 3) 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_write_firmware_mbim_qdu(FuDevice *device, GBytes *fw, FuProgress *progress, GError **error) { GBytes *data; XbNode *part = NULL; const gchar *filename = NULL; const gchar *csum; FuMmDevice *self = FU_MM_DEVICE(device); g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *autosuspend_delay_filename = NULL; g_autofree gchar *csum_actual = NULL; g_autofree gchar *version = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = 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 = fu_archive_lookup_by_fn(archive, "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; 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 = fu_archive_lookup_by_fn(archive, filename, error); if (data == NULL) return FALSE; csum_actual = g_compute_checksum_for_bytes(G_CHECKSUM_MD5, data); 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; } else { g_debug("[%s] MD5 matched", filename); } /* 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); if (!fu_mm_device_writeln(autosuspend_delay_filename, "10000", error)) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); fu_mbim_qdu_updater_write(self->mbim_qdu_updater, filename, data, device, progress, error); 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; } #endif /* MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) */ #if MM_CHECK_VERSION(1, 17, 2) static gboolean fu_mm_device_firehose_open(FuMmDevice *self, GError **error) { self->firehose_updater = fu_firehose_updater_new(self->port_edl); 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); } 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_common_get_path(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_common_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, FuArchive *archive, GError **error) { g_autofree gchar *qcom_fw_dir = NULL; g_autofree gchar *firehose_file_path = NULL; g_autoptr(GBytes) firehose_prog = NULL; /* lookup firehose-prog bootloader */ firehose_prog = fu_archive_lookup_by_fn(archive, "firehose-prog.mbn", error); if (firehose_prog == NULL) return FALSE; qcom_fw_dir = g_build_filename(self->firmware_path, "qcom", NULL); if (!fu_common_mkdir_parent(qcom_fw_dir, error)) return FALSE; firehose_file_path = g_build_filename(qcom_fw_dir, "prog_firehose_sdx24.mbn", NULL); if (!fu_common_set_contents_bytes(firehose_file_path, firehose_prog, error)) return FALSE; return TRUE; } static gboolean fu_mm_prepare_firmware_search_path(FuMmDevice *self, GError **error) { self->restore_firmware_path = fu_common_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_common_set_firmware_search_path(self->restore_firmware_path, error); return fu_common_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); GBytes *firehose_rawprogram; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(XbSilo) firehose_rawprogram_silo = 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* decompress entire archive ahead of time */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; fu_progress_step_done(progress); /* 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 switch to the EDL mode over QCDM port require * firehose binary to be present in the firmware-loader search path. */ if (!fu_mm_copy_firehose_prog(self, archive, error)) return FALSE; /* switch to embedded downloader (EDL) execution environment */ if (!fu_mm_device_qcdm_switch_to_edl(FU_DEVICE(self), error)) 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; } 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; } #endif /* MM_CHECK_VERSION(1,17,2) */ 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); #if MM_CHECK_VERSION(1, 17, 1) && MBIM_CHECK_VERSION(1, 25, 3) /* 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); #endif /* MM_CHECK_VERSION(1,17,1) && MBIM_CHECK_VERSION(1,25,3) */ #if MM_CHECK_VERSION(1, 17, 2) /* firehose operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) return fu_mm_device_write_firmware_firehose(device, fw, progress, error); #endif 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; } /* 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_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; /* 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; /* firmware branch AT command may fail if not implemented, * clear error if not supported */ if (self->branch_at != NULL) { g_autoptr(GError) error_branch = NULL; if (!fu_mm_device_at_cmd(self, self->branch_at, TRUE, &error_branch)) g_debug("unable to get firmware branch: %s", error_branch->message); } /* success */ return TRUE; } static gboolean fu_mm_device_setup(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); if (self->port_at != NULL) { if (!fu_mm_device_setup_branch_at(self, error)) return FALSE; } if (fu_device_get_branch(device) != NULL) g_debug("using firmware branch: %s", fu_device_get_branch(device)); else g_debug("using firmware branch: default"); return TRUE; } 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_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), "network-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"); } static void fu_mm_device_finalize(GObject *object) { FuMmDevice *self = FU_MM_DEVICE(object); if (self->attach_idle) g_source_remove(self->attach_idle); if (self->qmi_pdc_active_id) g_array_unref(self->qmi_pdc_active_id); 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_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->set_progress = fu_mm_device_set_progress; /** * 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; } FuPluginMmInhibitedDeviceInfo * fu_plugin_mm_inhibited_device_info_new(FuMmDevice *device) { FuPluginMmInhibitedDeviceInfo *info; info = g_new0(FuPluginMmInhibitedDeviceInfo, 1); info->physical_id = g_strdup(fu_device_get_physical_id(FU_DEVICE(device))); info->vendor = g_strdup(fu_device_get_vendor(FU_DEVICE(device))); info->name = g_strdup(fu_device_get_name(FU_DEVICE(device))); info->version = g_strdup(fu_device_get_version(FU_DEVICE(device))); info->guids = fu_device_get_guids(FU_DEVICE(device)); info->update_methods = fu_mm_device_get_update_methods(device); info->detach_fastboot_at = g_strdup(fu_mm_device_get_detach_fastboot_at(device)); info->port_at_ifnum = fu_mm_device_get_port_at_ifnum(device); info->port_qmi_ifnum = fu_mm_device_get_port_qmi_ifnum(device); info->port_mbim_ifnum = fu_mm_device_get_port_mbim_ifnum(device); info->inhibited_uid = g_strdup(fu_mm_device_get_inhibition_uid(device)); return info; } void fu_plugin_mm_inhibited_device_info_free(FuPluginMmInhibitedDeviceInfo *info) { g_free(info->inhibited_uid); g_free(info->physical_id); g_free(info->vendor); g_free(info->name); g_free(info->version); if (info->guids) g_ptr_array_unref(info->guids); g_free(info->detach_fastboot_at); g_free(info); } FuMmDevice * fu_mm_device_udev_new(FuContext *ctx, MMManager *manager, FuPluginMmInhibitedDeviceInfo *info) { FuMmDevice *self = g_object_new(FU_TYPE_MM_DEVICE, "context", ctx, NULL); g_debug("creating udev-based mm device at %s", info->physical_id); self->manager = g_object_ref(manager); fu_device_set_physical_id(FU_DEVICE(self), info->physical_id); fu_device_set_vendor(FU_DEVICE(self), info->vendor); fu_device_set_name(FU_DEVICE(self), info->name); fu_device_set_version(FU_DEVICE(self), info->version); self->update_methods = info->update_methods; self->detach_fastboot_at = g_strdup(info->detach_fastboot_at); self->port_at_ifnum = info->port_at_ifnum; self->port_qmi_ifnum = info->port_qmi_ifnum; for (guint i = 0; i < info->guids->len; i++) fu_device_add_guid(FU_DEVICE(self), g_ptr_array_index(info->guids, i)); 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.7.5/plugins/modem-manager/fu-mm-device.h000066400000000000000000000033551420024370600214760ustar00rootroot00000000000000/* * 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 #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); const gchar * fu_mm_device_get_inhibition_uid(FuMmDevice *device); const gchar * fu_mm_device_get_detach_fastboot_at(FuMmDevice *device); gint fu_mm_device_get_port_at_ifnum(FuMmDevice *device); gint fu_mm_device_get_port_qmi_ifnum(FuMmDevice *device); gint fu_mm_device_get_port_mbim_ifnum(FuMmDevice *device); MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods(FuMmDevice *device); /* support for udev-based devices */ typedef struct { gchar *inhibited_uid; gchar *physical_id; gchar *vendor; gchar *name; gchar *version; GPtrArray *guids; MMModemFirmwareUpdateMethod update_methods; gchar *detach_fastboot_at; gint port_at_ifnum; gint port_qmi_ifnum; gint port_mbim_ifnum; } FuPluginMmInhibitedDeviceInfo; FuPluginMmInhibitedDeviceInfo * fu_plugin_mm_inhibited_device_info_new(FuMmDevice *device); void fu_plugin_mm_inhibited_device_info_free(FuPluginMmInhibitedDeviceInfo *info); G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuPluginMmInhibitedDeviceInfo, fu_plugin_mm_inhibited_device_info_free); FuMmDevice * fu_mm_device_udev_new(FuContext *ctx, MMManager *manager, FuPluginMmInhibitedDeviceInfo *info); void fu_mm_device_udev_add_port(FuMmDevice *device, const gchar *subsystem, const gchar *path, gint ifnum); #endif /* __FU_MM_DEVICE_H */ fwupd-1.7.5/plugins/modem-manager/fu-mm-utils.c000066400000000000000000000124311420024370600213650ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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.7.5/plugins/modem-manager/fu-mm-utils.h000066400000000000000000000013531420024370600213730ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_MM_UTILS_H #define __FU_MM_UTILS_H #include "config.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.7.5/plugins/modem-manager/fu-plugin-modem-manager.c000066400000000000000000000323461420024370600236320ustar00rootroot00000000000000 /* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 */ struct FuPluginData { MMManager *manager; gboolean manager_ready; GUdevClient *udev_client; 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 */ FuPluginMmInhibitedDeviceInfo *inhibited; }; static void fu_plugin_mm_udev_device_removed(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); FuMmDevice *dev; if (priv->inhibited == NULL) return; dev = fu_plugin_cache_lookup(plugin, priv->inhibited->physical_id); if (dev == NULL) return; /* once the first port is gone, consider device is gone */ fu_plugin_cache_remove(plugin, priv->inhibited->physical_id); fu_plugin_device_remove(plugin, FU_DEVICE(dev)); /* no need to wait for more ports, cancel that right away */ if (priv->udev_timeout_id != 0) { g_source_remove(priv->udev_timeout_id); priv->udev_timeout_id = 0; } } static void fu_plugin_mm_uninhibit_device(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FuPluginMmInhibitedDeviceInfo) info = NULL; g_clear_object(&priv->udev_client); /* get the device removed from the plugin cache before uninhibiting */ fu_plugin_mm_udev_device_removed(plugin); info = g_steal_pointer(&priv->inhibited); if ((priv->manager != NULL) && (info != NULL)) { g_debug("uninhibit modemmanager device with uid %s", info->inhibited_uid); mm_manager_uninhibit_device_sync(priv->manager, info->inhibited_uid, NULL, NULL); } } static gboolean fu_plugin_mm_udev_device_ports_timeout(gpointer user_data) { FuPlugin *plugin = user_data; FuPluginData *priv = fu_plugin_get_data(plugin); FuMmDevice *dev; g_autoptr(GError) error = NULL; g_return_val_if_fail(priv->inhibited != NULL, G_SOURCE_REMOVE); priv->udev_timeout_id = 0; dev = fu_plugin_cache_lookup(plugin, priv->inhibited->physical_id); if (dev != NULL) { if (!fu_device_probe(FU_DEVICE(dev), &error)) { g_warning("failed to probe MM device: %s", error->message); } else { fu_plugin_device_add(plugin, FU_DEVICE(dev)); } } return G_SOURCE_REMOVE; } static void fu_plugin_mm_udev_device_ports_timeout_reset(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); g_return_if_fail(priv->inhibited != NULL); if (priv->udev_timeout_id != 0) g_source_remove(priv->udev_timeout_id); priv->udev_timeout_id = g_timeout_add_seconds(FU_MM_UDEV_DEVICE_PORTS_TIMEOUT, fu_plugin_mm_udev_device_ports_timeout, plugin); } static void fu_plugin_mm_udev_device_port_added(FuPlugin *plugin, const gchar *subsystem, const gchar *path, gint ifnum) { FuPluginData *priv = fu_plugin_get_data(plugin); FuMmDevice *existing; g_autoptr(FuMmDevice) dev = NULL; g_return_if_fail(priv->inhibited != NULL); existing = fu_plugin_cache_lookup(plugin, priv->inhibited->physical_id); if (existing != NULL) { /* add port to existing device */ fu_mm_device_udev_add_port(existing, subsystem, path, ifnum); fu_plugin_mm_udev_device_ports_timeout_reset(plugin); return; } /* device is being created, update is complete, uninhibit */ fu_plugin_mm_uninhibit_device(plugin); /* create device and add to cache */ dev = fu_mm_device_udev_new(fu_plugin_get_context(plugin), priv->manager, priv->inhibited); fu_mm_device_udev_add_port(dev, subsystem, path, ifnum); fu_plugin_cache_add(plugin, priv->inhibited->physical_id, dev); /* wait a bit before probing, in case more ports get added */ fu_plugin_mm_udev_device_ports_timeout_reset(plugin); } static gboolean fu_plugin_mm_udev_uevent_cb(GUdevClient *udev, const gchar *action, GUdevDevice *device, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuPluginData *priv = fu_plugin_get_data(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 || priv->inhibited == 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, priv->inhibited->physical_id) != 0) return TRUE; path = g_strdup_printf("/dev/%s", name); if ((g_str_equal(action, "add")) || (g_str_equal(action, "change"))) { g_debug("added port to inhibited modem: %s (ifnum %d)", path, ifnum); fu_plugin_mm_udev_device_port_added(plugin, subsystem, path, ifnum); } else if (g_str_equal(action, "remove")) { g_debug("removed port from inhibited modem: %s", path); fu_plugin_mm_udev_device_removed(plugin); } return TRUE; } static gboolean fu_plugin_mm_inhibit_device(FuPlugin *plugin, FuDevice *device, GError **error) { static const gchar *subsystems[] = {"tty", "usbmisc", "wwan", NULL}; FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FuPluginMmInhibitedDeviceInfo) info = NULL; fu_plugin_mm_uninhibit_device(plugin); info = fu_plugin_mm_inhibited_device_info_new(FU_MM_DEVICE(device)); g_debug("inhibit modemmanager device with uid %s", info->inhibited_uid); if (!mm_manager_inhibit_device_sync(priv->manager, info->inhibited_uid, NULL, error)) return FALSE; /* setup inhibited device info */ priv->inhibited = g_steal_pointer(&info); /* 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) { priv->udev_client = g_udev_client_new(subsystems); g_signal_connect(G_UDEV_CLIENT(priv->udev_client), "uevent", G_CALLBACK(fu_plugin_mm_udev_uevent_cb), plugin); } return TRUE; } static void fu_plugin_mm_device_add(FuPlugin *plugin, MMObject *modem) { FuPluginData *priv = fu_plugin_get_data(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), priv->manager, modem); if (!fu_device_setup(FU_DEVICE(dev), &error)) { g_warning("failed to probe MM device: %s", error->message); return; } fu_plugin_device_add(plugin, FU_DEVICE(dev)); fu_plugin_cache_add(plugin, object_path, dev); } static void fu_plugin_mm_device_added_cb(MMManager *manager, MMObject *modem, FuPlugin *plugin) { fu_plugin_mm_device_add(plugin, modem); } static void fu_plugin_mm_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); if (dev == NULL) return; g_debug("removed modem: %s", mm_object_get_path(modem)); #if MM_CHECK_VERSION(1, 17, 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 */ if (!(fu_mm_device_get_update_methods(FU_MM_DEVICE(dev)) & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU)) { fu_plugin_cache_remove(plugin, object_path); fu_plugin_device_remove(plugin, FU_DEVICE(dev)); } #else fu_plugin_cache_remove(plugin, object_path); fu_plugin_device_remove(plugin, FU_DEVICE(dev)); #endif /* MM_CHECK_VERSION(1,17,1) */ } static void fu_plugin_mm_teardown_manager(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); if (priv->manager_ready) { g_debug("ModemManager no longer available"); g_signal_handlers_disconnect_by_func(priv->manager, G_CALLBACK(fu_plugin_mm_device_added_cb), plugin); g_signal_handlers_disconnect_by_func(priv->manager, G_CALLBACK(fu_plugin_mm_device_removed_cb), plugin); priv->manager_ready = FALSE; } } static void fu_plugin_mm_setup_manager(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); const gchar *version = mm_manager_get_version(priv->manager); GList *list; if (fu_common_vercmp_full(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_debug("ModemManager %s is available", version); g_signal_connect(G_DBUS_OBJECT_MANAGER(priv->manager), "object-added", G_CALLBACK(fu_plugin_mm_device_added_cb), plugin); g_signal_connect(G_DBUS_OBJECT_MANAGER(priv->manager), "object-removed", G_CALLBACK(fu_plugin_mm_device_removed_cb), plugin); list = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(priv->manager)); for (GList *l = list; l != NULL; l = g_list_next(l)) { MMObject *modem = MM_OBJECT(l->data); fu_plugin_mm_device_add(plugin, modem); g_object_unref(modem); } g_list_free(list); priv->manager_ready = TRUE; } static void fu_plugin_mm_name_owner_updated(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autofree gchar *name_owner = NULL; name_owner = g_dbus_object_manager_client_get_name_owner( G_DBUS_OBJECT_MANAGER_CLIENT(priv->manager)); if (name_owner != NULL) fu_plugin_mm_setup_manager(plugin); else fu_plugin_mm_teardown_manager(plugin); } static gboolean fu_plugin_mm_coldplug(FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); g_signal_connect_swapped(MM_MANAGER(priv->manager), "notify::name-owner", G_CALLBACK(fu_plugin_mm_name_owner_updated), plugin); fu_plugin_mm_name_owner_updated(plugin); return TRUE; } static gboolean fu_plugin_mm_startup(FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(GDBusConnection) connection = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; priv->manager = mm_manager_new_sync(connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, NULL, error); if (priv->manager == NULL) return FALSE; return TRUE; } static void fu_plugin_mm_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_mm_destroy(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); fu_plugin_mm_uninhibit_device(plugin); if (priv->udev_timeout_id) g_source_remove(priv->udev_timeout_id); if (priv->udev_client) g_object_unref(priv->udev_client); if (priv->manager != NULL) g_object_unref(priv->manager); } static gboolean fu_plugin_mm_detach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuPluginData *priv = fu_plugin_get_data(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 (priv->inhibited == NULL) { if (!fu_plugin_mm_inhibit_device(plugin, device, error)) return FALSE; } /* reset */ if (!fu_device_detach_full(device, progress, error)) { fu_plugin_mm_uninhibit_device(plugin); return FALSE; } /* note: wait for replug set by device if it really needs it */ return TRUE; } static void fu_plugin_mm_device_attach_finished(gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); fu_plugin_mm_uninhibit_device(plugin); } static gboolean fu_plugin_mm_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_plugin_mm_device_attach_finished), plugin); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_mm_init; vfuncs->destroy = fu_plugin_mm_destroy; vfuncs->startup = fu_plugin_mm_startup; vfuncs->coldplug = fu_plugin_mm_coldplug; vfuncs->attach = fu_plugin_mm_attach; vfuncs->detach = fu_plugin_mm_detach; } fwupd-1.7.5/plugins/modem-manager/fu-qmi-pdc-updater.c000066400000000000000000000525421420024370600226210ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; #if !QMI_CHECK_VERSION(1, 26, 0) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcLoadConfigInput, qmi_message_pdc_load_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcLoadConfigOutput, qmi_message_pdc_load_config_output_unref) #pragma clang diagnostic pop #endif 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; #if !QMI_CHECK_VERSION(1, 26, 0) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcActivateConfigInput, qmi_message_pdc_activate_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcActivateConfigOutput, qmi_message_pdc_activate_config_output_unref) #pragma clang diagnostic pop #endif 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); } #if !QMI_CHECK_VERSION(1, 26, 0) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcSetSelectedConfigInput, qmi_message_pdc_set_selected_config_input_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(QmiMessagePdcSetSelectedConfigOutput, qmi_message_pdc_set_selected_config_output_unref) #pragma clang diagnostic pop #endif 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; QmiConfigTypeAndId type_and_id; type_and_id.config_type = QMI_PDC_CONFIGURATION_TYPE_SOFTWARE; type_and_id.id = ctx->digest; input = qmi_message_pdc_set_selected_config_input_new(); qmi_message_pdc_set_selected_config_input_set_type_with_id(input, &type_and_id, NULL); 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 *path) { FuQmiPdcUpdater *self = g_object_new(FU_TYPE_QMI_PDC_UPDATER, NULL); self->qmi_port = g_strdup(path); return self; } fwupd-1.7.5/plugins/modem-manager/fu-qmi-pdc-updater.h000066400000000000000000000014701420024370600226200ustar00rootroot00000000000000/* * 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.7.5/plugins/modem-manager/meson.build000066400000000000000000000013211420024370600212000ustar00rootroot00000000000000if get_option('plugin_modem_manager') cargs = ['-DG_LOG_DOMAIN="FuPluginMm"'] install_data(['modem-manager.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_modem_manager', fu_hash, sources : [ 'fu-plugin-modem-manager.c', 'fu-mm-device.c', 'fu-qmi-pdc-updater.c', 'fu-mbim-qdu-updater.c', 'fu-firehose-updater.c', 'fu-mm-utils.c' ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, libmm_glib, libqmi_glib, libmbim_glib, ], ) endif fwupd-1.7.5/plugins/modem-manager/modem-manager.quirk000066400000000000000000000026621420024370600226350ustar00rootroot00000000000000 # DW5821e [USB\VID_413C&PID_81D7] Summary = Dell DW5821e LTE modem CounterpartGuid = USB\VID_413C&PID_81D6 # DW5821e in fastboot mode [USB\VID_413C&PID_81D6] Summary = Dell DW5821e LTE modem (fastboot) CounterpartGuid = USB\VID_413C&PID_81D7 # DW5821e/eSIM [USB\VID_413C&PID_81E0] Summary = Dell DW5821e/eSIM LTE modem CounterpartGuid = USB\VID_413C&PID_81E1 # DW5821e/eSIM in fastboot mode [USB\VID_413C&PID_81E1] Summary = Dell DW5821e/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_413C&PID_81E0 # T77W968 [USB\VID_0489&PID_E0B4] Summary = Foxconn T77w968 LTE modem CounterpartGuid = USB\VID_0489&PID_E0B7 # T77W968 in fastboot mode [USB\VID_0489&PID_E0B7] Summary = Foxconn T77w968 LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B4 # T77W968/eSIM [USB\VID_0489&PID_E0B5] Summary = Foxconn T77w968/eSIM LTE modem CounterpartGuid = USB\VID_0489&PID_E0B8 # T77W968/eSIM in fastboot mode [USB\VID_0489&PID_E0B8] Summary = Foxconn T77w968/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B5 # 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 # Quectel EG25-G in fastboot mode [USB\VID_18D1&PID_D00D] Summary = Quectel EG25-G modem (fastboot) CounterpartGuid = USB\VID_2C7C&PID_0125 Flags = detach-at-fastboot-has-no-response ModemManagerBranchAtCommand = AT+GETFWBRANCH fwupd-1.7.5/plugins/msr/000077500000000000000000000000001420024370600151315ustar00rootroot00000000000000fwupd-1.7.5/plugins/msr/README.md000066400000000000000000000007421420024370600164130ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/msr/fu-plugin-msr.c000066400000000000000000000210761420024370600200100ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include typedef union { guint32 data; struct { guint32 enabled : 1; guint32 rsrvd : 29; guint32 locked : 1; guint32 debug_occurred : 1; } __attribute__((packed)) fields; } FuMsrIa32Debug; 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 FuPluginData { gboolean ia32_debug_supported; FuMsrIa32Debug ia32_debug; gboolean amd64_syscfg_supported; gboolean amd64_sev_supported; FuMsrAMD64Syscfg amd64_syscfg; FuMsrAMD64Sev amd64_sev; }; #define PCI_MSR_IA32_DEBUG_INTERFACE 0xc80 #define PCI_MSR_IA32_BIOS_SIGN_ID 0x8b #define PCI_MSR_AMD64_SYSCFG 0xC0010010 #define PCI_MSR_AMD64_SEV 0xC0010131 static void fu_plugin_msr_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_udev_subsystem(plugin, "msr"); } static gboolean fu_plugin_msr_startup(FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); guint eax = 0; guint ebx = 0; guint ecx = 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_common_get_cpu_vendor() == FU_CPU_VENDOR_INTEL) { if (!fu_common_cpuid(0x01, NULL, NULL, &ecx, NULL, error)) return FALSE; priv->ia32_debug_supported = ((ecx >> 11) & 0x1) > 0; } /* indicates support for SME and SEV */ if (fu_common_get_cpu_vendor() == FU_CPU_VENDOR_AMD) { if (!fu_common_cpuid(0x8000001f, &eax, &ebx, NULL, NULL, error)) return FALSE; g_debug("SME/SEV check MSR: eax 0%x, ebx 0%x", eax, ebx); priv->amd64_syscfg_supported = ((eax >> 0) & 0x1) > 0; priv->amd64_sev_supported = ((eax >> 1) & 0x1) > 0; } return TRUE; } static gboolean fu_plugin_msr_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuDevice *device_cpu = fu_plugin_cache_lookup(plugin, "cpu"); FuPluginData *priv = fu_plugin_get_data(plugin); 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 (priv->ia32_debug_supported) { if (!fu_udev_device_pread_full(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_common_read_uint32_safe(buf, sizeof(buf), 0x0, &priv->ia32_debug.data, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("IA32_DEBUG_INTERFACE: enabled=%i, locked=%i, debug_occurred=%i", priv->ia32_debug.fields.enabled, priv->ia32_debug.fields.locked, priv->ia32_debug.fields.debug_occurred); } /* grab AMD MSRs */ if (priv->amd64_syscfg_supported) { if (!fu_udev_device_pread_full(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_common_read_uint32_safe(buf, sizeof(buf), 0x0, &priv->amd64_syscfg.data, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("PCI_MSR_AMD64_SYSCFG: 0%x, sme_is_enabled=%i", priv->amd64_syscfg.data, priv->amd64_syscfg.fields.sme_is_enabled); } if (priv->amd64_sev_supported) { if (!fu_udev_device_pread_full(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_common_read_uint32_safe(buf, sizeof(buf), 0x0, &priv->amd64_sev.data, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("PCI_MSR_AMD64_SEV: 0%x, sev_is_enabled=%i", priv->amd64_sev.data, priv->amd64_sev.fields.sev_is_enabled); } /* get microcode version */ if (device_cpu != NULL) { guint32 ver_raw; if (!fu_udev_device_pread_full(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_common_dump_raw(G_LOG_DOMAIN, "IA32_BIOS_SIGN_ID", buf, sizeof(buf)); if (!fu_common_read_uint32_safe(buf, sizeof(buf), 0x4, &ver_raw, G_LITTLE_ENDIAN, error)) return FALSE; if (ver_raw != 0) { FwupdVersionFormat verfmt = fu_device_get_version_format(device_cpu); g_autofree gchar *ver_str = NULL; ver_str = fu_common_version_from_uint32(ver_raw, verfmt); g_debug("setting microcode version to %s", ver_str); fu_device_set_version(device_cpu, ver_str); fu_device_set_version_raw(device_cpu, ver_raw); } } /* success */ return TRUE; } static void fu_plugin_msr_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) { FuPluginData *priv = fu_plugin_get_data(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_common_get_cpu_vendor() != FU_CPU_VENDOR_INTEL) return; if (!priv->ia32_debug_supported) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_DCI_ENABLED); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); if (device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); fu_security_attrs_append(attrs, attr); /* check fields */ if (priv->ia32_debug.fields.enabled) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); } static void fu_plugin_add_security_attr_dci_locked(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(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_common_get_cpu_vendor() != FU_CPU_VENDOR_INTEL) return; if (!priv->ia32_debug_supported) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_DCI_LOCKED); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); if (device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); fu_security_attrs_append(attrs, attr); /* check fields */ if (!priv->ia32_debug.fields.locked) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } static void fu_plugin_msr_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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_msr_init; vfuncs->startup = fu_plugin_msr_startup; vfuncs->backend_device_added = fu_plugin_msr_backend_device_added; vfuncs->add_security_attrs = fu_plugin_msr_add_security_attrs; vfuncs->device_registered = fu_plugin_msr_device_registered; } fwupd-1.7.5/plugins/msr/fwupd-msr.conf000066400000000000000000000000041420024370600177160ustar00rootroot00000000000000msr fwupd-1.7.5/plugins/msr/meson.build000066400000000000000000000011561420024370600172760ustar00rootroot00000000000000if get_option('hsi') and get_option('plugin_msr') cargs = ['-DG_LOG_DOMAIN="FuPluginMsr"'] install_data(['msr.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) if get_option('systemd') install_data(['fwupd-msr.conf'], install_dir: systemd_modules_load_dir, ) endif shared_module('fu_plugin_msr', fu_hash, sources : [ 'fu-plugin-msr.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/msr/msr.quirk000066400000000000000000000001001420024370600167760ustar00rootroot00000000000000# match all devices with this udev subsystem [MSR] Plugin = msr fwupd-1.7.5/plugins/mtd/000077500000000000000000000000001420024370600151145ustar00rootroot00000000000000fwupd-1.7.5/plugins/mtd/README.md000066400000000000000000000012651420024370600163770ustar00rootroot00000000000000# 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`, e.g. * `MTD\NAME_Factory` ## Update Behavior The MTD device is erased in chunks, written and then read back to verify. ## 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`. fwupd-1.7.5/plugins/mtd/fu-mtd-device.c000066400000000000000000000165711420024370600177230ustar00rootroot00000000000000/* * 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; }; G_DEFINE_TYPE(FuMtdDevice, fu_mtd_device, FU_TYPE_UDEV_DEVICE) static void fu_mtd_device_to_string(FuDevice *device, guint idt, GString *str) { FuMtdDevice *self = FU_MTD_DEVICE(device); if (self->erasesize > 0) fu_common_string_append_kx(str, idt, "EraseSize", self->erasesize); } static gboolean fu_mtd_device_probe(FuDevice *device, GError **error) { FuMtdDevice *self = FU_MTD_DEVICE(device); const gchar *name; guint64 flags = 0; guint64 size = 0; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_mtd_device_parent_class)->probe(device, error)) return FALSE; /* set physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "mtd", error)) return FALSE; /* get name */ name = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL); if (name != NULL) { g_autofree gchar *devid = NULL; g_autofree gchar *name_safe = g_strdup(name); g_strdelimit(name_safe, " /\\\"", '-'); devid = g_strdup_printf("MTD\\NAME_%s", name_safe); fu_device_add_instance_id(FU_DEVICE(self), devid); fu_device_set_name(FU_DEVICE(self), name); } /* 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); if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "flags", &flags, error)) return FALSE; #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(GPtrArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, self->erasesize); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); /* erase each chunk */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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, 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, GPtrArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); /* 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 < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_udev_device_pwrite_full(FU_UDEV_DEVICE(self), 0x0, 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, GPtrArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); /* verify each chunk */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_full(FU_UDEV_DEVICE(self), 0x0, 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_common_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(GPtrArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 50); /* 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 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50); /* 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 void fu_mtd_device_init(FuMtdDevice *self) { 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->probe = fu_mtd_device_probe; klass_device->to_string = fu_mtd_device_to_string; klass_device->write_firmware = fu_mtd_device_write_firmware; } fwupd-1.7.5/plugins/mtd/fu-mtd-device.h000066400000000000000000000004311420024370600177140ustar00rootroot00000000000000/* * 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.7.5/plugins/mtd/fu-plugin-mtd.c000066400000000000000000000027751420024370600177630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-mtd-device.h" static void fu_plugin_mtd_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "mtd"); fu_plugin_add_device_gtype(plugin, FU_TYPE_MTD_DEVICE); } static gboolean fu_plugin_mtd_startup(FuPlugin *plugin, 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 gboolean fu_plugin_mtd_device_created(FuPlugin *plugin, FuDevice *dev, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *vendor; fu_device_set_summary(dev, "Memory Technology Device"); fu_device_add_protocol(dev, "org.infradead.mtd"); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_icon(dev, "drive-harddisk-solidstate"); /* 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(dev, vendor_id); } return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_mtd_init; vfuncs->startup = fu_plugin_mtd_startup; vfuncs->device_created = fu_plugin_mtd_device_created; } fwupd-1.7.5/plugins/mtd/meson.build000066400000000000000000000010121420024370600172500ustar00rootroot00000000000000if get_option('plugin_mtd') cargs = ['-DG_LOG_DOMAIN="FuPluginMtd"'] install_data([ 'mtd.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_mtd', fu_hash, sources : [ 'fu-plugin-mtd.c', 'fu-mtd-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/mtd/mtd.quirk000066400000000000000000000000231420024370600167500ustar00rootroot00000000000000[MTD] Plugin = mtd fwupd-1.7.5/plugins/nitrokey/000077500000000000000000000000001420024370600161745ustar00rootroot00000000000000fwupd-1.7.5/plugins/nitrokey/README.md000066400000000000000000000022071420024370600174540ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_20A0&PID_4109` * `USB\VID_20A0` ## 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`. fwupd-1.7.5/plugins/nitrokey/fu-nitrokey-common.c000066400000000000000000000015531420024370600221060ustar00rootroot00000000000000/* * 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.7.5/plugins/nitrokey/fu-nitrokey-common.h000066400000000000000000000031111420024370600221030ustar00rootroot00000000000000/* * 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 VolumeActiceFlag; guint8 NewSmartCardFound; guint8 UserPwRetryCount; guint8 AdminPwRetryCount; guint32 ActiveSmartCardID; guint8 StickKeysNotInitiated; } NitrokeyGetDeviceStatusPayload; fwupd-1.7.5/plugins/nitrokey/fu-nitrokey-device.c000066400000000000000000000107471420024370600220620ustar00rootroot00000000000000/* * 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_common_write_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; } if (g_getenv("FWUPD_NITROKEY_VERBOSE") != NULL) fu_common_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_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.7.5/plugins/nitrokey/fu-nitrokey-device.h000066400000000000000000000005711420024370600220610ustar00rootroot00000000000000/* * 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.7.5/plugins/nitrokey/fu-plugin-nitrokey.c000066400000000000000000000007321420024370600221120ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" #include "fu-nitrokey-device.h" static void fu_plugin_nitrokey_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_NITROKEY_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_nitrokey_init; } fwupd-1.7.5/plugins/nitrokey/fu-self-test.c000066400000000000000000000062641420024370600206660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { 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.7.5/plugins/nitrokey/lsusb.txt000066400000000000000000000165571420024370600201030ustar00rootroot00000000000000Bus 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 dwMaxiumumClock 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.7.5/plugins/nitrokey/meson.build000066400000000000000000000020551420024370600203400ustar00rootroot00000000000000if get_option('plugin_nitrokey') cargs = ['-DG_LOG_DOMAIN="FuPluginNitrokey"'] install_data(['nitrokey.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_nitrokey', fu_hash, sources : [ 'fu-nitrokey-device.c', 'fu-nitrokey-common.c', 'fu-plugin-nitrokey.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'nitrokey-self-test', fu_hash, sources : [ 'fu-nitrokey-common.c', 'fu-self-test.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, valgrind, ], link_with : [ fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('nitrokey-self-test', e) # added to installed-tests endif endif fwupd-1.7.5/plugins/nitrokey/nitrokey.quirk000066400000000000000000000003111420024370600211100ustar00rootroot00000000000000# 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.7.5/plugins/nordic-hid/000077500000000000000000000000001420024370600163505ustar00rootroot00000000000000fwupd-1.7.5/plugins/nordic-hid/README.md000066400000000000000000000041011420024370600176230ustar00rootroot00000000000000# Nordic Semiconductor 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 standard HIDRAW DeviceInstanceId values are used with the addition of the target board and bootloader name: * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_B0` -> 22952036-c346-5755-9646-7bf766b28922 * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_MCUBOOT` -> 43b38427-fdf5-5400-a23c-f3eb7ea00e7c ## Quirk Use This plugin uses the following plugin-specific quirks: ### NordicHidBootloader Explicitly set the expected bootloader type: "B0" or "MCUBOOT" This quirk must be set for devices without support of `bootloader variant` 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. fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-archive.c000066400000000000000000000124621420024370600225700ustar00rootroot00000000000000/* * 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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { JsonNode *json_root_node; JsonObject *json_obj; JsonArray *json_files; guint manifest_ver; guint files_cnt = 0; GBytes *manifest = NULL; g_autoptr(FuArchive) archive = 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); GBytes *blob = NULL; g_autoptr(FuFirmware) image = NULL; g_autofree gchar *image_id = NULL; g_auto(GStrv) board_split = 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 { 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; } /* images for B0 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 only the 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, 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); } fu_firmware_add_image(firmware, image); } /* success */ return TRUE; } static void fu_nordic_hid_archive_init(FuNordicHidArchive *self) { } 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.7.5/plugins/nordic-hid/fu-nordic-hid-archive.h000066400000000000000000000005411420024370600225700ustar00rootroot00000000000000/* * 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.7.5/plugins/nordic-hid/fu-nordic-hid-cfg-channel.c000066400000000000000000001017571420024370600233220ustar00rootroot00000000000000/* * 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 #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 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 */ 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_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; gchar *board_name; gchar *bl_name; guint8 flash_area_id; guint32 flashed_image_len; guint8 peer_id; GPtrArray *modules; /* of FuNordicCfgChannelModule */ }; G_DEFINE_TYPE(FuNordicHidCfgChannel, fu_nordic_hid_cfg_channel, FU_TYPE_UDEV_DEVICE) static void fu_nordic_hid_cfg_channel_module_option_free(FuNordicCfgChannelModuleOption *opt) { g_free(opt->name); g_free(opt); } 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); } #ifdef HAVE_HIDRAW_H static FuUdevDevice * fu_nordic_hid_cfg_channel_get_udev_device(FuNordicHidCfgChannel *self, GError **error) { FuDevice *parent; /* ourselves */ if (self->peer_id == 0) return FU_UDEV_DEVICE(self); /* parent */ parent = fu_device_get_parent(FU_DEVICE(self)); if (parent == 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 FU_UDEV_DEVICE(parent); } #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; if (g_getenv("FWUPD_NORDIC_HID_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "Sent", buf, bufsz); if (!fu_udev_device_ioctl(udev_device, HIDIOCSFEATURE(bufsz), buf, NULL, 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, 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; g_usleep(i * 50); } if (!fu_memcpy_safe(buf, bufsz, 0, (guint8 *)recv_msg, sizeof(*recv_msg), 0, sizeof(*recv_msg), error)) { return FALSE; } if (g_getenv("FWUPD_NORDIC_HID_VERBOSE") != NULL) fu_common_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_add_peers(FuNordicHidCfgChannel *self, GError **error) { guint cnt = 0; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); g_autoptr(GError) error_local = NULL; if (self->peer_id != 0) return TRUE; if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_INDEX_PEERS, NULL, 0, error)) return FALSE; if (fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_DISCONNECTED, res, &error_local)) { /* no peers */ return TRUE; } /* Peers available */ if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; while (cnt++ <= 0xFF) { g_autoptr(FuNordicHidCfgChannel) peer = NULL; if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_PEER, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; /* end of the list */ if (res->data[8] == INVALID_PEER_ID) return TRUE; if (g_getenv("FWUPD_NORDIC_HID_VERBOSE") != NULL) g_debug("detected peer: 0x%02x", res->data[8]); peer = fu_nordic_hid_cfg_channel_new(res->data[8]); /* prohibit to close close parent's communication descriptor */ fu_device_add_internal_flag(FU_DEVICE(peer), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); /* probe&setup are the part of adding child */ fu_device_add_child(FU_DEVICE(self), FU_DEVICE(peer)); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE, "too many peers detected"); return FALSE; } static gboolean fu_nordic_hid_cfg_channel_get_board_name(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_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_common_strsafe((const gchar *)res->data, res->data_len); /* 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 && strncmp(self->bl_name, (const char *)res->data, res->data_len) != 0) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "bootloader in qiurk file is '%s' while the board is supporting '%s'", self->bl_name, g_strndup((const gchar *)res->data, res->data_len)); return FALSE; } self->bl_name = fu_common_strsafe((const gchar *)res->data, res->data_len); } else if (g_getenv("FWUPD_NORDIC_HID_VERBOSE") != NULL) { g_debug("the board have no support of bootloader runtime detection"); } 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; } /* * 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-%s", 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], self->bl_name); 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++) { 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_common_strsafe((const gchar *)res->data, res->data_len); opt->idx = i; g_ptr_array_add(mod->options, opt); } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_load_module_info(FuNordicHidCfgChannel *self, guint8 module_idx, GError **error) { 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, 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; /* always use the bank 0 for MCUBOOT bootloader */ if (g_strcmp0(self->bl_name, "MCUBOOT") == 0) self->flash_area_id = 0; if (!fu_common_read_uint32_safe(res->data, REPORT_SIZE, 0x01, &self->flashed_image_len, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(res->data, REPORT_SIZE, 0x07, &ver_rev, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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; g_usleep(i * 5000); 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_common_read_uint32_safe(res->data, REPORT_SIZE, 0x01, &dfu_info->img_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(res->data, REPORT_SIZE, 0x05, &dfu_info->img_csum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(res->data, REPORT_SIZE, 0x09, &dfu_info->offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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_common_write_uint32_safe(data, REPORT_DATA_MAX_LEN, 0x00, (guint32)img_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_write_uint32_safe(data, REPORT_DATA_MAX_LEN, 0x04, img_crc, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_write_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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_nordic_hid_cfg_channel_parent_class)->probe(device, error)) return FALSE; return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_nordic_hid_cfg_channel_setup(FuDevice *device, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); g_autofree gchar *target_id = NULL; /* get the board name */ if (!fu_nordic_hid_cfg_channel_get_board_name(self, error)) return FALSE; /* detect available modules first */ if (!fu_nordic_hid_cfg_channel_get_modinfo(self, error)) return FALSE; /* detect bootloader type */ if (!fu_nordic_hid_cfg_channel_get_bl_name(self, error)) return FALSE; /* set the physical id based on name, HW id and bootloader type of the board * to detect if the device is connected via several interfaces */ if (!fu_nordic_hid_cfg_channel_get_hwid(self, error)) return FALSE; /* get device info and version */ if (!fu_nordic_hid_cfg_channel_dfu_fwinfo(self, error)) return FALSE; /* check if any peer is connected via this device */ if (!fu_nordic_hid_cfg_channel_add_peers(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); } /* additional GUID based on VID/PID and target area to flash * needed to distinguish images aimed to different bootloaders */ target_id = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&BOARD_%s&BL_%s", fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)), fu_udev_device_get_model(FU_UDEV_DEVICE(device)), self->board_name, self->bl_name); fu_device_add_guid(device, target_id); 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_common_string_append_kv(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_common_string_append_kv(str, idt, "BoardName", self->board_name); fu_common_string_append_kv(str, idt, "Bootloader", self->bl_name); fu_common_string_append_kx(str, idt, "FlashAreaId", self->flash_area_id); fu_common_string_append_kx(str, idt, "FlashedImageLen", self->flashed_image_len); fu_common_string_append_kx(str, idt, "PeerId", self->peer_id); 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_common_string_append_kv(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(GPtrArray) 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, 0, dfu_info->sync_buffer_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); gboolean is_last = (i == chunks->len - 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0); /* 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) self->bl_name = g_strdup("B0"); else if (g_strcmp0(value, "MCUBOOT") == 0) self->bl_name = g_strdup("MCUBOOT"); else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "must be 'B0' or 'MCUBOOT'"); 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_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_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->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_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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); } FuNordicHidCfgChannel * fu_nordic_hid_cfg_channel_new(guint8 id) { FuNordicHidCfgChannel *self = g_object_new(FU_TYPE_NORDIC_HID_CFG_CHANNEL, NULL); self->peer_id = id; return self; } fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-cfg-channel.h000066400000000000000000000006761420024370600233250ustar00rootroot00000000000000/* * 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) FuNordicHidCfgChannel * fu_nordic_hid_cfg_channel_new(guint8 id); fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-firmware-b0.c000066400000000000000000000102041420024370600232520ustar00rootroot00000000000000/* * 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 GBytes * 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_byte_array_free_to_bytes(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_common_read_uint32_safe(buf, bufsz, offset, &magic_common, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, offset + 0x04, &magic_fwinfo, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, offset + 0x08, &magic_compat, G_LITTLE_ENDIAN, error)) return FALSE; /* version */ if (!fu_common_read_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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; if (!FU_FIRMWARE_CLASS(fu_nordic_hid_firmware_b0_parent_class) ->parse(firmware, fw, addr_start, addr_end, 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; } FuFirmware * fu_nordic_hid_firmware_b0_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_NORDIC_HID_FIRMWARE_B0, NULL)); } fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-firmware-b0.h000066400000000000000000000006711420024370600232660ustar00rootroot00000000000000/* * 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) FuFirmware * fu_nordic_hid_firmware_b0_new(void); fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-firmware-mcuboot.c000066400000000000000000000116661420024370600244360ustar00rootroot00000000000000/* * 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 GBytes * 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_byte_array_free_to_bytes(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_common_read_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_common_read_uint16_safe(buf, bufsz, 8, &hdr_size, G_LITTLE_ENDIAN, error)) return FALSE; /* ignore protect_tlv_size */ if (!fu_common_read_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_common_read_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_common_read_uint8_safe(buf, bufsz, 0x14, &ver_major, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, 0x15, &ver_minor, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, 0x16, &ver_rev, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; if (!FU_FIRMWARE_CLASS(fu_nordic_hid_firmware_mcuboot_parent_class) ->parse(firmware, fw, addr_start, addr_end, 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; } FuFirmware * fu_nordic_hid_firmware_mcuboot_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_NORDIC_HID_FIRMWARE_MCUBOOT, NULL)); } fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-firmware-mcuboot.h000066400000000000000000000007271420024370600244370ustar00rootroot00000000000000/* * 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) FuFirmware * fu_nordic_hid_firmware_mcuboot_new(void); fwupd-1.7.5/plugins/nordic-hid/fu-nordic-hid-firmware.c000066400000000000000000000052421420024370600227610ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_crc32_full()? * according https://github.com/madler/zlib/blob/master/crc32.c#L225 */ crc32 ^= 0xFFFFFFFFUL; return fu_common_crc32_full(buf, bufsz, crc32, 0xEDB88320); } static gboolean fu_nordic_hid_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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); /* do not strip the header */ fu_firmware_set_bytes(firmware, fw); 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.7.5/plugins/nordic-hid/fu-nordic-hid-firmware.h000066400000000000000000000006421420024370600227650ustar00rootroot00000000000000/* * 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.7.5/plugins/nordic-hid/fu-plugin-nordic-hid.c000066400000000000000000000017201420024370600224400ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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" static void fu_plugin_nordic_hid_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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); fu_context_add_quirk_key(ctx, "NordicHidBootloader"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_nordic_hid_init; } fwupd-1.7.5/plugins/nordic-hid/meson.build000066400000000000000000000013151420024370600205120ustar00rootroot00000000000000if get_option('gudev') and get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginNordicHid"'] install_data([ 'nordic-hid.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_nordic_hid', fu_hash, sources : [ 'fu-plugin-nordic-hid.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/nordic-hid/nordic-hid.quirk000066400000000000000000000004631420024370600214500ustar00rootroot00000000000000# Mouse nRF52 Desktop [HIDRAW\VEN_1915&DEV_52DE] Plugin = nordic_hid GType = FuNordicHidCfgChannel #NordicHidBootloader = B0 #NordicHidBootloader = MCUBOOT # Nordic Semiconductor ASA Dongle nRF52 Desktop [HIDRAW\VEN_1915&DEV_52DC] Plugin = nordic_hid GType = FuNordicHidCfgChannel NordicHidBootloader = B0 fwupd-1.7.5/plugins/nordic-hid/tests/000077500000000000000000000000001420024370600175125ustar00rootroot00000000000000fwupd-1.7.5/plugins/nordic-hid/tests/nordic-hid-b0.bin000066400000000000000000000000431420024370600225200ustar00rootroot00000000000000(LΏ4chello worldfwupd-1.7.5/plugins/nordic-hid/tests/nordic-hid-b0.builder.xml000066400000000000000000000001451420024370600242000ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/nordic-hid/tests/nordic-hid-mcuboot.bin000066400000000000000000000000571420024370600236740ustar00rootroot00000000000000= chello worldifwupd-1.7.5/plugins/nordic-hid/tests/nordic-hid-mcuboot.builder.xml000066400000000000000000000001521420024370600253450ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/nvme/000077500000000000000000000000001420024370600152755ustar00rootroot00000000000000fwupd-1.7.5/plugins/nvme/README.md000066400000000000000000000032641420024370600165610ustar00rootroot00000000000000# 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&REV_01` * `NVME\VEN_1179&DEV_010F` * `NVME\VEN_1179` 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, 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 ## 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. fwupd-1.7.5/plugins/nvme/fu-nvme-common.c000066400000000000000000000123511420024370600203060ustar00rootroot00000000000000/* * 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_ACIVATE_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.7.5/plugins/nvme/fu-nvme-common.h000066400000000000000000000060321420024370600203120ustar00rootroot00000000000000/* * 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_ACIVATE_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.7.5/plugins/nvme/fu-nvme-device.c000066400000000000000000000341421420024370600202570ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; }; /** * FU_NVME_DEVICE_FLAG_FORCE_ALIGN: * * Force alignment of the firmware file. */ #define FU_NVME_DEVICE_FLAG_FORCE_ALIGN (1 << 0) G_DEFINE_TYPE(FuNvmeDevice, fu_nvme_device, FU_TYPE_UDEV_DEVICE) 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_common_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, 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 sanitiezed 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; if (g_getenv("FWUPD_NVME_VERBOSE") != NULL) 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; } static void fu_nvme_device_dump(const gchar *title, const guint8 *buf, gsize sz) { if (g_getenv("FWUPD_NVME_VERBOSE") == NULL) return; g_print("%s (%" G_GSIZE_FORMAT "):", title, sz); for (gsize i = 0; i < sz; i++) { if (i % 64 == 0) g_print("\naddr 0x%04x: ", (guint)i); g_print("%02x", buf[i]); } g_print("\n"); } /* * 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); } /* all devices need at least a warm reset, but some quirked drives * need a full "cold" shutdown and startup */ if (!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_nvme_device_dump("CNS", buf, sizeof(buf)); if (!fu_nvme_device_parse_cns(self, buf, sizeof(buf), error)) return FALSE; /* 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(GPtrArray) chunks = NULL; guint64 block_size = self->write_block_size > 0 ? self->write_block_size : 0x1000; /* 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); 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_common_bytes_align(fw, block_size, 0xff); } else { fw2 = g_bytes_ref(fw); } /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw2, 0x00, /* start_addr */ 0x00, /* page_sz */ block_size); /* block size */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } fu_progress_step_done(progress); /* commit */ if (!fu_nvme_device_fw_commit(self, 0x00, /* let controller choose */ 0x01, /* download replaces, activated on reboot */ 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_common_strtoull_full(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_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"); } 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.7.5/plugins/nvme/fu-nvme-device.h000066400000000000000000000006111420024370600202560ustar00rootroot00000000000000/* * 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.7.5/plugins/nvme/fu-plugin-nvme.c000066400000000000000000000007311420024370600203130ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nvme-device.h" static void fu_plugin_nvme_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "nvme"); fu_plugin_add_device_gtype(plugin, FU_TYPE_NVME_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_nvme_init; } fwupd-1.7.5/plugins/nvme/fu-self-test.c000066400000000000000000000056741420024370600177730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_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/cns", fu_nvme_cns_func); g_test_add_func("/fwupd/cns{all}", fu_nvme_cns_all_func); return g_test_run(); } fwupd-1.7.5/plugins/nvme/meson.build000066400000000000000000000025711420024370600174440ustar00rootroot00000000000000if get_option('plugin_nvme') if not get_option('gudev') error('gudev is required for plugin_nvme') endif cargs = ['-DG_LOG_DOMAIN="FuPluginNvme"'] install_data([ 'nvme.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_nvme', fu_hash, sources : [ 'fu-plugin-nvme.c', 'fu-nvme-common.c', 'fu-nvme-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-nvme-common.c', 'fu-nvme-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('nvme-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/nvme/nvme.quirk000066400000000000000000000002621420024370600173170ustar00rootroot00000000000000# match all devices with this udev subsystem [NVME] Plugin = nvme # Phison [NVME\VEN_1987] Flags = force-align,needs-shutdown # Kingston [NVME\VEN_2646] Flags = needs-shutdown fwupd-1.7.5/plugins/optionrom/000077500000000000000000000000001420024370600163565ustar00rootroot00000000000000fwupd-1.7.5/plugins/optionrom/README.md000066400000000000000000000007551420024370600176440ustar00rootroot00000000000000# 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`) fwupd-1.7.5/plugins/optionrom/fu-optionrom-device.c000066400000000000000000000077471420024370600224340ustar00rootroot00000000000000/* * 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; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_optionrom_device_parent_class)->probe(device, error)) return FALSE; /* 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; } /* 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) { 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; } } if (buf->len < 512) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small: %u bytes", buf->len); return NULL; } return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static void fu_optionrom_device_init(FuOptionromDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(FU_DEVICE(self), "audio-card"); 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.7.5/plugins/optionrom/fu-optionrom-device.h000066400000000000000000000004671420024370600224310ustar00rootroot00000000000000/* * 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.7.5/plugins/optionrom/fu-plugin-optionrom.c000066400000000000000000000010601420024370600224510ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-optionrom-device.h" static void fu_plugin_optionrom_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "pci"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "udev"); fu_plugin_add_device_gtype(plugin, FU_TYPE_OPTIONROM_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_optionrom_init; } fwupd-1.7.5/plugins/optionrom/fu-self-test.c000066400000000000000000000067101420024370600210440ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-plugin-private.h" #include "fu-rom.h" static void fu_rom_func(void) { struct { FuRomKind kind; const gchar *fn; const gchar *ver; guint16 vendor; guint16 model; } data[] = { {FU_ROM_KIND_ATI, "Asus.9800PRO.256.unknown.031114.rom", "008.015.041.001", 0x1002, 0x4e48}, {FU_ROM_KIND_ATI, /* atombios */ "Asus.R9290X.4096.131014.rom", "015.039.000.006.003515", 0x1002, 0x67b0}, {FU_ROM_KIND_ATI, /* atombios, with serial */ "Asus.HD7970.3072.121018.rom", "015.023.000.002.000000", 0x1002, 0x6798}, {FU_ROM_KIND_NVIDIA, "Asus.GTX480.1536.100406_1.rom", "70.00.1A.00.02", 0x10de, 0x06c0}, {FU_ROM_KIND_NVIDIA, /* nvgi */ "Asus.GTX980.4096.140905.rom", "84.04.1F.00.02", 0x10de, 0x13c0}, {FU_ROM_KIND_NVIDIA, /* nvgi, with serial */ "Asus.TitanBlack.6144.140212.rom", "80.80.4E.00.01", 0x10de, 0x100c}, {FU_ROM_KIND_UNKNOWN, NULL, NULL, 0x0000, 0x0000}}; for (guint i = 0; data[i].fn != NULL; i++) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuRom) rom = NULL; g_autoptr(GFile) file = NULL; rom = fu_rom_new(); g_assert_nonnull(rom); /* load file */ filename = g_test_build_filename(G_TEST_DIST, "tests", data[i].fn, NULL); if (!g_file_test(filename, G_FILE_TEST_EXISTS)) continue; g_print("\nparsing %s...", filename); file = g_file_new_for_path(filename); ret = fu_rom_load_file(rom, file, FU_ROM_LOAD_FLAG_BLANK_PPID, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fu_rom_get_version(rom), ==, data[i].ver); g_assert_cmpint(fu_rom_get_kind(rom), ==, data[i].kind); g_assert_cmpint(fu_rom_get_vendor(rom), ==, data[i].vendor); g_assert_cmpint(fu_rom_get_model(rom), ==, data[i].model); } } static void fu_rom_all_func(void) { GDir *dir; g_autofree gchar *path = NULL; /* may or may not exist */ path = g_test_build_filename(G_TEST_DIST, "tests", "roms", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) return; g_print("\n"); dir = g_dir_open(path, 0, NULL); do { const gchar *fn; gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuRom) rom = NULL; g_autoptr(GFile) file = NULL; fn = g_dir_read_name(dir); if (fn == NULL) break; filename = g_build_filename(path, fn, NULL); g_print("\nparsing %s...", filename); file = g_file_new_for_path(filename); rom = fu_rom_new(); ret = fu_rom_load_file(rom, file, FU_ROM_LOAD_FLAG_BLANK_PPID, NULL, &error); if (!ret) { g_print("%s %s : %s\n", fu_rom_kind_to_string(fu_rom_get_kind(rom)), filename, error->message); continue; } g_assert_cmpstr(fu_rom_get_version(rom), !=, NULL); g_assert_cmpstr(fu_rom_get_version(rom), !=, "\0"); g_assert_cmpint(fu_rom_get_kind(rom), !=, FU_ROM_KIND_UNKNOWN); } while (TRUE); } int main(int argc, char **argv) { 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/rom", fu_rom_func); g_test_add_func("/fwupd/rom{all}", fu_rom_all_func); return g_test_run(); } fwupd-1.7.5/plugins/optionrom/fuzzing/000077500000000000000000000000001420024370600200525ustar00rootroot00000000000000fwupd-1.7.5/plugins/optionrom/fuzzing/header-data-payload.rom000066400000000000000000000010001420024370600243460ustar00rootroot00000000000000U K7hdr-data-payload PCIRVersion 1.0\fwupd-1.7.5/plugins/optionrom/fuzzing/header-no-data.rom000066400000000000000000000010001420024370600233310ustar00rootroot00000000000000U K7hdr-no-data fwupd-1.7.5/plugins/optionrom/fuzzing/ifr-header-data-payload.rom000066400000000000000000000012001420024370600251260ustar00rootroot00000000000000NVGIU K7ifr-hdr-data-payld PCIRVersion 1.0\fwupd-1.7.5/plugins/optionrom/fuzzing/naked-ifr.rom000066400000000000000000000002001420024370600224210ustar00rootroot00000000000000NVGIfwupd-1.7.5/plugins/optionrom/meson.build000066400000000000000000000010341420024370600205160ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginOptionrom"'] install_data(['optionrom.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_optionrom', fu_hash, sources : [ 'fu-plugin-optionrom.c', 'fu-optionrom-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/optionrom/optionrom.quirk000066400000000000000000000001061420024370600214560ustar00rootroot00000000000000# match all devices with this udev subsystem [PCI] Plugin = optionrom fwupd-1.7.5/plugins/parade-lspcon/000077500000000000000000000000001420024370600170605ustar00rootroot00000000000000fwupd-1.7.5/plugins/parade-lspcon/README.md000066400000000000000000000034041420024370600203400ustar00rootroot00000000000000# 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 The plugin uses a custom DeviceInstanceId value derived from the device name provided by system firmware and read from sysfs, such as: * `PARADE-LSPCON\NAME_1AF80175:00` * `PARADE-LSPCON\NAME_1AF80175: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`. fwupd-1.7.5/plugins/parade-lspcon/fu-parade-lspcon-device.c000066400000000000000000000645571420024370600236420ustar00rootroot00000000000000/* * 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 /* * 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); 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); FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); const gchar *device_name; g_autofree gchar *instance_id_hwid = NULL; g_autofree gchar *instance_id = NULL; /* custom instance IDs to get device quirks */ instance_id = g_strdup_printf("PARADE-LSPCON\\NAME_%s", fu_udev_device_get_sysfs_attr(udev_device, "name", NULL)); fu_device_add_instance_id(device, instance_id); instance_id_hwid = g_strdup_printf("%s&FAMILY_%s", instance_id, fu_context_get_hwid_value(context, FU_HWIDS_KEY_FAMILY)); fu_device_add_instance_id_full(device, instance_id_hwid, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); device_name = fu_device_get_name(device); if (g_strcmp0(device_name, "PS175") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device name %s is not supported by this plugin", device_name); 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; } /* FuI2cDevice->probe */ return FU_DEVICE_CLASS(fu_parade_lspcon_device_parent_class)->probe(device, error); } 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, 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_full(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, register_addr, error)) return FALSE; return fu_i2c_device_read(i2c_device, value, 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_full(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 */ g_usleep(G_TIME_SPAN_MILLISECOND); } 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(GPtrArray) 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; g_usleep(100 * G_TIME_SPAN_MILLISECOND); if (!fu_parade_lspcon_write_register(self, REG_ADDR_CLT2SPI, 0, error)) return FALSE; chunks = fu_chunk_array_new_from_bytes(data, base_address, 0, 256); for (gsize i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_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 */ write_data[0] = 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_full(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_autoptr(FuDeviceLocker) aux_device_locker = NULL; g_autoptr(FuUdevDevice) aux_device = NULL; g_autoptr(GList) aux_devices = 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_debug("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 = fu_udev_device_new_with_context(fu_device_get_context(device), g_steal_pointer(&aux_devices->data)); 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 */ if ((aux_device_locker = fu_device_locker_new(aux_device, error)) == NULL) return FALSE; /* DPCD address 00500-00502: device OUI */ if (!fu_udev_device_pread_full(aux_device, 0x500, (guint8 *)&oui, 3, error)) return FALSE; oui = GUINT32_FROM_BE(oui) >> 8; oui_string = g_strdup_printf("OUI:%06X", oui); fu_device_add_vendor_id(device, oui_string); 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; } /* DPCD address 0x50A, 0x50B: branch device firmware * major and minor revision */ if (!fu_udev_device_pread_full(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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 25); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 3); /* boot */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2); /* 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_common_bytes_compare_raw(buf, bufsz, readback_buf, 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_common_bytes_compare_raw(flag_data, sizeof(flag_data), readback_buf, 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.7.5/plugins/parade-lspcon/fu-parade-lspcon-device.h000066400000000000000000000005501420024370600236260ustar00rootroot00000000000000/* * 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.7.5/plugins/parade-lspcon/fu-plugin-parade-lspcon.c000066400000000000000000000007451420024370600236660ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-parade-lspcon-device.h" static void fu_plugin_parade_lspcon_init(FuPlugin *plugin) { fu_plugin_add_udev_subsystem(plugin, "i2c"); fu_plugin_add_device_gtype(plugin, FU_TYPE_PARADE_LSPCON_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_parade_lspcon_init; } fwupd-1.7.5/plugins/parade-lspcon/meson.build000066400000000000000000000013371420024370600212260ustar00rootroot00000000000000if get_option('plugin_parade_lspcon') if not get_option('gudev') error('gudev is required for plugin_parade_lspcon') endif cargs = ['-DG_LOG_DOMAIN="FuPluginParadeLspcon"'] install_data(['parade-lspcon.quirk'], install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d') ) shared_module('fu_plugin_parade_lspcon', fu_hash, sources : [ 'fu-parade-lspcon-device.c', 'fu-plugin-parade-lspcon.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/parade-lspcon/parade-lspcon.quirk000066400000000000000000000003671420024370600226730ustar00rootroot00000000000000# match all devices with this udev subsystem [I2C] Plugin = parade_lspcon # Parade PS175 [PARADE-LSPCON\NAME_1AF80175:00] Name = PS175 # "Puff" Chromeboxes [PARADE-LSPCON\NAME_1AF80175:00&FAMILY_Google_Hatch] ParadeLspconAuxDeviceName = DPDDC-B fwupd-1.7.5/plugins/pci-bcr/000077500000000000000000000000001420024370600156475ustar00rootroot00000000000000fwupd-1.7.5/plugins/pci-bcr/README.md000066400000000000000000000004511420024370600171260ustar00rootroot00000000000000# PCI 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`) fwupd-1.7.5/plugins/pci-bcr/config000066400000000000000000000001001420024370600170260ustar00rootroot00000000000000P1."fwupd-1.7.5/plugins/pci-bcr/fu-plugin-pci-bcr.c000066400000000000000000000153221420024370600212410ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { gboolean has_device; guint8 bcr_addr; guint8 bcr; }; #define BCR_WPD (1 << 0) #define BCR_BLE (1 << 1) #define BCR_SMM_BWP (1 << 5) static void fu_plugin_pci_bcr_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *priv = fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_context_add_quirk_key(ctx, "PciBcrAddr"); /* this is true except for some Atoms */ priv->bcr_addr = 0xdc; } static void fu_plugin_pci_bcr_set_updatable(FuPlugin *plugin, FuDevice *dev) { FuPluginData *priv = fu_plugin_get_data(plugin); if ((priv->bcr & BCR_WPD) == 0 && (priv->bcr & BCR_BLE) > 0) { fu_device_inhibit(dev, "bcr-locked", "BIOS locked"); } else { fu_device_uninhibit(dev, "bcr-locked"); } } static void fu_plugin_pci_bcr_device_registered(FuPlugin *plugin, FuDevice *dev) { FuPluginData *priv = fu_plugin_get_data(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 && priv->bcr_addr != tmp) { g_debug("overriding BCR addr from 0x%02x to 0x%02x", priv->bcr_addr, tmp); priv->bcr_addr = tmp; } } if (g_strcmp0(fu_device_get_plugin(dev), "flashrom") == 0 && fu_device_has_instance_id(dev, "main-system-firmware")) { /* PCI\VEN_8086 added first */ if (priv->has_device) { fu_plugin_pci_bcr_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) { FuPluginData *priv = fu_plugin_get_data(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fu_security_attrs_append(attrs, attr); /* no device */ if (!priv->has_device) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* load file */ if ((priv->bcr & BCR_WPD) == 1) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); } static void fu_plugin_add_security_attr_ble(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* no device */ if (!priv->has_device) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_BLE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fu_security_attrs_append(attrs, attr); /* load file */ if ((priv->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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_plugin_add_security_attr_smm_bwp(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* no device */ if (!priv->has_device) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fu_security_attrs_append(attrs, attr); /* load file */ if ((priv->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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } static gboolean fu_plugin_pci_bcr_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); FuDevice *device_msf; g_autoptr(FuDeviceLocker) locker = NULL; /* not supported */ if (priv->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), priv->bcr_addr, &priv->bcr, 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_plugin_pci_bcr_set_updatable(plugin, device_msf); /* success */ priv->has_device = TRUE; return TRUE; } static void fu_plugin_pci_bcr_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { /* only Intel */ if (fu_common_get_cpu_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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_pci_bcr_init; vfuncs->add_security_attrs = fu_plugin_pci_bcr_add_security_attrs; vfuncs->device_registered = fu_plugin_pci_bcr_device_registered; vfuncs->backend_device_added = fu_plugin_pci_bcr_backend_device_added; } fwupd-1.7.5/plugins/pci-bcr/meson.build000066400000000000000000000010311420024370600200040ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginPciBcr"'] install_data(['pci-bcr.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_pci_bcr', fu_hash, sources : [ 'fu-plugin-pci-bcr.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/pci-bcr/pci-bcr.quirk000066400000000000000000000003421420024370600202420ustar00rootroot00000000000000# 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.7.5/plugins/pci-mei/000077500000000000000000000000001420024370600156535ustar00rootroot00000000000000fwupd-1.7.5/plugins/pci-mei/README.md000066400000000000000000000004311420024370600171300ustar00rootroot00000000000000# 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`) fwupd-1.7.5/plugins/pci-mei/fu-mei-common.c000066400000000000000000000320561420024370600204750ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mei-common.h" const gchar * fu_mei_common_family_to_string(FuMeiFamily family) { if (family == FU_MEI_FAMILY_SPS) return "SPS"; if (family == FU_MEI_FAMILY_TXE) return "TXE"; if (family == FU_MEI_FAMILY_ME) return "ME"; if (family == FU_MEI_FAMILY_CSME) return "CSME"; return "AMT"; } 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) { if (vers->major == 11 && (vers->minor == 8 || vers->minor == 11 || vers->minor == 22)) { return vers->hotfix >= 70 ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } else if (vers->major == 12 && vers->minor == 0) { return (vers->hotfix == 49 || vers->hotfix >= 56) ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } else if (vers->major == 13 && vers->minor == 0) { return vers->hotfix >= 21 ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } else if (vers->major == 14 && vers->minor == 0) { return vers->hotfix >= 11 ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } return FU_MEI_ISSUE_NOT_VULNERABLE; } FuMeiIssue fu_mei_common_is_txe_vulnerable(FuMeiVersion *vers) { if (vers->major == 3 && vers->minor == 1) return vers->hotfix >= 70 ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; if (vers->major == 4 && vers->minor == 0) return vers->hotfix >= 20 ? 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; } /* HFS1[3:0] Current Working State Values */ static const char *me_cws_values[] = { [ME_HFS_CWS_RESET] = "reset", [ME_HFS_CWS_INIT] = "initializing", [ME_HFS_CWS_REC] = "recovery", [ME_HFS_CWS_TEST] = "test", [ME_HFS_CWS_DISABLED] = "disabled", [ME_HFS_CWS_NORMAL] = "normal", [ME_HFS_CWS_WAIT] = "wait", [ME_HFS_CWS_TRANS] = "transition", [ME_HFS_CWS_INVALID] = "invalid", }; /* HFS1[8:6] Current Operation State Values */ static const char *me_opstate_values[] = { [ME_HFS_STATE_PREBOOT] = "preboot", [ME_HFS_STATE_M0_UMA] = "m0-with-uma", [ME_HFS_STATE_M3] = "m3-without-uma", [ME_HFS_STATE_M0] = "m0-without-uma", [ME_HFS_STATE_BRINGUP] = "bring-up", [ME_HFS_STATE_ERROR] = "error", }; /* HFS[19:16] Current Operation Mode Values */ static const char *me_opmode_values[] = { [ME_HFS_MODE_NORMAL] = "normal", [ME_HFS_MODE_DEBUG] = "debug", [ME_HFS_MODE_DIS] = "disable", [ME_HFS_MODE_OVER_JMPR] = "override-jumper", [ME_HFS_MODE_OVER_MEI] = "override-mei", [ME_HFS_MODE_UNKNOWN_6] = "unknown-6", [ME_HFS_MODE_MAYBE_SPS] = "maybe-sps", }; /* HFS[15:12] Error Code Values */ static const char *me_error_values[] = { [ME_HFS_ERROR_NONE] = "no-error", [ME_HFS_ERROR_UNCAT] = "uncategorized-failure", [ME_HFS_ERROR_DISABLED] = "disabled", [ME_HFS_ERROR_IMAGE] = "image-failure", [ME_HFS_ERROR_DEBUG] = "debug-failure", }; void fu_mei_hfsts1_to_string(FuMeiHfsts1 hfsts1, guint idt, GString *str) { fu_common_string_append_kv(str, idt, "WorkingState", me_cws_values[hfsts1.fields.working_state]); fu_common_string_append_kb(str, idt, "MfgMode", hfsts1.fields.mfg_mode); fu_common_string_append_kb(str, idt, "FptBad", hfsts1.fields.fpt_bad); fu_common_string_append_kv(str, idt, "OperationState", me_opstate_values[hfsts1.fields.operation_state]); fu_common_string_append_kb(str, idt, "FwInitComplete", hfsts1.fields.fw_init_complete); fu_common_string_append_kb(str, idt, "FtBupLdFlr", hfsts1.fields.ft_bup_ld_flr); fu_common_string_append_kb(str, idt, "UpdateInProgress", hfsts1.fields.update_in_progress); fu_common_string_append_kv(str, idt, "ErrorCode", me_error_values[hfsts1.fields.error_code]); fu_common_string_append_kv(str, idt, "OperationMode", me_opmode_values[hfsts1.fields.operation_mode]); fu_common_string_append_kx(str, idt, "ResetCount", hfsts1.fields.reset_count); fu_common_string_append_kb(str, idt, "BootOptions_present", hfsts1.fields.boot_options_present); fu_common_string_append_kb(str, idt, "BistFinished", hfsts1.fields.bist_finished); fu_common_string_append_kb(str, idt, "BistTestState", hfsts1.fields.bist_test_state); fu_common_string_append_kb(str, idt, "BistResetRequest", hfsts1.fields.bist_reset_request); fu_common_string_append_kx(str, idt, "CurrentPowerSource", hfsts1.fields.current_power_source); fu_common_string_append_kb(str, idt, "D3SupportValid", hfsts1.fields.d3_support_valid); fu_common_string_append_kb(str, idt, "D0i3SupportValid", hfsts1.fields.d0i3_support_valid); } void fu_mei_hfsts2_to_string(FuMeiHfsts2 hfsts2, guint idt, GString *str) { fu_common_string_append_kb(str, idt, "NftpLoadFailure", hfsts2.fields.nftp_load_failure); fu_common_string_append_kx(str, idt, "IccProgStatus", hfsts2.fields.icc_prog_status); fu_common_string_append_kb(str, idt, "InvokeMebx", hfsts2.fields.invoke_mebx); fu_common_string_append_kb(str, idt, "CpuReplaced", hfsts2.fields.cpu_replaced); fu_common_string_append_kb(str, idt, "Rsvd0", hfsts2.fields.rsvd0); fu_common_string_append_kb(str, idt, "MfsFailure", hfsts2.fields.mfs_failure); fu_common_string_append_kb(str, idt, "WarmResetRqst", hfsts2.fields.warm_reset_rqst); fu_common_string_append_kb(str, idt, "CpuReplacedValid", hfsts2.fields.cpu_replaced_valid); fu_common_string_append_kb(str, idt, "LowPowerState", hfsts2.fields.low_power_state); fu_common_string_append_kb(str, idt, "MePowerGate", hfsts2.fields.me_power_gate); fu_common_string_append_kb(str, idt, "IpuNeeded", hfsts2.fields.ipu_needed); fu_common_string_append_kb(str, idt, "ForcedSafeBoot", hfsts2.fields.forced_safe_boot); fu_common_string_append_kx(str, idt, "Rsvd1", hfsts2.fields.rsvd1); fu_common_string_append_kb(str, idt, "ListenerChange", hfsts2.fields.listener_change); fu_common_string_append_kx(str, idt, "StatusData", hfsts2.fields.status_data); fu_common_string_append_kx(str, idt, "CurrentPmevent", hfsts2.fields.current_pmevent); fu_common_string_append_kx(str, idt, "Phase", hfsts2.fields.phase); } void fu_mei_hfsts3_to_string(FuMeiHfsts3 hfsts3, guint idt, GString *str) { fu_common_string_append_kx(str, idt, "Chunk0", hfsts3.fields.chunk0); fu_common_string_append_kx(str, idt, "Chunk1", hfsts3.fields.chunk1); fu_common_string_append_kx(str, idt, "Chunk2", hfsts3.fields.chunk2); fu_common_string_append_kx(str, idt, "Chunk3", hfsts3.fields.chunk3); fu_common_string_append_kx(str, idt, "FwSku", hfsts3.fields.fw_sku); fu_common_string_append_kb(str, idt, "EncryptKeyCheck", hfsts3.fields.encrypt_key_check); fu_common_string_append_kb(str, idt, "PchConfigChange", hfsts3.fields.pch_config_change); fu_common_string_append_kb(str, idt, "IbbVerificationResult", hfsts3.fields.ibb_verification_result); fu_common_string_append_kb(str, idt, "IbbVerificationDone", hfsts3.fields.ibb_verification_done); fu_common_string_append_kx(str, idt, "Reserved11", hfsts3.fields.reserved_11); fu_common_string_append_kx(str, idt, "ActualIbbSize", hfsts3.fields.actual_ibb_size * 1024); fu_common_string_append_ku(str, idt, "NumberOfChunks", hfsts3.fields.number_of_chunks); fu_common_string_append_kb(str, idt, "EncryptKeyOverride", hfsts3.fields.encrypt_key_override); fu_common_string_append_kb(str, idt, "PowerDownMitigation", hfsts3.fields.power_down_mitigation); } void fu_mei_hfsts4_to_string(FuMeiHfsts4 hfsts4, guint idt, GString *str) { fu_common_string_append_kx(str, idt, "Rsvd0", hfsts4.fields.rsvd0); fu_common_string_append_kb(str, idt, "EnforcementFlow", hfsts4.fields.enforcement_flow); fu_common_string_append_kb(str, idt, "SxResumeType", hfsts4.fields.sx_resume_type); fu_common_string_append_kb(str, idt, "Rsvd1", hfsts4.fields.rsvd1); fu_common_string_append_kb(str, idt, "TpmsDisconnected", hfsts4.fields.tpms_disconnected); fu_common_string_append_kb(str, idt, "Rvsd2", hfsts4.fields.rvsd2); fu_common_string_append_kb(str, idt, "FwstsValid", hfsts4.fields.fwsts_valid); fu_common_string_append_kb(str, idt, "BootGuardSelfTest", hfsts4.fields.boot_guard_self_test); fu_common_string_append_kx(str, idt, "Rsvd3", hfsts4.fields.rsvd3); } void fu_mei_hfsts5_to_string(FuMeiHfsts5 hfsts5, guint idt, GString *str) { fu_common_string_append_kb(str, idt, "AcmActive", hfsts5.fields.acm_active); fu_common_string_append_kb(str, idt, "Valid", hfsts5.fields.valid); fu_common_string_append_kb(str, idt, "ResultCodeSource", hfsts5.fields.result_code_source); fu_common_string_append_kx(str, idt, "ErrorStatusCode", hfsts5.fields.error_status_code); fu_common_string_append_kx(str, idt, "AcmDoneSts", hfsts5.fields.acm_done_sts); fu_common_string_append_kx(str, idt, "TimeoutCount", hfsts5.fields.timeout_count); fu_common_string_append_kb(str, idt, "ScrtmIndicator", hfsts5.fields.scrtm_indicator); fu_common_string_append_kx(str, idt, "IncBootGuardAcm", hfsts5.fields.inc_boot_guard_acm); fu_common_string_append_kx(str, idt, "IncKeyManifest", hfsts5.fields.inc_key_manifest); fu_common_string_append_kx(str, idt, "IncBootPolicy", hfsts5.fields.inc_boot_policy); fu_common_string_append_kx(str, idt, "Rsvd0", hfsts5.fields.rsvd0); fu_common_string_append_kb(str, idt, "StartEnforcement", hfsts5.fields.start_enforcement); } void fu_mei_hfsts6_to_string(FuMeiHfsts6 hfsts6, guint idt, GString *str) { fu_common_string_append_kb(str, idt, "ForceBootGuardAcm", hfsts6.fields.force_boot_guard_acm); fu_common_string_append_kb(str, idt, "CpuDebugDisable", hfsts6.fields.cpu_debug_disable); fu_common_string_append_kb(str, idt, "BspInitDisable", hfsts6.fields.bsp_init_disable); fu_common_string_append_kb(str, idt, "ProtectBiosEnv", hfsts6.fields.protect_bios_env); fu_common_string_append_kx(str, idt, "Rsvd0", hfsts6.fields.rsvd0); fu_common_string_append_kx(str, idt, "ErrorEnforcePolicy", hfsts6.fields.error_enforce_policy); fu_common_string_append_kb(str, idt, "MeasuredBoot", hfsts6.fields.measured_boot); fu_common_string_append_kb(str, idt, "VerifiedBoot", hfsts6.fields.verified_boot); fu_common_string_append_kx(str, idt, "BootGuardAcmsvn", hfsts6.fields.boot_guard_acmsvn); fu_common_string_append_kx(str, idt, "Kmsvn", hfsts6.fields.kmsvn); fu_common_string_append_kx(str, idt, "Bpmsvn", hfsts6.fields.bpmsvn); fu_common_string_append_kx(str, idt, "KeyManifestId", hfsts6.fields.key_manifest_id); fu_common_string_append_kb(str, idt, "BootPolicyStatus", hfsts6.fields.boot_policy_status); fu_common_string_append_kb(str, idt, "Error", hfsts6.fields.error); fu_common_string_append_kb(str, idt, "BootGuardDisable", hfsts6.fields.boot_guard_disable); fu_common_string_append_kb(str, idt, "FpfDisable", hfsts6.fields.fpf_disable); fu_common_string_append_kb(str, idt, "FpfSocLock", hfsts6.fields.fpf_soc_lock); fu_common_string_append_kb(str, idt, "TxtSupport", hfsts6.fields.txt_support); } fwupd-1.7.5/plugins/pci-mei/fu-mei-common.h000066400000000000000000000126651420024370600205060ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_MEI_FAMILY_UNKNOWN, FU_MEI_FAMILY_SPS, FU_MEI_FAMILY_TXE, FU_MEI_FAMILY_ME, FU_MEI_FAMILY_CSME, } FuMeiFamily; 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; #define ME_HFS_CWS_RESET 0 #define ME_HFS_CWS_INIT 1 #define ME_HFS_CWS_REC 2 #define ME_HFS_CWS_TEST 3 #define ME_HFS_CWS_DISABLED 4 #define ME_HFS_CWS_NORMAL 5 #define ME_HFS_CWS_WAIT 6 #define ME_HFS_CWS_TRANS 7 #define ME_HFS_CWS_INVALID 8 #define ME_HFS_STATE_PREBOOT 0 #define ME_HFS_STATE_M0_UMA 1 #define ME_HFS_STATE_M3 4 #define ME_HFS_STATE_M0 5 #define ME_HFS_STATE_BRINGUP 6 #define ME_HFS_STATE_ERROR 7 #define ME_HFS_ERROR_NONE 0 #define ME_HFS_ERROR_UNCAT 1 #define ME_HFS_ERROR_DISABLED 2 #define ME_HFS_ERROR_IMAGE 3 #define ME_HFS_ERROR_DEBUG 4 #define ME_HFS_MODE_NORMAL 0 #define ME_HFS_MODE_DEBUG 2 #define ME_HFS_MODE_DIS 3 #define ME_HFS_MODE_OVER_JMPR 4 #define ME_HFS_MODE_OVER_MEI 5 #define ME_HFS_MODE_UNKNOWN_6 6 #define ME_HFS_MODE_MAYBE_SPS 7 #define ME_HFS_ENFORCEMENT_POLICY_NOTHING 0b00 #define ME_HFS_ENFORCEMENT_POLICY_SHUTDOWN_TO 0b01 #define ME_HFS_ENFORCEMENT_POLICY_SHUTDOWN_NOW 0b11 const gchar * fu_mei_common_family_to_string(FuMeiFamily family); 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.7.5/plugins/pci-mei/fu-plugin-pci-mei.c000066400000000000000000000400021420024370600212420ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-mei-common.h" struct FuPluginData { FuDevice *pci_device; FuMeiHfsts1 hfsts1; FuMeiHfsts2 hfsts2; FuMeiHfsts3 hfsts3; FuMeiHfsts4 hfsts4; FuMeiHfsts5 hfsts5; FuMeiHfsts6 hfsts6; FuMeiFamily family; FuMeiVersion vers; FuMeiIssue issue; }; #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_mei_hfsts_to_string(FuPlugin *plugin, guint idt, GString *str) { FuPluginData *priv = fu_plugin_get_data(plugin); fu_common_string_append_kv(str, idt, "HFSTS1", NULL); fu_mei_hfsts1_to_string(priv->hfsts1, idt + 1, str); fu_common_string_append_kv(str, idt, "HFSTS2", NULL); fu_mei_hfsts2_to_string(priv->hfsts2, idt + 1, str); fu_common_string_append_kv(str, idt, "HFSTS3", NULL); fu_mei_hfsts3_to_string(priv->hfsts3, idt + 1, str); fu_common_string_append_kv(str, idt, "HFSTS4", NULL); fu_mei_hfsts4_to_string(priv->hfsts4, idt + 1, str); fu_common_string_append_kv(str, idt, "HFSTS5", NULL); fu_mei_hfsts5_to_string(priv->hfsts5, idt + 1, str); fu_common_string_append_kv(str, idt, "HFSTS6", NULL); fu_mei_hfsts6_to_string(priv->hfsts6, idt + 1, str); } static void fu_plugin_pci_mei_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_udev_subsystem(plugin, "pci"); } static void fu_plugin_pci_mei_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->pci_device != NULL) g_object_unref(data->pci_device); } static FuMeiFamily fu_mei_detect_family(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); guint8 ver = priv->vers.major; if (ver == 1 || ver == 2) { if (priv->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) return FU_MEI_FAMILY_CSME; return FU_MEI_FAMILY_UNKNOWN; } static gboolean fu_mei_parse_fwvers(FuPlugin *plugin, const gchar *fwvers, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); 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 */ priv->vers.platform = fu_common_strtoull(sections[0]); 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; } priv->vers.major = fu_common_strtoull(split[0]); priv->vers.minor = fu_common_strtoull(split[1]); priv->vers.hotfix = fu_common_strtoull(split[2]); priv->vers.buildno = fu_common_strtoull(split[3]); /* check the AMT version for issues using the data from: * https://downloadcenter.intel.com/download/28632 */ priv->family = fu_mei_detect_family(plugin); if (priv->family == FU_MEI_FAMILY_CSME) priv->issue = fu_mei_common_is_csme_vulnerable(&priv->vers); else if (priv->family == FU_MEI_FAMILY_TXE) priv->issue = fu_mei_common_is_txe_vulnerable(&priv->vers); else if (priv->family == FU_MEI_FAMILY_SPS) priv->issue = fu_mei_common_is_sps_vulnerable(&priv->vers); if (g_getenv("FWUPD_MEI_VERBOSE") != NULL) { g_debug("%s version parsed as %u.%u.%u", fu_mei_common_family_to_string(priv->family), priv->vers.major, priv->vers.minor, priv->vers.hotfix); } return TRUE; } static gboolean fu_plugin_pci_mei_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data(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_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_1, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS1: "); return FALSE; } priv->hfsts1.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_2, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS2: "); return FALSE; } priv->hfsts2.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_3, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS3: "); return FALSE; } priv->hfsts3.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_4, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS4: "); return FALSE; } priv->hfsts4.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_5, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS5: "); return FALSE; } priv->hfsts5.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(device), PCI_CFG_HFS_6, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS6: "); return FALSE; } priv->hfsts6.data = fu_common_read_uint32(buf, G_LITTLE_ENDIAN); g_set_object(&priv->pci_device, device); /* dump to console */ if (g_getenv("FWUPD_PCI_MEI_VERBOSE") != NULL) { g_autoptr(GString) str = g_string_new(NULL); fu_mei_hfsts_to_string(plugin, 0, str); g_debug("\n%s", str->str); } /* 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) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_metadata(attr, "kind", fu_mei_common_family_to_string(priv->family)); fu_security_attrs_append(attrs, attr); /* Manufacturing Mode */ if (priv->hfsts1.fields.mfg_mode) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } static void fu_plugin_add_security_attrs_override_strap(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_metadata(attr, "kind", fu_mei_common_family_to_string(priv->family)); fu_security_attrs_append(attrs, attr); /* Flash Descriptor Security Override Strap */ if (priv->hfsts1.fields.operation_mode == ME_HFS_MODE_OVER_JMPR) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } static void fu_plugin_add_security_attrs_bootguard_enabled(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* not supported */ if (priv->family == FU_MEI_FAMILY_TXE) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fu_security_attrs_append(attrs, attr); /* disabled at runtime? */ if (priv->hfsts6.fields.boot_guard_disable) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_plugin_add_security_attrs_bootguard_verified(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* not supported */ if (priv->family == FU_MEI_FAMILY_TXE) return; /* disabled */ if (priv->hfsts6.fields.boot_guard_disable) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fu_security_attrs_append(attrs, attr); /* measured boot is not sufficient, verified is required */ if (!priv->hfsts6.fields.verified_boot) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_plugin_add_security_attrs_bootguard_acm(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* not supported */ if (priv->family == FU_MEI_FAMILY_TXE) return; /* disabled */ if (priv->hfsts6.fields.boot_guard_disable) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fu_security_attrs_append(attrs, attr); /* ACM protection required */ if (!priv->hfsts6.fields.force_boot_guard_acm) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_plugin_add_security_attrs_bootguard_policy(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* not supported */ if (priv->family == FU_MEI_FAMILY_TXE) return; /* disabled */ if (priv->hfsts6.fields.boot_guard_disable) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fu_security_attrs_append(attrs, attr); /* policy must be to immediately shutdown */ if (priv->hfsts6.fields.error_enforce_policy != ME_HFS_ENFORCEMENT_POLICY_SHUTDOWN_NOW) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_plugin_add_security_attrs_bootguard_otp(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* not supported */ if (priv->family == FU_MEI_FAMILY_TXE) return; /* disabled */ if (priv->hfsts6.fields.boot_guard_disable) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fu_security_attrs_append(attrs, attr); /* ensure vendor set the FPF OTP fuse */ if (!priv->hfsts6.fields.fpf_soc_lock) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } 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) { FuPluginData *priv = fu_plugin_get_data(plugin); g_autofree gchar *version = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; /* format version as string */ version = g_strdup_printf("%u:%u.%u.%u.%u", priv->vers.platform, priv->vers.major, priv->vers.minor, priv->vers.hotfix, priv->vers.buildno); if (priv->issue == FU_MEI_ISSUE_UNKNOWN) { g_warning("ME family not supported for %s", version); return; } /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_MEI_VERSION); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_metadata(attr, "kind", fu_mei_common_family_to_string(priv->family)); fwupd_security_attr_add_metadata(attr, "version", version); fu_security_attrs_append(attrs, attr); /* Flash Descriptor Security Override Strap */ if (priv->issue == FU_MEI_ISSUE_VULNERABLE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* success */ fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_pci_mei_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); /* only Intel */ if (fu_common_get_cpu_vendor() != FU_CPU_VENDOR_INTEL) return; if (priv->pci_device == NULL) return; 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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_pci_mei_init; vfuncs->destroy = fu_plugin_pci_mei_destroy; vfuncs->add_security_attrs = fu_plugin_pci_mei_add_security_attrs; vfuncs->backend_device_added = fu_plugin_pci_mei_backend_device_added; } fwupd-1.7.5/plugins/pci-mei/meson.build000066400000000000000000000010601420024370600200120ustar00rootroot00000000000000if get_option('hsi') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginPciMei"'] install_data(['pci-mei.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_pci_mei', fu_hash, sources : [ 'fu-plugin-pci-mei.c', 'fu-mei-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/pci-mei/pci-mei.quirk000066400000000000000000000000451420024370600202520ustar00rootroot00000000000000[PCI\DRIVER_mei_me] Plugin = pci_mei fwupd-1.7.5/plugins/pixart-rf/000077500000000000000000000000001420024370600162445ustar00rootroot00000000000000fwupd-1.7.5/plugins/pixart-rf/README.md000066400000000000000000000020601420024370600175210ustar00rootroot00000000000000# PixArt RF Devices ## 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. fwupd-1.7.5/plugins/pixart-rf/fu-plugin-pixart-rf.c000066400000000000000000000012511420024370600222270ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-pxi-ble-device.h" #include "fu-pxi-firmware.h" #include "fu-pxi-receiver-device.h" static void fu_plugin_pixart_init(FuPlugin *plugin) { 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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_pixart_init; } fwupd-1.7.5/plugins/pixart-rf/fu-pxi-ble-device.c000066400000000000000000000650611420024370600216250ustar00rootroot00000000000000/* * 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 #include "fu-pxi-ble-device.h" #include "fu-pxi-common.h" #include "fu-pxi-firmware.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 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 10 /* 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; 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, 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_common_string_append_kv(str, idt, "ModelName", self->model_name); fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str); fu_common_string_append_kx(str, idt, "RetransmitID", self->retransmit_id); } static FuFirmware * fu_pxi_ble_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); const gchar *model_name; g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* 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; } } 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, error); } #endif static gboolean fu_pxi_ble_device_set_feature(FuPxiBleDevice *self, GByteArray *req, GError **error) { #ifdef HAVE_HIDRAW_H if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_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, error)) { return FALSE; } if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_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] != PXI_HID_DEV_OTA_FEATURE_REPORT_ID) { g_debug("doing fixup for old bluez version"); memmove(buf + 2, buf, bufsz - 2); buf[0] = PXI_HID_DEV_OTA_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_usage_page(guint8 *report_descriptor, gint size, guint8 *usage_page, guint8 usage_page_sz) { gint pos = 0; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "target usage_page", usage_page, usage_page_sz); } while (pos < size) { /* HID info define by HID specification */ guint8 item = report_descriptor[pos]; guint8 report_size = item & 0x03; guint8 report_tag = item & 0xF0; guint8 usage_page_tmp[4] = {0x00}; report_size = (report_size == 3) ? 4 : report_size; if (report_tag != 0) { pos += report_size + 1; continue; } memmove(usage_page_tmp, &report_descriptor[pos + 1], report_size); if (memcmp(usage_page, usage_page_tmp, usage_page_sz) == 0) { if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { g_debug("hit item: %x ", item); fu_common_dump_raw(G_LOG_DOMAIN, "usage_page", usage_page, report_size); g_debug("hit pos %d", pos); } return TRUE; /* finished processing */ } pos += report_size + 1; } return FALSE; /* finished processing */ } static gboolean fu_pxi_ble_device_check_support_report_id(FuPxiBleDevice *self, GError **error) { #ifdef HAVE_HIDRAW_H gint desc_size = 0; g_autoptr(GByteArray) req = g_byte_array_new(); struct hidraw_report_descriptor rpt_desc; /* Get Report Descriptor Size */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESCSIZE, (guint8 *)&desc_size, NULL, error)) return FALSE; rpt_desc.size = desc_size; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESC, (guint8 *)&rpt_desc, NULL, error)) return FALSE; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "HID descriptor", rpt_desc.value, rpt_desc.size); /* check ota retransmit feature report usage page exist or not */ fu_byte_array_append_uint16(req, PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, G_LITTLE_ENDIAN); if (!fu_pxi_ble_device_search_hid_usage_page(rpt_desc.value, rpt_desc.size, req->data, req->len)) { /* replace retransmit report id with feature report id, if retransmit report id not * found */ self->retransmit_id = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; } 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(GPtrArray) 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, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); if (self->fwstate.offset > chunks->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "offset from device is invalid: " "got 0x%x, current maximum 0x%x", self->fwstate.offset, chunks->len); return FALSE; } /* calculate device current checksum */ for (guint i = 0; i < self->fwstate.offset; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); checksum_tmp += fu_common_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_full(FU_UDEV_DEVICE(self), port, res, (FU_PXI_BLE_DEVICE_NOTIFY_RET_LEN + 1) - port, error)) return FALSE; if (res[0] == PXI_HID_DEV_OTA_INPUT_REPORT_ID) break; } /* timeout */ if (res[0] != PXI_HID_DEV_OTA_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_common_read_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_common_read_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_common_read_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, PXI_HID_DEV_OTA_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, PXI_HID_DEV_OTA_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(GPtrArray) chunks = NULL; /* 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(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk), 0x0, self->fwstate.mtu_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk2 = g_ptr_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 == chunks->len - 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_common_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, PXI_HID_DEV_OTA_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, PXI_HID_DEV_OTA_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, PXI_HID_DEV_OTA_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 */ g_usleep(10 * 1000); /* read fw ota init new command */ res[0] = PXI_HID_DEV_OTA_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 != OTA_SPEC_CHECK_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwInitNew spec check fail: %s [0x%02x]", fu_pxi_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_common_sum16_bytes(fw); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_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); 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; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_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(GPtrArray) chunks = NULL; g_autoptr(GError) error_local = 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, 9); /* ota-init */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1); /* 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, 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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; g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_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 */ g_usleep(10 * 1000); res[0] = PXI_HID_DEV_OTA_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_common_read_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 */ version_str = g_strndup((gchar *)res + 0x6, 5); fu_device_set_version(FU_DEVICE(self), version_str); /* add current checksum */ if (!fu_common_read_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, PXI_HID_DEV_OTA_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 */ g_usleep(10 * 1000); res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; if (!fu_pxi_ble_device_get_feature(self, res, sizeof(res), error)) return FALSE; if (!fu_common_read_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 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_ble_device_get_raw_info(self, &hid_raw_info, error)) return FALSE; dev_name = g_string_new(fu_device_get_name(FU_DEVICE(self))); g_string_ascii_up(dev_name); fu_common_string_replace(dev_name, " ", "_"); 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(self), devid); /* extra GUID with model name*/ if (self->model_name != NULL) { g_autofree gchar *devid2 = NULL; g_autoptr(GString) model_name = NULL; model_name = g_string_new(self->model_name); g_string_ascii_up(model_name); fu_common_string_replace(model_name, " ", "_"); devid2 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&MODEL_%s", (guint)hid_raw_info.vendor, (guint)hid_raw_info.product, model_name->str); fu_device_add_instance_id(FU_DEVICE(self), devid2); } #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_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_pxi_ble_device_init(FuPxiBleDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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; } 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.7.5/plugins/pixart-rf/fu-pxi-ble-device.h000066400000000000000000000005421420024370600216230ustar00rootroot00000000000000/* * 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.7.5/plugins/pixart-rf/fu-pxi-common.c000066400000000000000000000111541420024370600211100ustar00rootroot00000000000000/* * Copyright (C) 2021 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pxi-common.h" const gchar * fu_pxi_spec_check_result_to_string(guint8 spec_check_result) { if (spec_check_result == OTA_SPEC_CHECK_OK) return "ok"; if (spec_check_result == OTA_FW_OUT_OF_BOUNDS) return "fw-out-of-bounds"; if (spec_check_result == OTA_PROCESS_ILLEGAL) return "process-illegal"; if (spec_check_result == OTA_RECONNECT) return "reconnect"; if (spec_check_result == OTA_FW_IMG_VERSION_ERROR) return "fw-img-version-error"; if (spec_check_result == OTA_DEVICE_LOW_BATTERY) return "device-low-battery"; return NULL; } const gchar * fu_pxi_receiver_cmd_result_to_string(guint8 result) { if (result == OTA_RSP_OK) return "ok"; if (result == OTA_RSP_FINISH) return "ota-response-finish"; if (result == OTA_RSP_FAIL) return "ota-response-fail"; if (result == OTA_RSP_CODE_ERROR) return "ota-response-error"; return NULL; } void fu_pxi_ota_fw_state_to_string(struct ota_fw_state *fwstate, guint idt, GString *str) { fu_common_string_append_kx(str, idt, "Status", fwstate->status); fu_common_string_append_kx(str, idt, "NewFlow", fwstate->new_flow); fu_common_string_append_kx(str, idt, "CurrentObjectOffset", fwstate->offset); fu_common_string_append_kx(str, idt, "CurrentChecksum", fwstate->checksum); fu_common_string_append_kx(str, idt, "MaxObjectSize", fwstate->max_object_size); fu_common_string_append_kx(str, idt, "MtuSize", fwstate->mtu_size); fu_common_string_append_kx(str, idt, "PacketReceiptNotificationThreshold", fwstate->prn_threshold); fu_common_string_append_kv(str, idt, "SpecCheckResult", fu_pxi_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_common_read_uint8_safe(buf, bufsz, offset + 0x00, &fwstate->status, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, bufsz, offset + 0x01, &fwstate->new_flow, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, offset + 0x2, &fwstate->offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, offset + 0x4, &fwstate->checksum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, offset + 0x06, &fwstate->max_object_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, offset + 0x0A, &fwstate->mtu_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, offset + 0x0C, &fwstate->prn_threshold, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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); /* wirelss module ota op code */ fu_byte_array_append_uint8(wireless_mod_cmd, ota_sn); /* wirelss 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 */ len = wireless_mod_cmd->len; 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_common_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; } fwupd-1.7.5/plugins/pixart-rf/fu-pxi-common.h000066400000000000000000000070031420024370600211130ustar00rootroot00000000000000/* * Copyright (C) 2021 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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_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 /* OTA spec check result */ enum ota_spec_check_result { OTA_SPEC_CHECK_OK = 1, /* Spec check ok */ OTA_FW_OUT_OF_BOUNDS = 2, /* OTA firmware size out of bound */ OTA_PROCESS_ILLEGAL = 3, /* Illegal OTA process */ OTA_RECONNECT = 4, /* Inform OTA app do reconnect */ OTA_FW_IMG_VERSION_ERROR = 5, /* FW image file version check error */ OTA_DEVICE_LOW_BATTERY = 6, /* Device is under low battery */ OTA_SPEC_CHECK_MAX_NUM, /* Max number of OTA driver defined error code */ }; /* 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, }; /* ota rsp code for wireless module */ enum wireless_module_ota_rsp_code { OTA_RSP_OK, OTA_RSP_FINISH, OTA_RSP_FAIL, OTA_RSP_CODE_ERROR, OTA_RSP_WRITE_PKT_LEN_ERROR, OTA_RSP_WRITE_PKT_TOTAL_SIZE_ERROR, OTA_RSP_READ_PKT_LEN_ERROR, OTA_RSP_NOT_READY, }; 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); const gchar * fu_pxi_receiver_cmd_result_to_string(guint8 result); const gchar * fu_pxi_spec_check_result_to_string(guint8 spec_check_result); 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); fwupd-1.7.5/plugins/pixart-rf/fu-pxi-firmware.c000066400000000000000000000140001420024370600214250ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-pxi-firmware.h" #define PIXART_RF_FW_HEADER_SIZE 32 /* bytes */ #define PIXART_RF_FW_HEADER_TAG_OFFSET 24 struct _FuPxiFirmware { FuFirmware parent_instance; gchar *model_name; }; 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_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); const guint8 *buf; const guint8 tag[] = { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, }; 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}; g_autofree gchar *version = NULL; /* get buf */ buf = g_bytes_get_data(fw, &bufsz); if (bufsz < sizeof(fw_header)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware invalid, too small!"); return FALSE; } /* get fw header from buf */ 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; } if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "fw header", fw_header, sizeof(fw_header)); } /* check the tag from fw header is correct */ for (guint32 i = 0x0; i < sizeof(tag); i++) { guint8 tmp = 0; if (!fu_common_read_uint8_safe(fw_header, sizeof(fw_header), i + PIXART_RF_FW_HEADER_TAG_OFFSET, &tmp, error)) return FALSE; if (tmp != tag[i]) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Fw tag is incorrect"); return FALSE; } } /* set fw version */ version_raw = (((guint32)(fw_header[0] - '0')) << 16) + (((guint32)(fw_header[2] - '0')) << 8) + (guint32)(fw_header[4] - '0'); fu_firmware_set_version_raw(firmware, version_raw); version = fu_common_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_DELL_BIOS); 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 */ fu_firmware_set_bytes(firmware, fw); 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 GBytes * 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; 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_byte_array_free_to_bytes(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->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)); } fwupd-1.7.5/plugins/pixart-rf/fu-pxi-firmware.h000066400000000000000000000006741420024370600214460ustar00rootroot00000000000000/* * 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); fwupd-1.7.5/plugins/pixart-rf/fu-pxi-receiver-device.c000066400000000000000000000634151420024370600226700ustar00rootroot00000000000000/* * 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 #include "fu-pxi-firmware.h" #include "fu-pxi-receiver-device.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, 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_common_string_append_kx(str, idt, "Vendor", self->vendor); fu_common_string_append_kx(str, idt, "Product", self->product); } static gboolean fu_pxi_receiver_device_set_feature(FuPxiReceiverDevice *self, const guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "SetFeature", buf, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(bufsz), (guint8 *)buf, NULL, 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, error)) { return FALSE; } if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_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 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 */ g_usleep(5 * 1000); 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; g_usleep(5 * 1000); if (!fu_pxi_receiver_device_get_feature(device, buf, bufsz, error)) return FALSE; if (!fu_common_read_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_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status == 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_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_receiver_cmd_result_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); 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_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; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; /* get the write payload command response */ if (!fu_pxi_receiver_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_receiver_cmd_result_to_string(status), status); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_write_chunk(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint32 prn = 0; guint16 checksum; g_autoptr(GPtrArray) chunks = NULL; /* 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(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk), 0x0, self->fwstate.mtu_size); /* the checksum of chunk */ checksum = fu_common_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); self->fwstate.checksum += checksum; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk2 = g_ptr_array_index(chunks, i); 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 == chunks->len - 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_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 5); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 95); 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_common_sum16_bytes(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command checksum */ 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)); if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_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 */ g_usleep(5 * 1000); if (!fu_pxi_receiver_device_get_cmd_response(self, res, sizeof(res), error)) return FALSE; if (!fu_common_read_uint8_safe(res, sizeof(res), 0x5, &result, error)) return FALSE; if (result != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_receiver_cmd_result_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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1); /* 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, 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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 */ g_usleep(5 * 1000); /* 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, GError **error) { guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint16 checksum = 0; 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); 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; g_usleep(5 * 1000); buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (!fu_pxi_receiver_device_get_feature(device, buf, sizeof(buf), error)) return FALSE; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "model_info", buf, sizeof(buf)); if (!fu_common_read_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_common_read_uint8_safe(buf, sizeof(buf), 0x16, &model->type, error)) return FALSE; if (!fu_common_read_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_common_read_uint16_safe(buf, sizeof(buf), 0x1D, &checksum, G_LITTLE_ENDIAN, error)) return FALSE; /* set current version and model name */ model->checksum = checksum; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { g_autofree gchar *version_str = g_strndup((gchar *)model->version, 5); g_debug("checksum %x", model->checksum); 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 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; g_usleep(5 * 1000); buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (!fu_pxi_receiver_device_get_feature(device, buf, sizeof(buf), error)) return FALSE; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "buf from get model num", buf, sizeof(buf)); } if (!fu_common_read_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}; g_autofree gchar *instance_id = NULL; 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, error)) return FALSE; model_version = g_strndup((gchar *)model.version, 5); model_name = g_strndup((gchar *)model.name, FU_PXI_DEVICE_MODEL_NAME_LEN); instance_id = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&MODEL_%s", device->vendor, device->product, model_name); if (model.type == OTA_WIRELESS_MODULE_TYPE_RECEIVER) { fu_device_set_version(FU_DEVICE(device), model_version); fu_device_add_instance_id(FU_DEVICE(device), instance_id); } else { g_autoptr(FuPxiWirelessDevice) wireless_device = fu_pxi_wireless_device_new(&model); g_autofree gchar *logical_id = g_strdup_printf("IDX:0x%02x", idx); fu_device_add_instance_id(FU_DEVICE(wireless_device), instance_id); fu_device_set_name(FU_DEVICE(wireless_device), model_name); fu_device_set_version(FU_DEVICE(wireless_device), model_version); fu_device_set_logical_id(FU_DEVICE(wireless_device), logical_id); fu_device_add_child(FU_DEVICE(device), FU_DEVICE(wireless_device)); } 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); fu_common_string_replace(dev_name, " ", "_"); 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; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) 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_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_pxi_receiver_device_init(FuPxiReceiverDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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_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->set_progress = fu_pxi_receiver_device_set_progress; } fwupd-1.7.5/plugins/pixart-rf/fu-pxi-receiver-device.h000066400000000000000000000006311420024370600226640ustar00rootroot00000000000000/* * 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.7.5/plugins/pixart-rf/fu-pxi-wireless-device.c000066400000000000000000000472111420024370600227150ustar00rootroot00000000000000/* * 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 #include "fu-pxi-common.h" #include "fu-pxi-firmware.h" #include "fu-pxi-receiver-device.h" #include "fu-pxi-wireless-device.h" #define FU_PXI_WIRELESS_DEV_DELAY_US 50000 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_common_string_append_kv(str, idt, "ModelName", (gchar *)self->model.name); fu_common_string_append_kx(str, idt, "ModelType", self->model.type); fu_common_string_append_kx(str, idt, "ModelTarget", self->model.target); } static gboolean fu_pxi_wireless_device_set_feature(FuDevice *self, const guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "SetFeature", buf, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(bufsz), (guint8 *)buf, NULL, 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, error)) { return FALSE; } if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_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 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 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; g_usleep(5 * 1000); if (!fu_pxi_wireless_device_get_feature(FU_DEVICE(parent), buf, bufsz, error)) return FALSE; if (!fu_common_read_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_common_read_uint8_safe(buf, bufsz, 0x5, &status, error)) return FALSE; if (status == OTA_RSP_NOT_READY) { retry = 0x0; g_debug("OTA_RSP_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, 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; if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "crc buf", buf, sizeof(buf)); if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, sizeof(buf), 0x6, &checksum_device, G_LITTLE_ENDIAN, error)) return FALSE; if (status == 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 != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "status:%s", fu_pxi_receiver_cmd_result_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); 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; /* delay for wireless module device get command response*/ g_usleep(FU_PXI_WIRELESS_DEV_DELAY_US); return fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error); } static gboolean fu_pxi_wireless_device_write_payload(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) 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 wireless module device get command response*/ g_usleep(FU_PXI_WIRELESS_DEV_DELAY_US); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_receiver_cmd_result_to_string(status), status); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_write_chunk(FuDevice *device, FuChunk *chk, GError **error) { FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint16 checksum; guint32 prn = 0; g_autoptr(GPtrArray) chunks = NULL; /* 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(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk), 0x0, self->fwstate.mtu_size); /* calculate checksum of chunk */ checksum = fu_common_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); self->fwstate.checksum += checksum; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk2 = g_ptr_array_index(chunks, i); 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 == (chunks->len - 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_init_new(FuDevice *device, gsize bufsz, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); 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 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; 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_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*/ g_usleep(FU_PXI_WIRELESS_DEV_DELAY_US); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != OTA_RSP_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_receiver_cmd_result_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); 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_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 5); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 95); /* 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_common_sum16_bytes(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command checksum */ 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; 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1); /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) 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, 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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 */ g_usleep(FU_PXI_WIRELESS_DEV_DELAY_US); 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_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_pxi_wireless_device_init(FuPxiWirelessDevice *self) { 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_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->set_progress = fu_pxi_wireless_device_set_progress; } FuPxiWirelessDevice * fu_pxi_wireless_device_new(struct ota_fw_dev_model *model) { FuPxiWirelessDevice *self = NULL; self = g_object_new(FU_TYPE_PXI_WIRELESS_DEVICE, 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.7.5/plugins/pixart-rf/fu-pxi-wireless-device.h000066400000000000000000000007141420024370600227170ustar00rootroot00000000000000/* * 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(struct ota_fw_dev_model *model); fwupd-1.7.5/plugins/pixart-rf/fu-self-test.c000066400000000000000000000033001420024370600207220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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(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); 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) { 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.7.5/plugins/pixart-rf/meson.build000066400000000000000000000027331420024370600204130ustar00rootroot00000000000000if get_option('plugin_pixart_rf') and host_machine.system() == 'linux' if not get_option('gudev') error('gudev is required for plugin_pixart_rf') endif cargs = ['-DG_LOG_DOMAIN="FuPluginPixartRf"'] install_data(['pixart-rf.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_pixart_rf', fu_hash, sources : [ 'fu-plugin-pixart-rf.c', 'fu-pxi-common.c', 'fu-pxi-ble-device.c', 'fu-pxi-receiver-device.c', 'fu-pxi-wireless-device.c', 'fu-pxi-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-pxi-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('pxi-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/pixart-rf/pixart-rf.quirk000066400000000000000000000060221420024370600212350ustar00rootroot00000000000000# 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 [HIDRAW\VEN_04F2&DEV_2199] 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 # 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 fwupd-1.7.5/plugins/pixart-rf/tests/000077500000000000000000000000001420024370600174065ustar00rootroot00000000000000fwupd-1.7.5/plugins/pixart-rf/tests/pixart.bin000066400000000000000000000000531420024370600214050ustar00rootroot00000000000000hello world1.2.3TestUUUUfwupd-1.7.5/plugins/pixart-rf/tests/pixart.builder.xml000066400000000000000000000002451420024370600230650ustar00rootroot00000000000000 0x00010203 Test aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/platform-integrity/000077500000000000000000000000001420024370600201705ustar00rootroot00000000000000fwupd-1.7.5/plugins/platform-integrity/README.md000066400000000000000000000003731420024370600214520ustar00rootroot00000000000000# Platform Integrity ## 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 `/sys/class/platform-integrity` fwupd-1.7.5/plugins/platform-integrity/fu-plugin-platform-integrity.c000066400000000000000000000131411420024370600261000ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { gchar *sysfs_path; }; static void fu_plugin_platform_integrity_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_udev_subsystem(plugin, "platform-integrity"); } static void fu_plugin_platform_integrity_destroy(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); g_free(priv->sysfs_path); } static gboolean fu_plugin_platform_integrity_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "platform-integrity") != 0) return TRUE; /* we only care about the first instance */ if (priv->sysfs_path != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only one platform-integrity device supported; already using %s", priv->sysfs_path); return FALSE; } /* success */ priv->sysfs_path = g_strdup(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))); return TRUE; } static void fu_plugin_add_security_attr_bioswe(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_obsolete(attr, "pci_bcr"); fu_security_attrs_append(attrs, attr); /* load file */ fn = g_build_filename(priv->sysfs_path, "bioswe", NULL); if (!g_file_get_contents(fn, &buf, &bufsz, &error_local)) { 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_strcmp0(buf, "0\n") != 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); } static void fu_plugin_add_security_attr_ble(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_BLE); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_obsolete(attr, "pci_bcr"); fu_security_attrs_append(attrs, attr); /* load file */ fn = g_build_filename(priv->sysfs_path, "biosle", NULL); if (!g_file_get_contents(fn, &buf, &bufsz, &error_local)) { 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_strcmp0(buf, "1\n") != 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); } static void fu_plugin_add_security_attr_smm_bwp(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_obsolete(attr, "pci_bcr"); fu_security_attrs_append(attrs, attr); /* load file */ fn = g_build_filename(priv->sysfs_path, "smm_bioswp", NULL); if (!g_file_get_contents(fn, &buf, &bufsz, &error_local)) { 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_strcmp0(buf, "1\n") != 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); } static void fu_plugin_platform_integrity_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); /* only when the kernel module is available */ if (priv->sysfs_path == NULL) return; /* look for the three files in sysfs */ 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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_platform_integrity_init; vfuncs->destroy = fu_plugin_platform_integrity_destroy; vfuncs->backend_device_added = fu_plugin_platform_integrity_backend_device_added; vfuncs->add_security_attrs = fu_plugin_platform_integrity_add_security_attrs; } fwupd-1.7.5/plugins/platform-integrity/fwupd-platform-integrity.conf000066400000000000000000000000231420024370600260150ustar00rootroot00000000000000platform-integrity fwupd-1.7.5/plugins/platform-integrity/meson.build000066400000000000000000000013161420024370600223330ustar00rootroot00000000000000if get_option('hsi') and get_option('plugin_platform_integrity') cargs = ['-DG_LOG_DOMAIN="FuPluginPlatformIntegrity"'] install_data([ 'platform-integrity.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) if get_option('systemd') install_data(['fwupd-platform-integrity.conf'], install_dir: systemd_modules_load_dir, ) endif shared_module('fu_plugin_platform_integrity', fu_hash, sources : [ 'fu-plugin-platform-integrity.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/platform-integrity/platform-integrity.quirk000066400000000000000000000001361420024370600251050ustar00rootroot00000000000000# match all devices with this udev subsystem [PLATFORM-INTEGRITY] Plugin = platform_integrity fwupd-1.7.5/plugins/powerd/000077500000000000000000000000001420024370600156305ustar00rootroot00000000000000fwupd-1.7.5/plugins/powerd/README.md000066400000000000000000000007111420024370600171060ustar00rootroot00000000000000Powerd Support ============== 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. fwupd-1.7.5/plugins/powerd/fu-plugin-powerd.c000066400000000000000000000106451420024370600212060ustar00rootroot00000000000000/* * Copyright (C) 2021 Twain Byrnes * Copyright (C) 2021 George Popoola * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { GDBusProxy *proxy; /* nullable */ }; static void fu_plugin_powerd_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static gboolean fu_plugin_powerd_create_suspend_file(GError **error) { g_autofree gchar *lockdir = NULL; g_autofree gchar *inhibitsuspend_filename = NULL; g_autofree gchar *getpid_str = NULL; lockdir = fu_common_get_path(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_plugin_powerd_delete_suspend_file(GError **error) { g_autoptr(GError) local_error = NULL; g_autofree gchar *lockdir = NULL; g_autoptr(GFile) inhibitsuspend_file = NULL; lockdir = fu_common_get_path(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_plugin_powerd_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->proxy != NULL) g_object_unref(data->proxy); } static void fu_plugin_powerd_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 = FU_BATTERY_VALUE_INVALID; fu_context_set_battery_state(ctx, current_state); fu_context_set_battery_level(ctx, current_level); } static void fu_plugin_powerd_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_plugin_powerd_rescan(plugin, parameters); } static gboolean fu_plugin_powerd_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *name_owner = NULL; if (!fu_plugin_powerd_delete_suspend_file(error)) return FALSE; /* establish proxy for method call to powerd */ data->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 (data->proxy == NULL) { g_prefix_error(error, "failed to connect to powerd: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(data->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(data->proxy)); return FALSE; } fu_plugin_powerd_rescan(plugin, g_dbus_proxy_call_sync(data->proxy, "GetBatteryState", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, G_SOURCE_REMOVE)); g_signal_connect(G_DBUS_PROXY(data->proxy), "g-signal", G_CALLBACK(fu_plugin_powerd_proxy_changed_cb), plugin); return TRUE; } static gboolean fu_plugin_powerd_prepare(FuPlugin *plugin, FuDevice *dev, FwupdInstallFlags flags, GError **error) { return fu_plugin_powerd_create_suspend_file(error); } static gboolean fu_plugin_powerd_cleanup(FuPlugin *plugin, FuDevice *dev, FwupdInstallFlags flags, GError **error) { return fu_plugin_powerd_delete_suspend_file(error); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_powerd_init; vfuncs->destroy = fu_plugin_powerd_destroy; vfuncs->startup = fu_plugin_powerd_startup; vfuncs->cleanup = fu_plugin_powerd_cleanup; vfuncs->prepare = fu_plugin_powerd_prepare; } fwupd-1.7.5/plugins/powerd/meson.build000066400000000000000000000007651420024370600200020ustar00rootroot00000000000000if get_option('plugin_powerd') and host_machine.system() == 'linux' and gio.version().version_compare('>=2.58') cargs = ['-DG_LOG_DOMAIN="FuPluginPowerd"'] shared_module('fu_plugin_powerd', fu_hash, sources : [ 'fu-plugin-powerd.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/realtek-mst/000077500000000000000000000000001420024370600165605ustar00rootroot00000000000000fwupd-1.7.5/plugins/realtek-mst/README.md000066400000000000000000000032221420024370600200360ustar00rootroot00000000000000# 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 custom DeviceInstanceId values derived from device names provided by system firmware and read from sysfs, like: * REALTEK-MST\NAME_10EC2142:00 * REALTEK-MST\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. fwupd-1.7.5/plugins/realtek-mst/fu-plugin-realtek-mst.c000066400000000000000000000011071420024370600230570ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-realtek-mst-device.h" static void fu_plugin_realtek_mst_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "RealtekMstDpAuxName"); fu_plugin_add_udev_subsystem(plugin, "i2c"); fu_plugin_add_device_gtype(plugin, FU_TYPE_REALTEK_MST_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_realtek_mst_init; } fwupd-1.7.5/plugins/realtek-mst/fu-realtek-mst-device.c000066400000000000000000000756431420024370600230400ustar00rootroot00000000000000/* * 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) 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_with_context(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_with_context(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, 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_full(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_full(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, error)) return FALSE; return fu_i2c_device_read(FU_I2C_DEVICE(self), value, 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) { g_usleep(G_TIME_SPAN_MILLISECOND); 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); const gchar *hardware_family = NULL; const gchar *quirk_name = NULL; g_autofree gchar *family_instance_id = NULL; g_autofree gchar *instance_id = NULL; /* set custom instance ID and load matching quirks */ instance_id = g_strdup_printf("REALTEK-MST\\NAME_%s", fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL)); fu_device_add_instance_id(device, instance_id); hardware_family = fu_context_get_hwid_value(context, FU_HWIDS_KEY_FAMILY); family_instance_id = g_strdup_printf("%s&FAMILY_%s", instance_id, hardware_family); fu_device_add_instance_id_full(device, family_instance_id, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* having loaded quirks, check this device is supported */ quirk_name = fu_device_get_name(device); if (g_strcmp0(quirk_name, "RTD2142") != 0 && g_strcmp0(quirk_name, "RTD2141B") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device name %s is not supported", quirk_name); return FALSE; } 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 */ /* FuI2cDevice */ if (!FU_DEVICE_CLASS(fu_realtek_mst_device_parent_class)->probe(device, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_realtek_mst_device_get_dual_bank_info(FuRealtekMstDevice *self, struct dual_bank_info *info, GError **error) { 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 */ g_usleep(200 * G_TIME_SPAN_MILLISECOND); /* request dual bank state and read back */ if (!fu_i2c_device_write(FU_I2C_DEVICE(self), 0x01, error)) return FALSE; if (!fu_i2c_device_read_full(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_debug("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_debug("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; 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), 0x70, error)) return FALSE; if (!fu_i2c_device_read(FU_I2C_DEVICE(self), &byte, 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_full(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(GPtrArray) chunks = fu_chunk_array_new_from_bytes(data, address, 0, 256); g_debug("write %#" G_GSIZE_MODIFIER "x bytes at %#08x", total_size, address); for (guint i = 0; i < chunks->len; i++) { FuChunk *chunk = g_ptr_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, chunks->len); } 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9); 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 */ g_usleep(G_USEC_PER_SEC); /* 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_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.7.5/plugins/realtek-mst/fu-realtek-mst-device.h000066400000000000000000000005021420024370600230230ustar00rootroot00000000000000/* * 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.7.5/plugins/realtek-mst/meson.build000066400000000000000000000013211420024370600207170ustar00rootroot00000000000000if get_option('plugin_realtek_mst') if not get_option('gudev') error('gudev is required for plugin_realtek_mst') endif cargs = ['-DG_LOG_DOMAIN="FuPluginRealtekMst"'] install_data(['realtek-mst.quirk'], install_dir: join_paths(get_option('datadir'), 'fwupd', 'quirks.d') ) shared_module('fu_plugin_realtek_mst', fu_hash, sources : [ 'fu-realtek-mst-device.c', 'fu-plugin-realtek-mst.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/realtek-mst/realtek-mst.quirk000066400000000000000000000005251420024370600220670ustar00rootroot00000000000000# match all devices with this udev subsystem [I2C] Plugin = realtek_mst [REALTEK-MST\NAME_10EC2142:00] Name = RTD2142 [REALTEK-MST\NAME_10EC2142:00&FAMILY_Google_Hatch] RealtekMstDpAuxName = DPDDC-C [REALTEK-MST\NAME_10EC2141:00] Name = RTD2141B [REALTEK-MST\NAME_10EC2141:00&FAMILY_Google_Zork] RealtekMstDrmCardKernelName = card0-DP-1 fwupd-1.7.5/plugins/redfish/000077500000000000000000000000001420024370600157545ustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/README.md000066400000000000000000000051651420024370600172420ustar00rootroot00000000000000# 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. ## 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. fwupd-1.7.5/plugins/redfish/fu-ipmi-device.c000066400000000000000000000437241420024370600207350ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_DEVICE_IN_UPDATE_MODE_ERR 0xD1 #define IPMI_INITIALIZATION_IN_PROGRESS_ERR 0xD2 #define IPMI_DESTINATION_UNAVAILABLE_ERR 0xD3 #define IPMI_INSUFFICIENT_PRIVILEGE_ERR 0xD4 #define IPMI_COMMAND_DISABLED_ERR 0xD6 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) static void fu_ipmi_device_to_string(FuDevice *device, guint idt, GString *str) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); fu_common_string_append_kx(str, idt, "DeviceId", self->device_id); fu_common_string_append_kx(str, idt, "DeviceRev", self->device_rev); fu_common_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) { g_autofree guint8 *buf2 = fu_memdup_safe(buf, bufsz, NULL); 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 = buf2, .msg.data_len = (guint16)bufsz, .msg.netfn = netfn, .msg.cmd = cmd, }; if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL && buf2 != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "ipmi-send", buf2, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), IPMICTL_SEND_COMMAND, (guint8 *)&req, NULL, 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, error)) return FALSE; if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL && buf != NULL) fu_common_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); struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET}; if (fcntl(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), 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); struct flock lock = {.l_type = F_UNLCK}; if (fcntl(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), 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"; #ifdef HAVE_IPMI_DEVICE_IN_FW_UPDATE_ERR if (errcode == IPMI_DEVICE_IN_FW_UPDATE_ERR) return "device-in-fw-update"; #endif #ifdef HAVE_IPMI_DEVICE_IN_INIT_ERR if (errcode == IPMI_DEVICE_IN_INIT_ERR) return "device-in-init"; #endif 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_DEVICE_IN_UPDATE_MODE_ERR) return "device-in-update-mode"; if (errcode == IPMI_INITIALIZATION_IN_PROGRESS_ERR) return "initialization-in-progress"; 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); 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_udev_device_get_fd(FU_UDEV_DEVICE(self)); 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; if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { 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_common_strsafe((const gchar *)resp, resp_len); } 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; } 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.7.5/plugins/redfish/fu-ipmi-device.h000066400000000000000000000017131420024370600207320ustar00rootroot00000000000000/* * 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 enable, GError **error); gboolean fu_ipmi_device_set_user_priv(FuIpmiDevice *self, guint8 user_id, guint8 priv_limit, guint8 channel, GError **error); fwupd-1.7.5/plugins/redfish/fu-plugin-redfish.c000066400000000000000000000337361420024370600214640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #ifdef HAVE_LINUX_IPMI_H #include "fu-ipmi-device.h" #endif #include "fu-redfish-backend.h" #include "fu-redfish-common.h" #include "fu-redfish-network.h" #include "fu-redfish-smbios.h" struct FuPluginData { FuRedfishBackend *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_plugin_redfish_change_expired(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(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_secure_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(data->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_patch(request, uri, builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; fu_redfish_backend_set_password(data->backend, password_new); /* success */ return fu_plugin_set_secure_config_value(plugin, "Password", password_new, error); } static gboolean fu_plugin_redfish_coldplug(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(GPtrArray) devices = NULL; g_autoptr(GError) error_local = NULL; /* get the list of devices */ if (!fu_backend_coldplug(FU_BACKEND(data->backend), &error_local)) { /* did the user password expire? */ if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_EXPIRED)) { if (!fu_plugin_redfish_change_expired(plugin, error)) return FALSE; if (!fu_backend_coldplug(FU_BACKEND(data->backend), 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(data->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; } static gboolean fu_redfish_plugin_discover_uefi_credentials(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(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_common_read_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(data->backend, split[0]); fu_redfish_backend_set_password(data->backend, split[1]); return TRUE; } static gboolean fu_redfish_plugin_discover_smbios_table(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); FuContext *ctx = fu_plugin_get_context(plugin); const gchar *smbios_data_fn; g_autofree gchar *hostname = NULL; g_autoptr(FuRedfishNetworkDevice) device = NULL; g_autoptr(FuRedfishSmbios) redfish_smbios = fu_redfish_smbios_new(); g_autoptr(GBytes) smbios_data = NULL; /* is optional if not in self tests */ smbios_data_fn = g_getenv("FWUPD_REDFISH_SMBIOS_DATA"); if (smbios_data_fn != NULL) { smbios_data = fu_common_get_contents_bytes(smbios_data_fn, error); if (smbios_data == NULL) return FALSE; } else { smbios_data = fu_context_get_smbios_data(ctx, REDFISH_SMBIOS_TABLE_TYPE); if (smbios_data == NULL) return TRUE; } if (!fu_firmware_parse(FU_FIRMWARE(redfish_smbios), smbios_data, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "failed to parse SMBIOS table entry type 42: "); return FALSE; } /* get IP, falling back to hostname, then MAC, then VID:PID */ hostname = g_strdup(fu_redfish_smbios_get_ip_addr(redfish_smbios)); if (hostname == NULL) hostname = g_strdup(fu_redfish_smbios_get_hostname(redfish_smbios)); if (device == NULL) { const gchar *mac_addr = fu_redfish_smbios_get_mac_addr(redfish_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(redfish_smbios); guint16 pid = fu_redfish_smbios_get_pid(redfish_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; if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { g_debug("device state is now %u", 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(data->backend, hostname); fu_redfish_backend_set_port(data->backend, fu_redfish_smbios_get_port(redfish_smbios)); return TRUE; } #ifdef HAVE_LINUX_IPMI_H static gboolean fu_redfish_plugin_ipmi_create_user(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(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, "fwupd") == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "fwupd user already exists in KCS slot %u", (guint)i); return FALSE; } } 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; fu_redfish_backend_set_username(data->backend, username_fwupd); fu_redfish_backend_set_password(data->backend, password_tmp); /* wait for Redfish to sync */ g_usleep(2 * G_USEC_PER_SEC); /* now use Redfish to change the temporary password to the actual password */ request = fu_redfish_backend_request_new(data->backend); uri = g_strdup_printf("/redfish/v1/AccountService/Accounts/%u", (guint)user_id - 1); 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_patch(request, uri, builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; fu_redfish_backend_set_password(data->backend, password_new); /* success */ if (!fu_plugin_set_secure_config_value(plugin, "UserUri", uri, error)) return FALSE; if (!fu_plugin_set_secure_config_value(plugin, "Username", username_fwupd, error)) return FALSE; if (!fu_plugin_set_secure_config_value(plugin, "Password", password_new, error)) return FALSE; return TRUE; } #endif static gboolean fu_plugin_redfish_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *ca_check_str = NULL; g_autofree gchar *password = NULL; g_autofree gchar *redfish_uri = NULL; g_autofree gchar *username = NULL; g_autoptr(GError) error_uefi = NULL; /* optional */ if (!fu_redfish_plugin_discover_smbios_table(plugin, 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(data->backend, TRUE); ip_str = redfish_uri + strlen("https://"); port = 443; } else if (g_str_has_prefix(redfish_uri, "http://")) { fu_redfish_backend_set_https(data->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(data->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(data->backend, port); } username = fu_plugin_get_config_value(plugin, "Username"); if (username != NULL) fu_redfish_backend_set_username(data->backend, username); password = fu_plugin_get_config_value(plugin, "Password"); if (password != NULL) fu_redfish_backend_set_password(data->backend, password); ca_check_str = fu_plugin_get_config_value(plugin, "CACheck"); if (ca_check_str != NULL) { gboolean ca_check = fu_plugin_get_config_value_boolean(plugin, "CACheck"); fu_redfish_backend_set_cacheck(data->backend, ca_check); } if (fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "wildcard-targets")) fu_redfish_backend_set_wildcard_targets(data->backend, TRUE); #ifdef HAVE_LINUX_IPMI_H /* we got neither a type 42 entry or config value, lets try IPMI */ if (fu_redfish_backend_get_username(data->backend) == NULL) { if (!fu_plugin_get_config_value_boolean(plugin, "IpmiDisableCreateUser")) { g_debug("attempting to create user using IPMI"); if (!fu_redfish_plugin_ipmi_create_user(plugin, error)) return FALSE; } } #endif return fu_backend_setup(FU_BACKEND(data->backend), error); } static void fu_plugin_redfish_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); data->backend = fu_redfish_backend_new(ctx); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_REDFISH_SMBIOS); } static void fu_plugin_redfish_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); g_object_unref(data->backend); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_redfish_init; vfuncs->destroy = fu_plugin_redfish_destroy; vfuncs->startup = fu_plugin_redfish_startup; vfuncs->coldplug = fu_plugin_redfish_coldplug; } fwupd-1.7.5/plugins/redfish/fu-redfish-backend.c000066400000000000000000000316311420024370600215450ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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" struct _FuRedfishBackend { FuBackend parent_instance; gchar *hostname; gchar *username; gchar *password; guint port; 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) FuRedfishRequest * fu_redfish_backend_request_new(FuRedfishBackend *self) { FuRedfishRequest *request = g_object_new(FU_TYPE_REDFISH_REQUEST, NULL); CURL *curl; #ifdef HAVE_LIBCURL_7_62_0 CURLU *uri; #else g_autofree gchar *uri_base = NULL; #endif 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); #ifdef HAVE_LIBCURL_7_62_0 uri = fu_redfish_request_get_uri(request); curl_url_set(uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0); curl_url_set(uri, CURLUPART_HOST, self->hostname, 0); curl_url_set(uri, CURLUPART_PORT, port, 0); curl_easy_setopt(curl, CURLOPT_CURLU, uri); #else uri_base = g_strdup_printf("%s://%s:%s", self->use_https ? "https" : "http", self->hostname, port); fu_redfish_request_set_uri_base(request, uri_base); #endif /* since DSP0266 makes Basic Authorization a requirement, * it is safe to use Basic Auth for all implementations */ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (glong)CURLAUTH_BASIC); curl_easy_setopt(curl, CURLOPT_TIMEOUT, (glong)180); curl_easy_setopt(curl, CURLOPT_USERNAME, self->username); curl_easy_setopt(curl, CURLOPT_PASSWORD, self->password); /* setup networking */ user_agent = g_strdup_printf("%s/%s", PACKAGE_NAME, PACKAGE_VERSION); curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 60L); if (!self->cacheck) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 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; /* 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); if (locker == NULL) 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 gboolean fu_redfish_backend_coldplug(FuBackend *backend, 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 (json_object_has_member(json_obj, "MultipartHttpPushUri")) { const gchar *tmp = json_object_get_string_member(json_obj, "MultipartHttpPushUri"); if (tmp != NULL) { self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE; self->push_uri_path = g_strdup(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; self->push_uri_path = g_strdup(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 gboolean fu_redfish_backend_setup(FuBackend *backend, GError **error) { FuRedfishBackend *self = FU_REDFISH_BACKEND(backend); JsonObject *json_obj; JsonObject *json_update_service = NULL; const gchar *data_id; const gchar *version = NULL; const gchar *uuid = 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 (json_object_has_member(json_obj, "UUID")) uuid = json_object_get_string_member(json_obj, "UUID"); g_debug("Version: %s", version); g_debug("UUID: %s", uuid); 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; } self->update_uri_path = g_strdup(data_id); return TRUE; } 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_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_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; 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", "context", ctx, NULL)); } fwupd-1.7.5/plugins/redfish/fu-redfish-backend.h000066400000000000000000000022571420024370600215540ustar00rootroot00000000000000/* * 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); 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.7.5/plugins/redfish/fu-redfish-common.c000066400000000000000000000057151420024370600214520ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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, ".")) { 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.7.5/plugins/redfish/fu-redfish-common.h000066400000000000000000000032471420024370600214550ustar00rootroot00000000000000/* * 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 #define REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST 0x40 #define REDFISH_INTERFACE_TYPE_USB_NETWORK 0x02 #define REDFISH_INTERFACE_TYPE_PCI_NETWORK 0x03 #define REDFISH_INTERFACE_TYPE_USB_NETWORK_V2 0x04 #define REDFISH_INTERFACE_TYPE_PCI_NETWORK_V2 0x05 #define REDFISH_IP_ASSIGNMENT_TYPE_STATIC 0x00 #define REDFISH_IP_ASSIGNMENT_TYPE_DHCP 0x02 #define REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG 0x03 #define REDFISH_IP_ASSIGNMENT_TYPE_HOST_SELECT 0x04 #define REDFISH_IP_ADDRESS_FORMAT_UNKNOWN 0x00 #define REDFISH_IP_ADDRESS_FORMAT_V4 0x01 #define REDFISH_IP_ADDRESS_FORMAT_V6 0x02 /* 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.7.5/plugins/redfish/fu-redfish-device.c000066400000000000000000000672641420024370600214300ustar00rootroot00000000000000/* * 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; } 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_common_string_append_kx(str, idt, "Milestone", priv->milestone); if (priv->build != NULL) fu_common_string_append_kv(str, idt, "Build", priv->build); } 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); fu_device_add_icon(FU_DEVICE(self), "audio-card"); } static gboolean fu_redfish_device_probe_related_pcie_item(FuRedfishDevice *self, const gchar *uri, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *json_obj; const gchar *subsystem = "PCI"; 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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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 && model_id != 0x0) { g_autofree gchar *devid1 = NULL; devid1 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X", subsystem, (guint)vendor_id, (guint)model_id); fu_device_add_instance_id(FU_DEVICE(self), devid1); } if (vendor_id != 0x0 && model_id != 0x0 && subsystem_vendor_id != 0x0 && subsystem_model_id != 0x0) { g_autofree gchar *devid2 = NULL; devid2 = g_strdup_printf("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X", subsystem, (guint)vendor_id, (guint)model_id, (guint)subsystem_vendor_id, (guint)subsystem_model_id); fu_device_add_instance_id(FU_DEVICE(self), devid2); } /* 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_common_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_common_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 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); } } } /* 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_get_vendor(dev) != NULL) { const gchar *instance_id_suffix = ""; g_autofree gchar *instance_id = NULL; if (fu_device_has_private_flag(dev, FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD)) instance_id_suffix = "&TYPE_UNSIGNED"; instance_id = g_strdup_printf("REDFISH\\VENDOR_%s&SOFTWAREID_%s%s", fu_device_get_vendor(dev), guid, instance_id_suffix); g_strdelimit(instance_id, " ", '_'); fu_device_add_instance_id(dev, instance_id); } /* used for quirking and parenting */ if (fu_device_get_vendor(dev) != NULL && fu_device_get_backend_id(dev) != NULL) { g_autofree gchar *instance_id = NULL; instance_id = g_strdup_printf("REDFISH\\VENDOR_%s&ID_%s", fu_device_get_vendor(dev), fu_device_get_backend_id(dev)); fu_device_add_instance_id(dev, instance_id); } 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_inhibit(dev, "not-updatable", "Updateable property is FALSE"); else fu_device_uninhibit(dev, "not-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; } } } /* 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_strcmp0(message_id, "TaskEvent.1.0.TaskProgressChanged") == 0 || g_strcmp0(message_id, "TaskEvent.1.0.TaskCompletedWarning") == 0 || g_strcmp0(message_id, "TaskEvent.1.0.TaskCompletedOK") == 0 || g_strcmp0(message_id, "Base.1.6.Success") == 0) return; /* set flags */ if (g_strcmp0(message_id, "Base.1.10.ResetRequired") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); return; } /* set error code */ if (g_strcmp0(message_id, "Update.1.0.AwaitToActivate") == 0) { ctx->error_code = FWUPD_ERROR_NEEDS_USER_ACTION; return; } if (g_strcmp0(message_id, "Update.1.0.TransferFailed") == 0) { ctx->error_code = FWUPD_ERROR_WRITE; return; } if (g_strcmp0(message_id, "Update.1.0.ActivateFailed") == 0) { ctx->error_code = FWUPD_ERROR_INVALID_FILE; return; } if (g_strcmp0(message_id, "Update.1.0.VerificationFailed") == 0 || g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyFailed") == 0) { ctx->error_code = FWUPD_ERROR_INVALID_FILE; return; } if (g_strcmp0(message_id, "Update.1.0.ApplyFailed") == 0) { ctx->error_code = FWUPD_ERROR_WRITE; return; } /* set status */ if (g_strcmp0(message_id, "Update.1.1.TargetDetermined") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateAssignment") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyInProgress") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE); return; } if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyCompleted") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE); return; } if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyInProgress") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY); return; } if (g_strcmp0(message_id, "Update.1.1.TransferringToComponent") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_strcmp0(message_id, "Update.1.1.VerifyingAtComponent") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY); return; } if (g_strcmp0(message_id, "Update.1.1.UpdateInProgress") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE); return; } if (g_strcmp0(message_id, "Update.1.1.UpdateSuccessful") == 0) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE); return; } if (g_strcmp0(message_id, "Update.1.1.InstallingOnComponent") == 0) { 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 { g_usleep(G_USEC_PER_SEC); 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; } 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_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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_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"); } 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; /** * 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.7.5/plugins/redfish/fu-redfish-device.h000066400000000000000000000024011420024370600214130ustar00rootroot00000000000000/* * 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_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 << 2) FuRedfishBackend * fu_redfish_device_get_backend(FuRedfishDevice *self); gboolean fu_redfish_device_poll_task(FuRedfishDevice *self, const gchar *location, FuProgress *progress, GError **error); fwupd-1.7.5/plugins/redfish/fu-redfish-legacy-device.c000066400000000000000000000114121420024370600226520ustar00rootroot00000000000000/* * 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_patch(request, "/redfish/v1/UpdateService", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, 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_patch(request, "/redfish/v1/UpdateService", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, 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); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, g_bytes_get_data(fw, NULL)); 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.7.5/plugins/redfish/fu-redfish-legacy-device.h000066400000000000000000000005641420024370600226650ustar00rootroot00000000000000/* * 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.7.5/plugins/redfish/fu-redfish-multipart-device.c000066400000000000000000000122471420024370600234360ustar00rootroot00000000000000/* * 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_autofree gchar *filename = NULL; 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; /* Get the update version */ filename = g_strdup_printf("%s.bin", fu_device_get_name(FU_DEVICE(self))); /* create the multipart request */ request = fu_redfish_backend_request_new(backend); curl = fu_redfish_request_get_curl(request); mime = curl_mime_init(curl); 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"); curl_mime_type(part, "application/json"); curl_mime_data(part, params->str, CURL_ZERO_TERMINATED); if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) g_debug("request: %s", params->str); part = curl_mime_addpart(mime); curl_mime_name(part, "UpdateFile"); curl_mime_type(part, "application/octet-stream"); curl_mime_filedata(part, filename); 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 %s: %li", filename, 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.7.5/plugins/redfish/fu-redfish-multipart-device.h000066400000000000000000000006031420024370600234340ustar00rootroot00000000000000/* * 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.7.5/plugins/redfish/fu-redfish-network-device.c000066400000000000000000000126601420024370600231050ustar00rootroot00000000000000/* * 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; if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { g_debug("%s device state is now %u", self->object_path, state); } if (state == FU_REDFISH_NETWORK_DEVICE_STATE_CONNECTED) 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.7.5/plugins/redfish/fu-redfish-network-device.h000066400000000000000000000017041420024370600231070ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) typedef enum { FU_REDFISH_NETWORK_DEVICE_STATE_UNKNOWN, FU_REDFISH_NETWORK_DEVICE_STATE_DISCONNECTED = 30, FU_REDFISH_NETWORK_DEVICE_STATE_CONNECTED = 100, } FuRedfishNetworkDeviceState; 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.7.5/plugins/redfish/fu-redfish-network.c000066400000000000000000000127311420024370600216470ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 */ if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) 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 */ if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) 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_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_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++) { if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) 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.7.5/plugins/redfish/fu-redfish-network.h000066400000000000000000000014201420024370600216450ustar00rootroot00000000000000/* * 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.7.5/plugins/redfish/fu-redfish-request.c000066400000000000000000000217331420024370600216500ustar00rootroot00000000000000/* * 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; #ifdef HAVE_LIBCURL_7_62_0 CURLU *uri; #else gchar *uri_base; #endif 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; } #ifdef HAVE_LIBCURL_7_62_0 CURLU * fu_redfish_request_get_uri(FuRedfishRequest *self) { g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL); return self->uri; } #else void fu_redfish_request_set_uri_base(FuRedfishRequest *self, const gchar *uri_base) { g_return_if_fail(FU_IS_REDFISH_REQUEST(self)); self->uri_base = g_strdup(uri_base); } #endif 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; /* 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 */ if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { g_autoptr(GString) str = g_string_new(NULL); g_autoptr(JsonGenerator) json_generator = json_generator_new(); 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; 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; #ifdef HAVE_LIBCURL_7_62_0 g_autoptr(curlptr) uri_str = NULL; #else g_autofree gchar *uri_str = NULL; #endif 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 */ #ifdef HAVE_LIBCURL_7_62_0 curl_url_set(self->uri, CURLUPART_PATH, path, 0); curl_url_get(self->uri, CURLUPART_URL, &uri_str, 0); #else uri_str = g_strdup_printf("%s%s", self->uri_base, path); if (curl_easy_setopt(self->curl, CURLOPT_URL, uri_str) != CURLE_OK) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to create message for URI"); return FALSE; } #endif res = curl_easy_perform(self->curl); curl_easy_getinfo(self->curl, CURLINFO_RESPONSE_CODE, &self->status_code); if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) { g_autofree gchar *str = NULL; 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; } /* 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 && path != NULL) { g_hash_table_insert(self->cache, g_strdup(path), g_byte_array_ref(self->buf)); } /* success */ return TRUE; } gboolean fu_redfish_request_patch(FuRedfishRequest *self, const gchar *path, JsonBuilder *builder, FuRedfishRequestPerformFlags flags, GError **error) { struct 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; /* 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); if (g_getenv("FWUPD_REDFISH_VERBOSE") != NULL) g_debug("request to %s: %s", path, str->str); /* patch */ curl_easy_setopt(self->curl, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_easy_setopt(self->curl, CURLOPT_POSTFIELDS, str->str); curl_easy_setopt(self->curl, CURLOPT_POSTFIELDSIZE, (long)str->len); hs = curl_slist_append(hs, "Content-Type: application/json"); 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); curl_easy_setopt(self->curl, CURLOPT_SHARE, curlsh); } static void fu_redfish_request_init(FuRedfishRequest *self) { self->curl = curl_easy_init(); #ifdef HAVE_LIBCURL_7_62_0 self->uri = curl_url(); #endif self->buf = g_byte_array_new(); self->json_parser = json_parser_new(); curl_easy_setopt(self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_request_write_cb); 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); #ifdef HAVE_LIBCURL_7_62_0 curl_url_cleanup(self->uri); #else g_free(self->uri_base); #endif 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.7.5/plugins/redfish/fu-redfish-request.h000066400000000000000000000025431420024370600216530ustar00rootroot00000000000000/* * 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, } FuRedfishRequestPerformFlags; gboolean fu_redfish_request_perform(FuRedfishRequest *self, const gchar *path, FuRedfishRequestPerformFlags flags, GError **error); gboolean fu_redfish_request_patch(FuRedfishRequest *self, const gchar *path, 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); #ifdef HAVE_LIBCURL_7_62_0 CURLU * fu_redfish_request_get_uri(FuRedfishRequest *self); #else void fu_redfish_request_set_uri_base(FuRedfishRequest *self, const gchar *uri_base); #endif glong fu_redfish_request_get_status_code(FuRedfishRequest *self); void fu_redfish_request_set_cache(FuRedfishRequest *self, GHashTable *cache); fwupd-1.7.5/plugins/redfish/fu-redfish-smbios.c000066400000000000000000000313001420024370600214430ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-redfish-common.h" #include "fu-redfish-smbios.h" struct _FuRedfishSmbios { FuFirmwareClass parent_instance; guint16 port; gchar *hostname; gchar *mac_addr; gchar *ip_addr; guint16 vid; guint16 pid; }; G_DEFINE_TYPE(FuRedfishSmbios, fu_redfish_smbios, FU_TYPE_FIRMWARE) typedef struct __attribute__((packed)) { guint8 service_uuid[16]; guint8 host_ip_assignment_type; guint8 host_ip_address_format; guint8 host_ip_address[16]; guint8 host_ip_mask[16]; guint8 service_ip_assignment_type; guint8 service_ip_address_format; guint8 service_ip_address[16]; guint8 service_ip_mask[16]; guint16 service_ip_port; guint32 service_ip_vlan_id; guint8 service_hostname_len; /* optional service_hostname goes here */ } FuRedfishProtocolOverIp; 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_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_common_read_uint8_safe(buf, bufsz, offset, &interface_type, error)) return FALSE; offset++; switch (interface_type) { case REDFISH_INTERFACE_TYPE_USB_NETWORK: case REDFISH_INTERFACE_TYPE_PCI_NETWORK: offset_vid_pid = 0x00; break; case REDFISH_INTERFACE_TYPE_USB_NETWORK_V2: offset_vid_pid = 0x01; offset_mac_addr = 0x06; break; case 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_common_read_uint16_safe(buf, bufsz, offset + offset_vid_pid, &self->vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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 = 0x0; guint8 service_ip_address_format = 0x0; guint16 service_ip_port = 0x0; guint8 service_ip_address[16] = {0x0}; /* port */ if (!fu_common_read_uint16_safe( buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port), &service_ip_port, G_LITTLE_ENDIAN, error)) return FALSE; fu_redfish_smbios_set_port(self, service_ip_port); /* IP address */ if (!fu_memcpy_safe( service_ip_address, sizeof(service_ip_address), 0x0, /* dst */ buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address), /* src */ sizeof(service_ip_address), error)) return FALSE; if (!fu_common_read_uint8_safe( buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format), &service_ip_address_format, error)) return FALSE; if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv4(service_ip_address); fu_redfish_smbios_set_ip_addr(self, tmp); } else if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv6(service_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 */ if (!fu_common_read_uint8_safe( buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len), &hostname_length, error)) return FALSE; 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 + sizeof(FuRedfishProtocolOverIp), /* 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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); gsize bufsz = 0; gsize offset = 0; guint8 protocol_rcds = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* check size */ if (bufsz < 0x09) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS entry too small: %" G_GSIZE_FORMAT, bufsz); return FALSE; } /* check type */ if (buf[0x0] != REDFISH_SMBIOS_TABLE_TYPE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not Management Controller Host Interface"); return FALSE; } if (buf[0x1] != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size of table 0x%x does not match binary 0x%x", buf[0x1], (guint)bufsz); return FALSE; } /* check interface type */ if (buf[0x04] != REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "only Network Host Interface supported"); return FALSE; } /* check length */ if (buf[0x05] > 0) { if (!fu_redfish_smbios_parse_interface_data(self, fw, 0x06, error)) return FALSE; } /* parse protocol records */ if (!fu_common_read_uint8_safe(buf, bufsz, 0x06 + buf[0x05], &protocol_rcds, error)) return FALSE; offset = 0x07 + buf[0x05]; 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_common_read_uint8_safe(buf, bufsz, offset, &protocol_id, error)) return FALSE; if (!fu_common_read_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 GBytes * fu_redfish_smbios_write(FuFirmware *firmware, GError **error) { FuRedfishProtocolOverIp proto = {0x0}; FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); gsize hostname_sz = 0; 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, REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST); fu_byte_array_append_uint8(buf, 0x09); /* iface datalen */ fu_byte_array_append_uint8(buf, 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, sizeof(FuRedfishProtocolOverIp) + hostname_sz); if (!fu_common_write_uint16_safe((guint8 *)&proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port), self->port, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_uint8_safe( (guint8 *)&proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format), REDFISH_IP_ADDRESS_FORMAT_V4, error)) return NULL; if (!fu_common_write_uint8_safe( (guint8 *)&proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_assignment_type), REDFISH_IP_ASSIGNMENT_TYPE_STATIC, error)) return NULL; if (self->hostname != NULL) hostname_sz = strlen(self->hostname); if (hostname_sz > 0) { if (!fu_common_write_uint8_safe( (guint8 *)&proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len), hostname_sz, error)) { g_prefix_error(error, "cannot write length: "); return NULL; } } g_byte_array_append(buf, (guint8 *)&proto, sizeof(proto)); if (hostname_sz > 0) g_byte_array_append(buf, (guint8 *)self->hostname, hostname_sz); return g_byte_array_free_to_bytes(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.7.5/plugins/redfish/fu-redfish-smbios.h000066400000000000000000000013311420024370600214510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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); 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.7.5/plugins/redfish/fu-self-test.c000066400000000000000000000277441420024370600204540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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" typedef struct { FuPlugin *plugin; } FuTest; static gboolean fu_test_is_installed_test(void) { const gchar *builddir = g_getenv("G_TEST_BUILDDIR"); if (builddir == NULL) return FALSE; return g_str_has_prefix(builddir, "/usr"); } static void fu_test_self_init(FuTest *self) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; g_autofree gchar *pluginfn = 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); self->plugin = fu_plugin_new(ctx); /* running as an installed test */ if (fu_test_is_installed_test()) { g_autofree gchar *plugindir = fu_common_get_path(FU_PATH_KIND_PLUGINDIR_PKG); pluginfn = g_build_filename(plugindir, "libfu_plugin_redfish." G_MODULE_SUFFIX, NULL); } else { pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_redfish." G_MODULE_SUFFIX, NULL); } ret = fu_plugin_open(self->plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin, &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { g_test_skip("no redfish.py running"); return; } g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_coldplug(self->plugin, &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_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_NONE, &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_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false(ret); } static void fu_test_self_free(FuTest *self) { 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_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; g_test_init(&argc, &argv, NULL); 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); g_setenv("FWUPD_REDFISH_SMBIOS_DATA", smbios_data_fn, TRUE); g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); g_setenv("CONFIGURATION_DIRECTORY", 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/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.7.5/plugins/redfish/fwupd-redfish.conf000066400000000000000000000000151420024370600213660ustar00rootroot00000000000000ipmi-devintf fwupd-1.7.5/plugins/redfish/meson.build000066400000000000000000000053211420024370600201170ustar00rootroot00000000000000if get_option('plugin_redfish') if not get_option('curl') error('curl is required for plugin_redfish') endif cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"'] install_data(['redfish.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) ipmi_src = [] if have_linux_ipmi ipmi_src += 'fu-ipmi-device.c' endif if get_option('systemd') and have_linux_ipmi install_data(['fwupd-redfish.conf'], install_dir: systemd_modules_load_dir, ) endif shared_module('fu_plugin_redfish', fu_hash, sources : [ 'fu-plugin-redfish.c', 'fu-redfish-backend.c', 'fu-redfish-common.c', # fuzzing 'fu-redfish-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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, libcurl, ], ) install_data(['redfish.conf'], install_dir: join_paths(sysconfdir, 'fwupd'), ) 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_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', fu_hash, sources : [ 'fu-self-test.c', 'fu-redfish-backend.c', 'fu-redfish-common.c', 'fu-redfish-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', ipmi_src, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], c_args : cargs, dependencies : [ plugin_deps, libcurl, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('redfish-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/redfish/redfish.conf000066400000000000000000000006261420024370600202530ustar00rootroot00000000000000[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 fwupd-1.7.5/plugins/redfish/redfish.quirk000066400000000000000000000011171420024370600204550ustar00rootroot00000000000000# Lenovo ThinkSystem [42f00735-c9ab-5374-bd63-a5deee5881e0] Flags = wildcard-targets,reset-required [REDFISH\VENDOR_Lenovo&ID_BMC-Backup] ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary Flags = dual-image,is-backup [REDFISH\VENDOR_Lenovo&ID_BMC-Primary] ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary Flags = dual-image [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 fwupd-1.7.5/plugins/redfish/tests/000077500000000000000000000000001420024370600171165ustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/tests/daemon.conf000077700000000000000000000000001420024370600251072../../../data/daemon.confustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/tests/efi/000077500000000000000000000000001420024370600176615ustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/tests/efi/efivars/000077500000000000000000000000001420024370600213205ustar00rootroot00000000000000RedfishIndications-16faa37e-4b6a-4891-9028-242de65a3b70000066400000000000000000000000101420024370600314220ustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/tests/efi/efivarsRedfishOSCredentials-16faa37e-4b6a-4891-9028-242de65a3b70000066400000000000000000000000251420024370600316630ustar00rootroot00000000000000fwupd-1.7.5/plugins/redfish/tests/efi/efivarsusername:passwordfwupd-1.7.5/plugins/redfish/tests/redfish-smbios.bin000066400000000000000000000001661420024370600225310ustar00rootroot00000000000000*v4@ vxVSnd4 localhostfwupd-1.7.5/plugins/redfish/tests/redfish-smbios.builder.xml000066400000000000000000000002531420024370600242030ustar00rootroot00000000000000 0x1234 localhost 0.0.0.0 0x9876 0x5678 fwupd-1.7.5/plugins/redfish/tests/redfish.conf000066400000000000000000000001121420024370600214030ustar00rootroot00000000000000[redfish] Uri=http://localhost:4661 Username=username2 Password=password2 fwupd-1.7.5/plugins/redfish/tests/redfish.py000077500000000000000000000202541420024370600211220ustar00rootroot00000000000000#!/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_USERNAME = "username2" HARDCODED_PASSWORD = "password2" app._percentage: 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._percentage = 0 # check password from the config file try: if ( request.authorization["username"] != HARDCODED_USERNAME or request.authorization["password"] != HARDCODED_PASSWORD ): return _failure("unauthorised", status=401) except (KeyError, TypeError): return _failure("invalid") res = { "@odata.id": "/redfish/v1/", "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", "FirmwareInventory": { "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory" }, "MultipartHttpPushUri": "/FWUpdate", "HttpPushUri": "/FWUpdate", "HttpPushUriOptions": { "HttpPushUriApplyTime": { "ApplyTime": "Immediate", } }, "HttpPushUriOptionsBusy": False, "ServiceEnabled": True, } 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", "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", "Id": "BMC", "LowestSupportedVersion": "11A-0.12", "Manufacturer": "Lenovo", "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", } 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", "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", "Id": "BIOS", "LowestSupportedVersion": "P79 v1.10", "Manufacturer": "Contoso", "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", } 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", "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(): res = { "@odata.id": "/redfish/v1/TaskService/Tasks/545", "@odata.type": "#Task.v1_4_3.Task", "Id": "545", "Name": "Task 545", "PercentComplete": app._percentage, } if app._percentage == 0: res["TaskState"] = "Running" elif app._percentage in [25, 50, 75]: res["TaskState"] = "Running" res["TaskStatus"] = "OK" res["Messages"] = [ { "Message": "Applying image", "MessageId": "Update.1.1.TransferringToComponent", } ] elif app._percentage == 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._percentage += 25 return Response(response=json.dumps(res), status=200, mimetype="application/json") @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", "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"}, ) if __name__ == "__main__": app.run(host="0.0.0.0", port=4661) fwupd-1.7.5/plugins/rts54hid/000077500000000000000000000000001420024370600157765ustar00rootroot00000000000000fwupd-1.7.5/plugins/rts54hid/README.md000066400000000000000000000026501420024370600172600ustar00rootroot00000000000000# Realtek 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&REV_0001` * `USB\VID_0BDA&PID_1100` * `USB\VID_0BDA` 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`. fwupd-1.7.5/plugins/rts54hid/fu-plugin-rts54hid.c000066400000000000000000000013451420024370600215170ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-rts54hid-device.h" #include "fu-rts54hid-module.h" static void fu_plugin_rts54hid_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HID_MODULE); fu_context_add_quirk_key(ctx, "Rts54TargetAddr"); fu_context_add_quirk_key(ctx, "Rts54I2cSpeed"); fu_context_add_quirk_key(ctx, "Rts54RegisterAddrLen"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_rts54hid_init; } fwupd-1.7.5/plugins/rts54hid/fu-rts54hid-common.h000066400000000000000000000030121420024370600215070ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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.7.5/plugins/rts54hid/fu-rts54hid-device.c000066400000000000000000000246261420024370600214670ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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_common_string_append_kb(str, idt, "FwAuth", self->fw_auth); fu_common_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}; memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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}; memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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); memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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 */ memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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_progress_sleep(progress, 4000); 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}; memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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 */ memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 46); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 52); 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, /* start addr */ 0x00, /* page_sz */ FU_RTS54HID_TRANSFER_BLOCK_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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_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.7.5/plugins/rts54hid/fu-rts54hid-device.h000066400000000000000000000005431420024370600214640ustar00rootroot00000000000000/* * 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.7.5/plugins/rts54hid/fu-rts54hid-module.c000066400000000000000000000155551420024370600215160ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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_common_string_append_kx(str, idt, "TargetAddr", self->target_addr); fu_common_string_append_kx(str, idt, "I2cSpeed", self->i2c_speed); fu_common_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; memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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 */ memcpy(buf, &cmd_buffer, sizeof(cmd_buffer)); 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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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(GPtrArray) 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, /* start addr */ 0x00, /* page_sz */ 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } /* success! */ return TRUE; } static void fu_rts54hid_module_init(FuRts54HidModule *self) { 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; } FuRts54HidModule * fu_rts54hid_module_new(void) { FuRts54HidModule *self = NULL; self = g_object_new(FU_TYPE_RTS54HID_MODULE, NULL); return self; } fwupd-1.7.5/plugins/rts54hid/fu-rts54hid-module.h000066400000000000000000000005401420024370600215070ustar00rootroot00000000000000/* * 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) FuRts54HidModule * fu_rts54hid_module_new(void); fwupd-1.7.5/plugins/rts54hid/meson.build000066400000000000000000000010711420024370600201370ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hid"'] install_data([ 'rts54hid.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_rts54hid', fu_hash, sources : [ 'fu-rts54hid-device.c', 'fu-rts54hid-module.c', 'fu-plugin-rts54hid.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/rts54hid/rts54hid.quirk000066400000000000000000000006511420024370600205230ustar00rootroot00000000000000# Realtek USBHub (HID Device) [USB\VID_0BDA&PID_5A00] Plugin = rts54hid 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.7.5/plugins/rts54hub/000077500000000000000000000000001420024370600160105ustar00rootroot00000000000000fwupd-1.7.5/plugins/rts54hub/README.md000066400000000000000000000020331420024370600172650ustar00rootroot00000000000000# Realtek RTS54 HUB ## 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&REV_0001` * `USB\VID_0BDA&PID_5423` * `USB\VID_0BDA` ## 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`. fwupd-1.7.5/plugins/rts54hub/data/000077500000000000000000000000001420024370600167215ustar00rootroot00000000000000fwupd-1.7.5/plugins/rts54hub/data/lsusb.txt000066400000000000000000000111441420024370600206130ustar00rootroot00000000000000Bus 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.7.5/plugins/rts54hub/fu-plugin-rts54hub.c000066400000000000000000000015631420024370600215450ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-rts54hub-device.h" #include "fu-rts54hub-rtd21xx-background.h" #include "fu-rts54hub-rtd21xx-foreground.h" static void fu_plugin_rts54hub_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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); fu_context_add_quirk_key(ctx, "Rts54TargetAddr"); fu_context_add_quirk_key(ctx, "Rts54I2cSpeed"); fu_context_add_quirk_key(ctx, "Rts54RegisterAddrLen"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_rts54hub_init; } fwupd-1.7.5/plugins/rts54hub/fu-rts54hub-device.c000066400000000000000000000375761420024370600215230ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_string_append_kb(str, idt, "FwAuth", self->fw_auth); fu_common_string_append_kb(str, idt, "DualBank", self->dual_bank); fu_common_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(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 46); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 52); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1); /* 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, /* start addr */ 0x00, /* page_sz */ FU_RTS54HUB_DEVICE_BLOCK_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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_common_read_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_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.7.5/plugins/rts54hub/fu-rts54hub-device.h000066400000000000000000000022531420024370600215100ustar00rootroot00000000000000/* * 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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-background.c000066400000000000000000000244131420024370600236770ustar00rootroot00000000000000/* * 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 #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 */ g_usleep(300000); 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 */ g_usleep(300000); 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_progress_sleep(progress, 1000); /* 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(GPtrArray) 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); 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 */ g_usleep(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_common_read_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_common_write_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, /* start addr */ 0x00, /* page_sz */ ISP_DATA_BLOCKSIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-background.h000066400000000000000000000010121420024370600236720ustar00rootroot00000000000000/* * 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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-device.c000066400000000000000000000145651420024370600230260ustar00rootroot00000000000000/* * 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 #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_common_string_append_kx(str, idt, "TargetAddr", priv->target_addr); fu_common_string_append_kx(str, idt, "I2cSpeed", priv->i2c_speed); fu_common_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_common_strtoull_full(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_common_strtoull_full(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_common_strtoull_full(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; } g_usleep(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_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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-device.h000066400000000000000000000032271420024370600230240ustar00rootroot00000000000000/* * 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 5000 /* us */ #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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-foreground.c000066400000000000000000000270421420024370600237330ustar00rootroot00000000000000/* * 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 #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 */ g_usleep(300000); 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 */ g_usleep(300000); 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_progress_sleep(progress, 60000); /* 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(GPtrArray) 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); 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 */ g_usleep(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_common_read_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_common_write_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, /* start addr */ 0x00, /* page_sz */ ISP_DATA_BLOCKSIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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.7.5/plugins/rts54hub/fu-rts54hub-rtd21xx-foreground.h000066400000000000000000000010121420024370600237250ustar00rootroot00000000000000/* * 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.7.5/plugins/rts54hub/meson.build000066400000000000000000000012211420024370600201460ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hub"'] install_data([ 'rts54hub.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_rts54hub', fu_hash, sources : [ 'fu-rts54hub-device.c', 'fu-rts54hub-rtd21xx-device.c', 'fu-rts54hub-rtd21xx-background.c', 'fu-rts54hub-rtd21xx-foreground.c', 'fu-plugin-rts54hub.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/rts54hub/rts54hub.quirk000066400000000000000000000020061420024370600205430ustar00rootroot00000000000000# RTS5423 Development Board [USB\VID_0BDA&PID_5B00] Plugin = rts54hub 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 fwupd-1.7.5/plugins/steelseries/000077500000000000000000000000001420024370600166575ustar00rootroot00000000000000fwupd-1.7.5/plugins/steelseries/README.md000066400000000000000000000017571420024370600201500ustar00rootroot00000000000000# SteelSeries ## Introduction This plugin allows to update firmware on SteelSeries gamepads: * Stratus Duo * Stratus Duo USB wireless adapter * Proton SteelSeries 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. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1038&PID_1702&REV_0001` * `USB\VID_1038&PID_1702` * `USB\VID_1038` ## 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. ## 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`. fwupd-1.7.5/plugins/steelseries/fu-plugin-steelseries.c000066400000000000000000000012261420024370600232570ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-steelseries-device.h" #include "fu-steelseries-gamepad.h" static void fu_plugin_steelseries_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD); fu_context_add_quirk_key(ctx, "SteelSeriesDeviceKind"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_steelseries_init; } fwupd-1.7.5/plugins/steelseries/fu-steelseries-common.c000066400000000000000000000012641420024370600232530ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-steelseries-common.h" FuSteelseriesDeviceKind fu_steelseries_device_type_from_string(const gchar *name) { if (g_strcmp0(name, "gamepad") == 0) return FU_STEELSERIES_DEVICE_GAMEPAD; if (g_strcmp0(name, "gamepad-dongle") == 0) return FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE; return FU_STEELSERIES_DEVICE_UNKNOWN; } const gchar * fu_steelseries_device_type_to_string(FuSteelseriesDeviceKind type) { if (type == FU_STEELSERIES_DEVICE_GAMEPAD) return "gamepad"; if (type == FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE) return "gamepad-dongle"; return "unknown"; } fwupd-1.7.5/plugins/steelseries/fu-steelseries-common.h000066400000000000000000000007151420024370600232600ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_STEELSERIES_DEVICE_UNKNOWN = 0, FU_STEELSERIES_DEVICE_GAMEPAD, FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE } FuSteelseriesDeviceKind; FuSteelseriesDeviceKind fu_steelseries_device_type_from_string(const gchar *name); const gchar * fu_steelseries_device_type_to_string(FuSteelseriesDeviceKind type); fwupd-1.7.5/plugins/steelseries/fu-steelseries-device.c000066400000000000000000000045661420024370600232320ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-steelseries-device.h" #define STEELSERIES_TRANSACTION_TIMEOUT 1000 /* ms */ G_DEFINE_TYPE(FuSteelseriesDevice, fu_steelseries_device, FU_TYPE_USB_DEVICE) static gboolean fu_steelseries_device_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_device_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_device_init(FuSteelseriesDevice *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_device_class_init(FuSteelseriesDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_steelseries_device_setup; } fwupd-1.7.5/plugins/steelseries/fu-steelseries-device.h000066400000000000000000000006331420024370600232260ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * 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; }; fwupd-1.7.5/plugins/steelseries/fu-steelseries-gamepad.c000066400000000000000000000323711420024370600233640ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-common.h" #include "fu-steelseries-gamepad.h" #define STEELSERIES_TRANSACTION_TIMEOUT 1000 #define STEELSERIES_BUFFER_CONTROL_SIZE 64 #define STEELSERIES_BUFFER_TRANSFER_SIZE 32 struct _FuSteelseriesGamepad { FuUsbDevice parent_instance; FuSteelseriesDeviceKind device_kind; guint8 iface_idx; guint8 ep; gsize in_size; }; G_DEFINE_TYPE(FuSteelseriesGamepad, fu_steelseries_gamepad, FU_TYPE_USB_DEVICE) static gboolean fu_steelseries_gamepad_command(FuDevice *device, guint8 *data, gboolean answer, GError **error) { FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); 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, self->iface_idx, data, STEELSERIES_BUFFER_CONTROL_SIZE, &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error(error, "failed to do control transfer: "); return FALSE; } if (actual_len != STEELSERIES_BUFFER_CONTROL_SIZE) { 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, STEELSERIES_BUFFER_CONTROL_SIZE); /* do not expect the answer from device */ if (answer != TRUE) return TRUE; ret = g_usb_device_interrupt_transfer(usb_device, self->ep, data, self->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 != self->in_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } static gboolean fu_steelseries_gamepad_cmd_erase(FuDevice *device, GError **error) { FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA1, 0xAA, 0x55}; /* dongle for gamepad is using different options */ if (self->device_kind == FU_STEELSERIES_DEVICE_GAMEPAD_DONGLE) { /* dongle */ data[8] = 0xD0; data[9] = 0x01; } else { /* gamepad */ data[9] = 0x02; /* magic is needed for newer gamepad */ data[13] = 0x02; } if (!fu_steelseries_gamepad_command(device, data, FALSE, error)) { g_prefix_error(error, "unable erase flash block: "); return FALSE; } /* timeout to give some time to erase */ g_usleep(20000); return TRUE; } static gboolean fu_steelseries_gamepad_probe(FuDevice *device, GError **error) { #if G_USB_CHECK_VERSION(0, 3, 3) FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); GUsbInterface *iface = NULL; GUsbEndpoint *ep = NULL; guint8 iface_id; guint8 ep_id; guint16 packet_size; g_autoptr(GPtrArray) ifaces = NULL; g_autoptr(GPtrArray) endpoints = NULL; /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_steelseries_gamepad_parent_class)->probe(device, error)) return FALSE; ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL || ifaces->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "update interface not found"); return FALSE; } /* use the last interface for interrupt transfer */ iface_id = ifaces->len - 1; iface = g_ptr_array_index(ifaces, iface_id); 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); self->iface_idx = iface_id; self->ep = ep_id; self->in_size = packet_size; fu_usb_device_add_interface(FU_USB_DEVICE(self), iface_id); return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "this version of GUsb is not supported"); return FALSE; #endif } static gboolean fu_steelseries_gamepad_setup(FuDevice *device, GError **error) { g_autofree gchar *bootloader_version = NULL; g_autofree gchar *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_gamepad_command(device, data, TRUE, error)) return FALSE; if (!fu_common_read_uint16_safe(data, sizeof(data), 0x01, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; version = fu_common_version_from_uint16(fw_ver, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version(FU_DEVICE(device), version); if (!fu_common_read_uint16_safe(data, sizeof(data), 0x03, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; bootloader_version = fu_common_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_gamepad_command(device, 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_gamepad_command(device, 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, GPtrArray *chunks, FuProgress *progress, guint32 *checksum, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint id = 0; id < chunks->len; id++) { FuChunk *chunk = g_ptr_array_index(chunks, id); guint16 chunk_checksum; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA3}; /* block ID */ if (!fu_common_write_uint16_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x01, (guint16)id, 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_common_sum16(data + 3, STEELSERIES_BUFFER_TRANSFER_SIZE); if (!fu_common_write_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_gamepad_command(device, data, FALSE, error)) { g_prefix_error(error, "unable to flash block %u: ", id); return FALSE; } /* timeout to give some time to flash the block on device */ g_usleep(10000); 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_common_write_uint32_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x03, checksum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_steelseries_gamepad_command(device, 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(GPtrArray) chunks = NULL; blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(blob, 0, 0, STEELSERIES_BUFFER_TRANSFER_SIZE); if (chunks->len > (G_MAXUINT16 + 1)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "too lot of firmware chunks for the device"); return FALSE; } fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 0); /* 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 gboolean fu_steelseries_gamepad_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); if (g_strcmp0(key, "SteelSeriesDeviceKind") == 0) { self->device_kind = fu_steelseries_device_type_from_string(value); if (self->device_kind != FU_STEELSERIES_DEVICE_UNKNOWN) return TRUE; g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unsupported SteelSeriesDeviceKind 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_steelseries_gamepad_to_string(FuDevice *device, guint idt, GString *str) { FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); FU_DEVICE_CLASS(fu_steelseries_gamepad_parent_class)->to_string(device, idt, str); fu_common_string_append_kv(str, idt, "DeviceKind", fu_steelseries_device_type_to_string(self->device_kind)); fu_common_string_append_kx(str, idt, "Interface", self->iface_idx); fu_common_string_append_kx(str, idt, "Endpoint", self->ep); } 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->probe = fu_steelseries_gamepad_probe; 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_quirk_kv = fu_steelseries_gamepad_set_quirk_kv; klass_device->to_string = fu_steelseries_gamepad_to_string; klass_device->set_progress = fu_steelseries_gamepad_set_progress; } static void fu_steelseries_gamepad_init(FuSteelseriesGamepad *device) { FuSteelseriesGamepad *self = FU_STEELSERIES_GAMEPAD(device); self->device_kind = FU_STEELSERIES_DEVICE_GAMEPAD; 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_BCD); fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(device), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_protocol(FU_DEVICE(device), "com.steelseries.gamepad"); fu_device_set_firmware_size_max(FU_DEVICE(device), (G_MAXUINT16 + 1) * STEELSERIES_BUFFER_TRANSFER_SIZE); } fwupd-1.7.5/plugins/steelseries/fu-steelseries-gamepad.h000066400000000000000000000006561420024370600233720ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_STEELSERIES_GAMEPAD (fu_steelseries_gamepad_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesGamepad, fu_steelseries_gamepad, FU, STEELSERIES_GAMEPAD, FuUsbDevice) struct _FuSteelseriesGamepadClass { FuUsbDeviceClass parent_class; }; fwupd-1.7.5/plugins/steelseries/meson.build000066400000000000000000000011441420024370600210210ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginSteelSeries"'] install_data(['steelseries.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_steelseries', fu_hash, sources : [ 'fu-plugin-steelseries.c', 'fu-steelseries-common.c', 'fu-steelseries-device.c', 'fu-steelseries-gamepad.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/steelseries/steelseries.quirk000066400000000000000000000022471420024370600222700ustar00rootroot00000000000000# Rival 100 [USB\VID_1038&PID_1702] Plugin = steelseries GType = FuSteelseriesDevice Summary = An optical gaming mouse Icon = input-mouse [USB\VID_1038&PID_1430] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo RX Icon = input-gaming CounterpartGuid = USB\VID_1038&PID_1432 SteelSeriesDeviceKind = gamepad-dongle [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_1432] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo RX bootloader CounterpartGuid = USB\VID_1038&PID_1430 Flags = is-bootloader SteelSeriesDeviceKind = gamepad-dongle [USB\VID_1038&PID_1433] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo bootloader CounterpartGuid = USB\VID_1038&PID_1431 Flags = is-bootloader [USB\VID_1038&PID_1434] Plugin = steelseries GType = FuSteelseriesGamepad Name = Proton gamepad Icon = input-gaming CounterpartGuid = USB\VID_1038&PID_1435 [USB\VID_1038&PID_1435] Plugin = steelseries GType = FuSteelseriesGamepad Name = Proton bootloader CounterpartGuid = USB\VID_1038&PID_1434 Flags = is-bootloader fwupd-1.7.5/plugins/superio/000077500000000000000000000000001420024370600160165ustar00rootroot00000000000000fwupd-1.7.5/plugins/superio/README.md000066400000000000000000000033561420024370600173040ustar00rootroot00000000000000# SuperIO 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`. fwupd-1.7.5/plugins/superio/fu-plugin-superio.c000066400000000000000000000065601420024370600215630ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-superio-it55-device.h" #include "fu-superio-it85-device.h" #include "fu-superio-it89-device.h" #define FU_QUIRKS_SUPERIO_GTYPE "SuperioGType" static gboolean fu_plugin_superio_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_autofree gchar *devid = NULL; 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 */ devid = g_strdup_printf("SUPERIO\\GUID_%s", guid); fu_device_add_instance_id(FU_DEVICE(dev), devid); /* 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 void fu_plugin_superio_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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"); 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"); } static gboolean fu_plugin_superio_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GPtrArray *hwids; if (fu_common_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_plugin_superio_coldplug_chipset(plugin, guid, error)) return FALSE; } return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_superio_init; vfuncs->coldplug = fu_plugin_superio_coldplug; } fwupd-1.7.5/plugins/superio/fu-superio-common.c000066400000000000000000000025151420024370600215510ustar00rootroot00000000000000/* * 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.7.5/plugins/superio/fu-superio-common.h000066400000000000000000000077421420024370600215650ustar00rootroot00000000000000/* * 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.7.5/plugins/superio/fu-superio-device.c000066400000000000000000000357021420024370600215240ustar00rootroot00000000000000/* * 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, error)) return FALSE; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->port + 1, data, error)) return FALSE; return TRUE; } 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, error)) return FALSE; if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->port + 1, data, 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); if (g_getenv("FWUPD_SUPERIO_VERBOSE") != NULL) fu_common_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_common_string_append_kv(str, idt, "Chipset", priv->chipset); fu_common_string_append_kx(str, idt, "Id", priv->id); fu_common_string_append_kx(str, idt, "Port", priv->port); fu_common_string_append_kx(str, idt, "DataPort", priv->data_port); fu_common_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, 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, 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, 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, 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, error)) return FALSE; if ((status & SIO_STATUS_EC_OBF) == 0) break; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->data_port, &unused, 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_common_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_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->id = tmp; return TRUE; } if (g_strcmp0(key, "SuperioPort") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioControlPort") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->control_port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioDataPort") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->data_port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioTimeout") == 0) { if (!fu_common_strtoull_full(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) { 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.7.5/plugins/superio/fu-superio-device.h000066400000000000000000000022271420024370600215250ustar00rootroot00000000000000/* * 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_read16(FuSuperioDevice *self, guint8 addr, guint16 *data, GError **error); gboolean fu_superio_device_io_write(FuSuperioDevice *self, guint8 addr, guint8 data, GError **error); fwupd-1.7.5/plugins/superio/fu-superio-it55-device.c000066400000000000000000000440311420024370600223030ustar00rootroot00000000000000/* * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_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); 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_set_percentage_full(progress, (gsize)i + 1, (gsize)block_count); } } 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; g_usleep(1000); } g_usleep(100000); 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); /* block 0 */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9); 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_common_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, 0x00, BLOCK_SIZE); for (guint i = 0; i < blocks->len; ++i) { FuChunk *block = g_ptr_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)blocks->len); } 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); g_usleep(1000); 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_common_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* 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", error); if (version == NULL) version = g_strdup("(unknown version)"); date = fu_ec_extract_field(fw, "DATE", error); 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); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); /* 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.7.5/plugins/superio/fu-superio-it55-device.h000066400000000000000000000005221420024370600223050ustar00rootroot00000000000000/* * 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.7.5/plugins/superio/fu-superio-it85-device.c000066400000000000000000000041241420024370600223050ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { 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(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.7.5/plugins/superio/fu-superio-it85-device.h000066400000000000000000000005521420024370600223130ustar00rootroot00000000000000/* * 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.7.5/plugins/superio/fu-superio-it89-device.c000066400000000000000000000523041420024370600223140ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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); 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_set_percentage_full(progress, (goffset)i, (goffset)size); } /* 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); } if (size % 2 != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "write length not supported, got 0x%04x", (guint)size); } /* 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_common_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 21); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 59); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20); /* 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_common_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_common_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_common_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, GPtrArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len - 1); for (guint i = 0; i < chunks->len - 1; i++) { FuChunk *chk = g_ptr_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(GPtrArray) 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 e-flash */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95); /* 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, 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.7.5/plugins/superio/fu-superio-it89-device.h000066400000000000000000000005521420024370600223170ustar00rootroot00000000000000/* * 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.7.5/plugins/superio/meson.build000066400000000000000000000012151420024370600201570ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginSuperio"'] install_data(['superio.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_superio', fu_hash, sources : [ 'fu-plugin-superio.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/superio/superio.quirk000066400000000000000000000036441420024370600205700ustar00rootroot00000000000000# 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) [013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 # 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 # 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 # 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 # Tuxedo InifinityBook S14 Gen6 [6c80d85b-d0b6-5ee2-99d4-ec28dd32febd] SuperioGType = FuEcIt55Device [SUPERIO\GUID_6c80d85b-d0b6-5ee2-99d4-ec28dd32febd] SuperioId = 0x5570 SuperioControlPort = 0x66 SuperioDataPort = 0x62 SuperioAutoloadAction = disable SuperioTimeout = 650 # Tuxedo InifinityBook S15 Gen6 [60f53465-e8fc-5122-b79b-f7b03f063037] SuperioGType = FuEcIt55Device [SUPERIO\GUID_60f53465-e8fc-5122-b79b-f7b03f063037] SuperioId = 0x5570 SuperioControlPort = 0x66 SuperioDataPort = 0x62 SuperioAutoloadAction = disable SuperioTimeout = 650 fwupd-1.7.5/plugins/synaptics-cape/000077500000000000000000000000001420024370600172535ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-cape/README.md000066400000000000000000000020071420024370600205310ustar00rootroot00000000000000# Synaptics CAPE devices ## 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` * `SYNAPTICS_CAPE\CX31988` ## 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`. fwupd-1.7.5/plugins/synaptics-cape/fu-plugin-synaptics-cape.c000066400000000000000000000011231420024370600242430ustar00rootroot00000000000000/* * 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-firmware.h" static void fu_plugin_synaptics_cape_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_CAPE_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CAPE_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_synaptics_cape_init; } fwupd-1.7.5/plugins/synaptics-cape/fu-synaptics-cape-device.c000066400000000000000000000612231420024370600242130ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaptics-cape-device.h" #include "fu-synaptics-cape-firmware.h" /* defines timings */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_WRITE_TIMEOUT 20000 /* us */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_READ_TIMEOUT 30000 /* us */ #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_RESET_DELAY_MS 3000 /* 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_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); if (g_getenv("FWUPD_SYNAPTICS_CAPE_VERBOSE") != NULL) fu_common_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; if (g_getenv("FWUPD_SYNAPTICS_CAPE_VERBOSE") != NULL) fu_common_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_RETRY_TIMEOUT * 1000, NULL, error)) { g_prefix_error(error, "failed to get report over interrupt ep: "); return FALSE; } if (g_getenv("FWUPD_SYNAPTICS_CAPE_VERBOSE") != NULL) fu_common_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(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: generic failure"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_ALREADY_EXISTS: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: already exists"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_APP_POINTER: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null app pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_MODULE_POINTER: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null module pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_POINTER: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: null pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_APP_ID: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: bad app id"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_MODULE_TYPE_HAS_NO_API: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: has no api"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_MAGIC_NUMBER: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: bad magic number"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_CMD_MODE_UNSUPPORTED: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: mode unsupported"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "CMD ERROR: 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, gulong delay_us, 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 = GUINT32_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; } if (delay_us > 0) g_usleep(delay_us); /* waits for the command to complete. There are two appraoches 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; g_usleep(FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL * 1000); } } 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_common_read_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 gulong delay_us, 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 = cmd_id; cmd.module_id = 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_us, 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_common_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_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "reset command is not supported"); return FALSE; } g_usleep(1000 * 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 != 1 && self->active_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}; g_autofree gchar *version_str = NULL; cmd.cmd_id = GUINT32_TO_LE(FU_SYNAPTICS_CMD_GET_VERSION); cmd.module_id = FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL; cmd.data_len = 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 */ fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error); /* 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); version_str = fu_common_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(FU_DEVICE(self), version_str); fu_device_set_version_raw(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_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; } /* checks file size */ if (bufsz < FW_CAPE_HID_HEADER_SIZE * 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file size is too small"); return NULL; } /* uses second partition if active partition is 1 */ if (self->active_partition == 1) offset = bufsz / 2; new_fw = g_bytes_new_from_bytes(fw, offset, bufsz / 2); 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), 0, 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(GPtrArray) 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, 0x00, sizeof(guint32) * FU_SYNAPTICS_CAPE_CMD_WRITE_DATAL_LEN); 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); 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); /* header */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 69); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 0); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 29); 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_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.7.5/plugins/synaptics-cape/fu-synaptics-cape-device.h000066400000000000000000000006761420024370600242250ustar00rootroot00000000000000/* * 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) struct _FuSynapticsCapeDeviceClass { FuHidDeviceClass parent_class; }; fwupd-1.7.5/plugins/synaptics-cape/fu-synaptics-cape-firmware.c000066400000000000000000000172531420024370600245740ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaptics-cape-firmware.h" typedef struct __attribute__((packed)) { guint32 data[8]; } FuCapeHidFwCmdUpdateWritePar; struct _FuSynapticsCapeFirmware { FuFirmware parent_instance; guint16 vid; guint16 pid; }; /* firmware update command structure, little endian */ typedef struct __attribute__((packed)) { guint32 vid; /* USB vendor id */ guint32 pid; /* USB product id */ guint32 fw_update_type; /* firmware update type */ guint32 fw_signature; /* firmware identifier */ guint32 crc_value; /* used to detect accidental changes to fw data */ } FuCapeHidFwCmdUpdateStartPar; typedef struct __attribute__((packed)) { FuCapeHidFwCmdUpdateStartPar par; guint16 version_w; /* firmware version is four parts number "z.y.x.w", this is last part */ guint16 version_x; /* firmware version, third part */ guint16 version_y; /* firmware version, second part */ guint16 version_z; /* firmware version, first part */ guint32 reserved3; } FuCapeHidFileHeader; G_DEFINE_TYPE(FuSynapticsCapeFirmware, fu_synaptics_cape_firmware, FU_TYPE_FIRMWARE) guint16 fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0); return self->vid; } guint16 fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0); return self->pid; } static void fu_synaptics_cape_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "vid", self->vid); fu_xmlb_builder_insert_kx(bn, "pid", self->pid); } static gboolean fu_synaptics_cape_firmware_parse_header(FuSynapticsCapeFirmware *self, FuFirmware *firmware, GBytes *fw, GError **error) { gsize bufsz = 0x0; guint16 version_w = 0; guint16 version_x = 0; guint16 version_y = 0; guint16 version_z = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *version_str = NULL; g_autoptr(FuFirmware) img_hdr = fu_firmware_new(); g_autoptr(GBytes) fw_hdr = NULL; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(firmware != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* the input fw image size should be the same as header size */ if (bufsz < sizeof(FuCapeHidFileHeader)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not enough data to parse header"); return FALSE; } if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_VID, &self->vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_PID, &self->pid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_VER_W, &version_w, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_VER_X, &version_x, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_VER_Y, &version_y, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, bufsz, FW_CAPE_HID_HEADER_OFFSET_VER_Z, &version_z, G_LITTLE_ENDIAN, error)) return FALSE; version_str = g_strdup_printf("%u.%u.%u.%u", version_z, version_y, version_x, version_w); fu_firmware_set_version(FU_FIRMWARE(self), version_str); fw_hdr = fu_common_bytes_new_offset(fw, 0, sizeof(FuCapeHidFwCmdUpdateStartPar), 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); /* success */ return TRUE; } static gboolean fu_synaptics_cape_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); const gsize bufsz = g_bytes_get_size(fw); const gsize headsz = sizeof(FuCapeHidFileHeader); g_autoptr(GBytes) fw_header = NULL; g_autoptr(GBytes) fw_body = NULL; /* check minimum size */ if (bufsz < sizeof(FuCapeHidFileHeader)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not enough data to parse header, size "); return FALSE; } if ((guint32)bufsz % 4 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 32 bits"); return FALSE; } fw_header = g_bytes_new_from_bytes(fw, 0x0, headsz); if (!fu_synaptics_cape_firmware_parse_header(self, firmware, fw_header, error)) return FALSE; fw_body = g_bytes_new_from_bytes(fw, headsz, bufsz - headsz); fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_set_bytes(firmware, fw_body); return TRUE; } static GBytes * fu_synaptics_cape_firmware_write(FuFirmware *firmware, GError **error) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); guint64 ver = fu_firmware_get_version_raw(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) payload = NULL; /* header */ fu_byte_array_append_uint32(buf, self->vid, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, self->pid, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* update type */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* identifier */ fu_byte_array_append_uint32(buf, 0xffff, G_LITTLE_ENDIAN); /* crc_value */ fu_byte_array_append_uint16(buf, ver >> 0, G_LITTLE_ENDIAN); /* version w */ fu_byte_array_append_uint16(buf, ver >> 16, G_LITTLE_ENDIAN); /* version x */ fu_byte_array_append_uint16(buf, ver >> 32, G_LITTLE_ENDIAN); /* version y */ fu_byte_array_append_uint16(buf, ver >> 48, G_LITTLE_ENDIAN); /* version z */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ /* 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_32, 0xFF); return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static gboolean fu_synaptics_cape_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "vid", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->vid = tmp; tmp = xb_node_query_text_as_uint(n, "pid", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->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->parse = fu_synaptics_cape_firmware_parse; klass_firmware->export = fu_synaptics_cape_firmware_export; klass_firmware->write = fu_synaptics_cape_firmware_write; klass_firmware->build = fu_synaptics_cape_firmware_build; } FuFirmware * fu_synaptics_cape_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_CAPE_FIRMWARE, NULL)); } fwupd-1.7.5/plugins/synaptics-cape/fu-synaptics-cape-firmware.h000066400000000000000000000021301420024370600245650ustar00rootroot00000000000000/* * 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_FINAL_TYPE(FuSynapticsCapeFirmware, fu_synaptics_cape_firmware, FU, SYNAPTICS_CAPE_FIRMWARE, FuSrecFirmware) FuFirmware * fu_synaptics_cape_firmware_new(void); guint16 fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self); guint16 fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self); #define FW_CAPE_HID_HEADER_OFFSET_VID 0x0 #define FW_CAPE_HID_HEADER_OFFSET_PID 0x4 #define FW_CAPE_HID_HEADER_OFFSET_UPDATE_TYPE 0x8 #define FW_CAPE_HID_HEADER_OFFSET_SIGNATURE 0xc #define FW_CAPE_HID_HEADER_OFFSET_CRC 0x10 #define FW_CAPE_HID_HEADER_OFFSET_VER_W 0x14 #define FW_CAPE_HID_HEADER_OFFSET_VER_X 0x16 #define FW_CAPE_HID_HEADER_OFFSET_VER_Y 0x18 #define FW_CAPE_HID_HEADER_OFFSET_VER_Z 0x1A #define FW_CAPE_HID_HEADER_SIZE 32 /* =sizeof(FuCapeHidFileHeader) */ fwupd-1.7.5/plugins/synaptics-cape/meson.build000066400000000000000000000011411420024370600214120ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCape"'] install_data(['synaptics-cape.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_cape', fu_hash, sources : [ 'fu-plugin-synaptics-cape.c', 'fu-synaptics-cape-device.c', 'fu-synaptics-cape-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/synaptics-cape/synaptics-cape.quirk000066400000000000000000000041111420024370600232500ustar00rootroot00000000000000# EPOS Raw Plus [USB\VID_1395&PID_0280] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0281] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # RAW Teams [USB\VID_1395&PID_0294] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0295] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0296] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0297] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0298] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0299] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0400] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # EPOS Morgan-T [USB\VID_1395&PID_0200] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0288] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0289] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028A] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028B] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028C] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028D] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028E] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028F] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # EPOS Morgan-V [USB\VID_1395&PID_0290] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0291] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0292] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0293] Guid = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # USB audio codec Dongle [SYNAPTICS_CAPE\CX31993] Plugin = synaptics_cape # USB audio codec Hifi [SYNAPTICS_CAPE\CX31988] Plugin = synaptics_cape fwupd-1.7.5/plugins/synaptics-cape/tests/000077500000000000000000000000001420024370600204155ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-cape/tests/synaptics-cape.bin000066400000000000000000000001001420024370600240210ustar00rootroot00000000000000Tfwupd-1.7.5/plugins/synaptics-cape/tests/synaptics-cape.builder.xml000066400000000000000000000003771420024370600255160ustar00rootroot00000000000000 has-vid-pid payload 8.41.24.0 EFSCh... header fwupd-1.7.5/plugins/synaptics-cxaudio/000077500000000000000000000000001420024370600177775ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-cxaudio/README.md000066400000000000000000000024471420024370600212650ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_17EF&PID_3083` * `USB\VID_17EF` These devices also use custom GUID values, e.g. * `SYNAPTICS_CXAUDIO\CX2198X` * `SYNAPTICS_CXAUDIO\CX21985` ## 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`. fwupd-1.7.5/plugins/synaptics-cxaudio/fu-plugin-synaptics-cxaudio.c000066400000000000000000000015541420024370600255230ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.h" static void fu_plugin_synaptics_cxaudio_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_CXAUDIO_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE); 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"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_synaptics_cxaudio_init; } fwupd-1.7.5/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-common.h000066400000000000000000000113741420024370600255230ustar00rootroot00000000000000/* * 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 typedef enum { FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_UNKNOWN, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX20562 = 20562, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070x = 20700, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2077x = 20770, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2076x = 20760, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2085x = 20850, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2089x = 20890, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2098x = 20980, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2198x = 21980, FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_LAST } FuSynapticsCxaudioDeviceKind; typedef enum { FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_ROM, FU_SYNAPTICS_CXAUDIO_MEM_KIND_LAST } FuSynapticsCxaudioMemKind; /* EEPROM */ #define FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET 0x0000 #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_DEVICE_CAPABILITIES_BYTE 0x03 #define FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE 'L' #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) typedef guint16 FuSynapticsCxaudioEepromPtr; typedef struct __attribute__((packed)) { FuSynapticsCxaudioEepromPtr PatchVersionStringAddress; guint8 CpxPatchVersion[3]; guint8 SpxPatchVersion[4]; guint8 LayoutSignature; guint8 LayoutVersion; guint8 ApplicationStatus; guint16 VendorID; guint16 ProductID; guint16 RevisionID; FuSynapticsCxaudioEepromPtr LanguageStringAddress; FuSynapticsCxaudioEepromPtr ManufacturerStringAddress; FuSynapticsCxaudioEepromPtr ProductStringAddress; FuSynapticsCxaudioEepromPtr SerialNumberStringAddress; } FuSynapticsCxaudioEepromCustomInfo; #define FU_SYNAPTICS_CXAUDIO_EEPROM_APP_STATUS_ADDRESS \ (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + \ G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, ApplicationStatus)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_SIGNATURE_ADDRESS \ (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + \ G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, LayoutSignature)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_LAYOUT_VERSION_ADDRESS \ (FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + \ G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, LayoutVersion)) typedef struct __attribute__((packed)) { guint8 Length; guint8 Type; } FuSynapticsCxaudioEepromStringHeader; typedef struct __attribute__((packed)) { guint8 PatchSignature; FuSynapticsCxaudioEepromPtr PatchAddress; } FuSynapticsCxaudioEepromPatchInfo; typedef struct __attribute__((packed)) { guint8 MagicByte; guint8 EeepromSizeCode; } FuSynapticsCxaudioEepromValiditySignature; #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET 0x0014 #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_SIZE (sizeof(FuSynapticsCxaudioEepromPatchInfo)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_SIGNATURE_ADDRESS \ (FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET + \ G_STRUCT_OFFSET(FuSynapticsCxaudioEepromPatchInfo, PatchSignature)) #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_PTR_ADDRESS \ (FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET + \ G_STRUCT_OFFSET(FuSynapticsCxaudioEepromPatchInfo, PatchAddress)) #define FU_SYNAPTICS_CXAUDIO_FIRMWARE_SIGNATURE_OFFSET \ (FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET + \ sizeof(FuSynapticsCxaudioEepromValiditySignature)) fwupd-1.7.5/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.c000066400000000000000000000662461420024370600254750ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaptics-cxaudio-common.h" #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.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_common_string_append_ku(str, idt, "ChipIdBase", self->chip_id_base); fu_common_string_append_ku(str, idt, "ChipId", self->chip_id); fu_common_string_append_kx(str, idt, "EepromLayoutVersion", self->eeprom_layout_version); fu_common_string_append_kx(str, idt, "EepromStorageAddress", self->eeprom_storage_address); fu_common_string_append_kx(str, idt, "EepromStorageSz", self->eeprom_storage_sz); fu_common_string_append_kx(str, idt, "EepromSz", self->eeprom_sz); fu_common_string_append_kb(str, idt, "SwResetSupported", self->sw_reset_supported); fu_common_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, guint32 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_common_write_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_common_bytes_compare_raw(outbuf + idx_write, payload_max, 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) { FuSynapticsCxaudioEepromStringHeader header = {0}; g_autofree gchar *str = NULL; /* read header */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address, (guint8 *)&header, sizeof(header), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM string header @0x%x: ", address); return NULL; } /* sanity check */ if (header.Type != FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_BYTE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM string header type invalid"); return NULL; } if (header.Length < sizeof(header)) { 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 - sizeof(header) + 1); if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address + sizeof(header), (guint8 *)str, header.Length - sizeof(header), 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)); FuSynapticsCxaudioEepromCustomInfo cinfo = {0x0}; guint32 addr = FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH_VERSION_ADDRESS; guint8 chip_id_offset = 0x0; guint8 sigbuf[2] = {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; /* 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; chip_id = g_strdup_printf("SYNAPTICS_CXAUDIO\\CX%u", self->chip_id); fu_device_add_instance_id(device, chip_id); /* 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 */ if (sigbuf[0] != FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM magic byte invalid, got 0x%02x expected 0x%02x", sigbuf[0], (guint)FU_SYNAPTICS_CXAUDIO_MAGIC_BYTE); return FALSE; } /* calculate EEPROM size */ self->eeprom_sz = (guint32)1 << (sigbuf[1] + 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_common_read_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, (guint8 *)&cinfo, sizeof(cinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM custom info: "); return FALSE; } if (cinfo.LayoutSignature == FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) self->eeprom_layout_version = cinfo.LayoutVersion; g_debug("CpxPatchVersion: %u.%u.%u", cinfo.CpxPatchVersion[0], cinfo.CpxPatchVersion[1], cinfo.CpxPatchVersion[2]); g_debug("SpxPatchVersion: %u.%u.%u.%u", cinfo.SpxPatchVersion[0], cinfo.SpxPatchVersion[1], cinfo.SpxPatchVersion[2], cinfo.SpxPatchVersion[3]); g_debug("VendorID: 0x%04x", cinfo.VendorID); g_debug("ProductID: 0x%04x", cinfo.ProductID); g_debug("RevisionID: 0x%04x", cinfo.RevisionID); g_debug("ApplicationStatus: 0x%02x", cinfo.ApplicationStatus); /* serial number, which also allows us to recover it after write */ if (self->eeprom_layout_version >= 0x01) { self->serial_number_set = cinfo.SerialNumberStringAddress != 0x0; if (self->serial_number_set) { g_autofree gchar *tmp = NULL; tmp = fu_synaptics_cxaudio_device_eeprom_read_string( self, cinfo.SerialNumberStringAddress, 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); 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; g_usleep(10 * 1000); 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_LAYOUT_SIGNATURE_ADDRESS, &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_LAYOUT_VERSION_ADDRESS, &value, sizeof(value), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to initialize layout signature: "); return FALSE; } g_debug("initialized layout signature"); } 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) { FuSynapticsCxaudioEepromPatchInfo pinfo = {0}; 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, (guint8 *)&pinfo, sizeof(pinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM patch info: "); return FALSE; } if (pinfo.PatchSignature == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { memset(&pinfo, 0x0, sizeof(pinfo)); 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, (guint8 *)&pinfo, sizeof(pinfo), 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)) { 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_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->chip_id_base = tmp; return TRUE; } if (g_strcmp0(key, "CxaudioSoftwareReset") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->sw_reset_supported = tmp; return TRUE; } if (g_strcmp0(key, "CxaudioPatch1ValidAddr") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->eeprom_patch_valid_addr = tmp; return TRUE; } if (g_strcmp0(key, "CxaudioPatch2ValidAddr") == 0) { if (!fu_common_strtoull_full(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_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.7.5/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.h000066400000000000000000000007051420024370600254660ustar00rootroot00000000000000/* * 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) struct _FuSynapticsCxaudioDeviceClass { FuHidDeviceClass parent_class; }; fwupd-1.7.5/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.c000066400000000000000000000260061420024370600260400ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaptics-cxaudio-firmware.h" struct _FuSynapticsCxaudioFirmware { FuSrecFirmwareClass parent_instance; FuSynapticsCxaudioFileKind file_kind; FuSynapticsCxaudioDeviceKind device_kind; FuSynapticsCxaudioEepromCustomInfo cinfo; }; 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->cinfo.LayoutVersion; } static void fu_synaptics_cxaudio_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "file_kind", self->file_kind); fu_xmlb_builder_insert_kx(bn, "device_kind", self->device_kind); fu_xmlb_builder_insert_kx(bn, "layout_signature", self->cinfo.LayoutSignature); fu_xmlb_builder_insert_kx(bn, "layout_version", self->cinfo.LayoutVersion); if (self->cinfo.LayoutVersion >= 1) { fu_xmlb_builder_insert_kx(bn, "vid", self->cinfo.VendorID); fu_xmlb_builder_insert_kx(bn, "pid", self->cinfo.ProductID); fu_xmlb_builder_insert_kx(bn, "rev", self->cinfo.RevisionID); } } 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, guint64 addr_start, guint64 addr_end, 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_autofree guint8 *shadow = g_malloc0(FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE); /* 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, FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE, rcd->addr, /* dst */ rcd->buf->data, rcd->buf->len, 0x0, /* src */ rcd->buf->len, error)) return FALSE; } /* parse EEPROM map */ if (!fu_memcpy_safe((guint8 *)&self->cinfo, sizeof(FuSynapticsCxaudioEepromCustomInfo), 0x0, /* dst */ shadow, FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, /* src */ sizeof(FuSynapticsCxaudioEepromCustomInfo), error)) return FALSE; /* just layout version byte is not enough in case of old CX20562 patch * files that could have non-zero value of the Layout version */ if (shadow[FU_SYNAPTICS_CXAUDIO_FIRMWARE_SIGNATURE_OFFSET] == 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 (shadow[FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_SIGNATURE_ADDRESS] == 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->cinfo.LayoutVersion >= 1) { 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_APP_STATUS_ADDRESS, 1); fu_synaptics_cxaudio_firmware_badblock_add( badblocks, "boot bytes", FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, sizeof(FuSynapticsCxaudioEepromValiditySignature) + 1); /* serial number address and also string pointer itself if set */ if (self->cinfo.SerialNumberStringAddress != 0x0) { FuSynapticsCxaudioEepromPtr addr_tmp; FuSynapticsCxaudioEepromPtr addr_str; addr_tmp = FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + G_STRUCT_OFFSET(FuSynapticsCxaudioEepromCustomInfo, SerialNumberStringAddress); fu_synaptics_cxaudio_firmware_badblock_add( badblocks, "serial number", addr_tmp, sizeof(FuSynapticsCxaudioEepromPtr)); memcpy(&addr_str, shadow + addr_tmp, sizeof(addr_str)); fu_synaptics_cxaudio_firmware_badblock_add(badblocks, "serial number data", addr_str, shadow[addr_str]); } fu_synaptics_cxaudio_firmware_avoid_badblocks(badblocks, records); } /* this isn't used, but it seems a good thing to add */ fu_firmware_set_bytes(firmware, fw); 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.7.5/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.h000066400000000000000000000024641420024370600260470ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-cxaudio-common.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) typedef enum { FU_SYNAPTICS_CXAUDIO_FILE_KIND_UNKNOWN, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2077X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2076X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2085X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2089X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2098X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2198X_PATCH, FU_SYNAPTICS_CXAUDIO_FILE_KIND_LAST } FuSynapticsCxaudioFileKind; 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.7.5/plugins/synaptics-cxaudio/meson.build000066400000000000000000000011511420024370600221370ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCxaudio"'] install_data(['synaptics-cxaudio.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_cxaudio', fu_hash, sources : [ 'fu-plugin-synaptics-cxaudio.c', 'fu-synaptics-cxaudio-device.c', 'fu-synaptics-cxaudio-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/synaptics-cxaudio/synaptics-cxaudio.quirk000066400000000000000000000014331420024370600245240ustar00rootroot00000000000000# ThinkPad TBT3-TR Gen 2 dock [USB\VID_17EF&PID_3083] Guid = SYNAPTICS_CXAUDIO\CX2098X ParentGuid = USB\VID_17EF&PID_307F&HUB_0006 # ThinkPad TBT3-MS Gen 2 dock [USB\VID_17EF&PID_3092] Guid = SYNAPTICS_CXAUDIO\CX2198X ParentGuid = USB\VID_17EF&PID_308F # ThinkPad USB-C Dock Gen2 Audio [USB\VID_17EF&PID_A396] Guid = SYNAPTICS_CXAUDIO\CX2198X ParentGuid = USB\VID_17EF&PID_A391 # Google Pixel USB-C headphones [USB\VID_18D1&PID_5033] Guid = SYNAPTICS_CXAUDIO\CX2198X # Google Pixel USB-C <-> 3.5mm adapter [USB\VID_18D1&PID_5034] Guid = SYNAPTICS_CXAUDIO\CX2198X [SYNAPTICS_CXAUDIO\CX2098X] Plugin = synaptics_cxaudio CxaudioChipIdBase = 20980 [SYNAPTICS_CXAUDIO\CX2198X] Plugin = synaptics_cxaudio CxaudioChipIdBase = 21980 CxaudioPatch1ValidAddr = 0x0014 CxaudioPatch2ValidAddr = 0x0171 fwupd-1.7.5/plugins/synaptics-mst/000077500000000000000000000000001420024370600171465ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-mst/README.md000066400000000000000000000053061420024370600204310ustar00rootroot00000000000000# Synaptics MST 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)` ## 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: ### libsmbios At compilation time and runtime you will need libsmbios_c version 2.3.0 or later * [source](https://github.com/dell/libsmbios) * [rpms](https://apps.fedoraproject.org/packages/libsmbios) * [debs (Debian)](http://tracker.debian.org/pkg/libsmbios) * [debs (Ubuntu)](http://launchpad.net/ubuntu/+source/libsmbios) If you don't want or need this functionality you can use the `--disable-dell` option. ## 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*`. fwupd-1.7.5/plugins/synaptics-mst/fu-plugin-synaptics-mst.c000066400000000000000000000120171420024370600240350ustar00rootroot00000000000000/* * 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 #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-device.h" #include "fu-synaptics-mst-firmware.h" #define FU_SYNAPTICS_MST_DRM_REPLUG_DELAY 5 /* s */ struct FuPluginData { GPtrArray *devices; guint drm_changed_id; }; static void fu_plugin_synaptics_mst_device_rescan(FuPlugin *plugin, FuDevice *device) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* open fd */ locker = fu_device_locker_new(device, &error_local); if (locker == NULL) { g_debug("failed to open device %s: %s", fu_device_get_logical_id(device), error_local->message); return; } if (!fu_device_rescan(device, &error_local)) { g_debug("no device found on %s: %s", fu_device_get_logical_id(device), error_local->message); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)) fu_plugin_device_remove(plugin, device); } else { fu_plugin_device_add(plugin, device); } } /* reprobe all existing devices added by this plugin */ static void fu_plugin_synaptics_mst_rescan(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); for (guint i = 0; i < priv->devices->len; i++) { FuDevice *device = FU_DEVICE(g_ptr_array_index(priv->devices, i)); fu_plugin_synaptics_mst_device_rescan(plugin, device); } } static gboolean fu_plugin_synaptics_mst_rescan_cb(gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuPluginData *priv = fu_plugin_get_data(plugin); fu_plugin_synaptics_mst_rescan(plugin); priv->drm_changed_id = 0; return FALSE; } static gboolean fu_plugin_synaptics_mst_backend_device_changed(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "drm") != 0) return TRUE; /* recoldplug all drm_dp_aux_dev devices after a *long* delay */ if (priv->drm_changed_id != 0) g_source_remove(priv->drm_changed_id); priv->drm_changed_id = g_timeout_add_seconds(FU_SYNAPTICS_MST_DRM_REPLUG_DELAY, fu_plugin_synaptics_mst_rescan_cb, plugin); return TRUE; } static gboolean fu_plugin_synaptics_mst_backend_device_added(FuPlugin *plugin, FuDevice *device, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *priv = fu_plugin_get_data(plugin); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapticsMstDevice) dev = NULL; /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; dev = fu_synaptics_mst_device_new(FU_UDEV_DEVICE(device)); locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; /* for SynapticsMstDeviceKind=system devices */ fu_synaptics_mst_device_set_system_type( FU_SYNAPTICS_MST_DEVICE(dev), fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_SKU)); /* this might fail if there is nothing connected */ fu_plugin_synaptics_mst_device_rescan(plugin, FU_DEVICE(dev)); g_ptr_array_add(priv->devices, g_steal_pointer(&dev)); return TRUE; } static gboolean fu_plugin_synaptics_mst_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_plugin_synaptics_mst_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *priv = fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); /* devices added by this plugin */ priv->devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_plugin_add_udev_subsystem(plugin, "drm"); /* used for uevent only */ fu_plugin_add_udev_subsystem(plugin, "drm_dp_aux_dev"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_MST_FIRMWARE); fu_context_add_quirk_key(ctx, "SynapticsMstDeviceKind"); } static void fu_plugin_synaptics_mst_destroy(FuPlugin *plugin) { FuPluginData *priv = fu_plugin_get_data(plugin); if (priv->drm_changed_id != 0) g_source_remove(priv->drm_changed_id); g_ptr_array_unref(priv->devices); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_synaptics_mst_init; vfuncs->destroy = fu_plugin_synaptics_mst_destroy; vfuncs->write_firmware = fu_plugin_synaptics_mst_write_firmware; vfuncs->backend_device_added = fu_plugin_synaptics_mst_backend_device_added; vfuncs->backend_device_changed = fu_plugin_synaptics_mst_backend_device_changed; } fwupd-1.7.5/plugins/synaptics-mst/fu-self-test.c000066400000000000000000000146451420024370600216420ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-context-private.h" #include "fu-plugin-private.h" #include "fu-synaptics-mst-firmware.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(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); while ((basename = g_dir_read_name(dir)) != NULL) { g_autofree gchar *fn = g_build_filename(path, basename, NULL); g_autoptr(FuUdevDevice) dev = NULL; if (!g_str_has_prefix(basename, "drm_dp_aux")) continue; dev = g_object_new(FU_TYPE_UDEV_DEVICE, "context", ctx, "physical-id", "PCI_SLOT_NAME=0000:3e:00.0", "logical-id", basename, "subsystem", "drm_dp_aux_dev", "device-file", fn, NULL); g_debug("creating drm_dp_aux_dev object backed by %s", fn); ret = fu_plugin_runner_backend_device_added(plugin, FU_DEVICE(dev), &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 = fu_plugin_new(ctx); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autofree gchar *pluginfn = NULL; 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); g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &devices); pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_synaptics_mst." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(plugin, &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(FuPlugin) plugin = fu_plugin_new(ctx); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autofree gchar *pluginfn = NULL; 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); g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &devices); pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_synaptics_mst." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(plugin, &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_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); 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.7.5/plugins/synaptics-mst/fu-synaptics-mst-common.c000066400000000000000000000027701420024370600240340ustar00rootroot00000000000000/* * 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" const gchar * fu_synaptics_mst_mode_to_string(FuSynapticsMstMode mode) { if (mode == FU_SYNAPTICS_MST_MODE_DIRECT) return "DIRECT"; if (mode == FU_SYNAPTICS_MST_MODE_REMOTE) return "REMOTE"; return NULL; } const gchar * fu_synaptics_mst_family_to_string(FuSynapticsMstFamily family) { if (family == FU_SYNAPTICS_MST_FAMILY_TESLA) return "tesla"; if (family == FU_SYNAPTICS_MST_FAMILY_LEAF) return "leaf"; if (family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) return "panamera"; if (family == FU_SYNAPTICS_MST_FAMILY_CAYENNE) return "cayenne"; if (family == FU_SYNAPTICS_MST_FAMILY_SPYDER) return "spyder"; return NULL; } 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; } fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-common.h000066400000000000000000000024461420024370600240410ustar00rootroot00000000000000/* * 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 #define SYNAPTICS_FLASH_MODE_DELAY 3 /* seconds */ /** * FuSynapticsMstMode: * @FU_SYNAPTICS_MST_MODE_UNKNOWN: Type invalid or not known * @FU_SYNAPTICS_MST_MODE_DIRECT: Directly addressable * @FU_SYNAPTICS_MST_MODE_REMOTE: Requires remote register work * * The device type. **/ typedef enum { FU_SYNAPTICS_MST_MODE_UNKNOWN, FU_SYNAPTICS_MST_MODE_DIRECT, FU_SYNAPTICS_MST_MODE_REMOTE, /*< private >*/ FU_SYNAPTICS_MST_MODE_LAST } FuSynapticsMstMode; typedef enum { FU_SYNAPTICS_MST_FAMILY_UNKNOWN, FU_SYNAPTICS_MST_FAMILY_TESLA, FU_SYNAPTICS_MST_FAMILY_LEAF, FU_SYNAPTICS_MST_FAMILY_PANAMERA, FU_SYNAPTICS_MST_FAMILY_CAYENNE, FU_SYNAPTICS_MST_FAMILY_SPYDER, /**/ FU_SYNAPTICS_MST_FAMILY_LAST } FuSynapticsMstFamily; const gchar * fu_synaptics_mst_mode_to_string(FuSynapticsMstMode mode); const gchar * fu_synaptics_mst_family_to_string(FuSynapticsMstFamily family); FuSynapticsMstFamily fu_synaptics_mst_family_from_chip_id(guint16 chip_id); fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-connection.c000066400000000000000000000304671420024370600247070ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-connection.h" #define UNIT_SIZE 32 #define MAX_WAIT_TIME 3 /* unit : second */ struct _FuSynapticsMstConnection { GObject parent_instance; gint fd; /* not owned by the connection */ guint8 layer; guint8 remain_layer; guint8 rad; }; G_DEFINE_TYPE(FuSynapticsMstConnection, fu_synaptics_mst_connection, G_TYPE_OBJECT) static void fu_synaptics_mst_connection_init(FuSynapticsMstConnection *self) { } static void fu_synaptics_mst_connection_class_init(FuSynapticsMstConnectionClass *klass) { } static gboolean fu_synaptics_mst_connection_aux_node_read(FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, gint length, GError **error) { if (lseek(self->fd, offset, SEEK_SET) != offset) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x on layer:%u, rad:0x%x", offset, self->layer, self->rad); return FALSE; } if (read(self->fd, buf, length) != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to read 0x%x bytes on layer:%u, rad:0x%x", (guint)length, self->layer, self->rad); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_connection_aux_node_write(FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, gint length, GError **error) { if (lseek(self->fd, offset, SEEK_SET) != offset) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x on layer:%u, rad:0x%x", offset, self->layer, self->rad); return FALSE; } if (write(self->fd, buf, length) != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to write 0x%x bytes on layer:%u, rad:0x%x", (guint)length, self->layer, self->rad); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_connection_bus_read(FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error) { return fu_synaptics_mst_connection_aux_node_read(self, offset, buf, length, error); } static gboolean fu_synaptics_mst_connection_bus_write(FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error) { return fu_synaptics_mst_connection_aux_node_write(self, offset, buf, length, error); } FuSynapticsMstConnection * fu_synaptics_mst_connection_new(gint fd, guint8 layer, guint rad) { FuSynapticsMstConnection *self = g_object_new(FU_TYPE_SYNAPTICS_MST_CONNECTION, NULL); self->fd = fd; self->layer = layer; self->remain_layer = layer; self->rad = rad; return self; } gboolean fu_synaptics_mst_connection_read(FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error) { if (self->layer && self->remain_layer) { guint8 node; gboolean result; self->remain_layer--; node = (self->rad >> self->remain_layer * 2) & 0x03; result = fu_synaptics_mst_connection_rc_get_command(self, UPDC_READ_FROM_TX_DPCD + node, length, offset, (guint8 *)buf, error); self->remain_layer++; return result; } return fu_synaptics_mst_connection_bus_read(self, offset, buf, length, error); } gboolean fu_synaptics_mst_connection_write(FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error) { if (self->layer && self->remain_layer) { guint8 node; gboolean result; self->remain_layer--; node = (self->rad >> self->remain_layer * 2) & 0x03; result = fu_synaptics_mst_connection_rc_set_command(self, UPDC_WRITE_TO_TX_DPCD + node, length, offset, (guint8 *)buf, error); self->remain_layer++; return result; } return fu_synaptics_mst_connection_bus_write(self, offset, buf, length, error); } gboolean fu_synaptics_mst_connection_rc_set_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, const guint8 *buf, GError **error) { guint32 cur_offset = offset; guint32 cur_length; gint data_left = length; gint cmd; gint readData = 0; long deadline; struct timespec t_spec; do { if (data_left > UNIT_SIZE) { cur_length = UNIT_SIZE; } else { cur_length = data_left; } if (cur_length) { /* write data */ if (!fu_synaptics_mst_connection_write(self, REG_RC_DATA, buf, cur_length, error)) { g_prefix_error(error, "failure writing data register: "); return FALSE; } /* write offset */ if (!fu_synaptics_mst_connection_write(self, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error(error, "failure writing offset register: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write(self, REG_RC_LEN, (guint8 *)&cur_length, 4, error)) { g_prefix_error(error, "failure writing length register: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!fu_synaptics_mst_connection_write(self, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error(error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime(CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!fu_synaptics_mst_connection_read(self, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error(error, "failed to read command: "); return FALSE; } clock_gettime(CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %d", (readData >> 8) & 0xFF); return FALSE; } buf += cur_length; cur_offset += cur_length; data_left -= cur_length; } while (data_left); return TRUE; } gboolean fu_synaptics_mst_connection_rc_get_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, guint8 *buf, GError **error) { guint32 cur_offset = offset; guint32 cur_length; gint data_need = length; guint32 cmd; guint32 readData = 0; long deadline; struct timespec t_spec; while (data_need) { if (data_need > UNIT_SIZE) { cur_length = UNIT_SIZE; } else { cur_length = data_need; } if (cur_length) { /* write offset */ if (!fu_synaptics_mst_connection_write(self, REG_RC_OFFSET, (guint8 *)&cur_offset, 4, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write(self, REG_RC_LEN, (guint8 *)&cur_length, 4, error)) { g_prefix_error(error, "failed to write length: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!fu_synaptics_mst_connection_write(self, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error(error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime(CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!fu_synaptics_mst_connection_read(self, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error(error, "failed to read command: "); return FALSE; } clock_gettime(CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %u", (readData >> 8) & 0xFF); return FALSE; } if (cur_length) { if (!fu_synaptics_mst_connection_read(self, REG_RC_DATA, buf, cur_length, error)) { g_prefix_error(error, "failed to read data: "); return FALSE; } } buf += cur_length; cur_offset += cur_length; data_need -= cur_length; } return TRUE; } gboolean fu_synaptics_mst_connection_rc_special_get_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 cmd_length, guint32 cmd_offset, guint8 *cmd_data, guint32 length, guint8 *buf, GError **error) { guint32 readData = 0; guint32 cmd; long deadline; struct timespec t_spec; if (cmd_length) { /* write cmd data */ if (cmd_data != NULL) { if (!fu_synaptics_mst_connection_write(self, REG_RC_DATA, cmd_data, cmd_length, error)) { g_prefix_error(error, "Failed to write command data: "); return FALSE; } } /* write offset */ if (!fu_synaptics_mst_connection_write(self, REG_RC_OFFSET, (guint8 *)&cmd_offset, 4, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } /* write length */ if (!fu_synaptics_mst_connection_write(self, REG_RC_LEN, (guint8 *)&cmd_length, 4, error)) { g_prefix_error(error, "failed to write length: "); return FALSE; } } /* send command */ cmd = 0x80 | rc_cmd; if (!fu_synaptics_mst_connection_write(self, REG_RC_CMD, (guint8 *)&cmd, 1, error)) { g_prefix_error(error, "failed to write command: "); return FALSE; } /* wait command complete */ clock_gettime(CLOCK_REALTIME, &t_spec); deadline = t_spec.tv_sec + MAX_WAIT_TIME; do { if (!fu_synaptics_mst_connection_read(self, REG_RC_CMD, (guint8 *)&readData, 2, error)) { g_prefix_error(error, "failed to read command: "); return FALSE; } clock_gettime(CLOCK_REALTIME, &t_spec); if (t_spec.tv_sec > deadline) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "timeout exceeded"); return FALSE; } } while (readData & 0x80); if (readData & 0xFF00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "remote command failed: %u", (readData >> 8) & 0xFF); return FALSE; } if (length) { if (!fu_synaptics_mst_connection_read(self, REG_RC_DATA, buf, length, error)) { g_prefix_error(error, "failed to read length: "); } } return TRUE; } gboolean fu_synaptics_mst_connection_enable_rc(FuSynapticsMstConnection *self, GError **error) { const gchar *sc = "PRIUS"; for (gint i = 0; i <= self->layer; i++) { g_autoptr(FuSynapticsMstConnection) connection_tmp = NULL; connection_tmp = fu_synaptics_mst_connection_new(self->fd, i, self->rad); if (!fu_synaptics_mst_connection_rc_set_command(connection_tmp, UPDC_ENABLE_RC, 5, 0, (guint8 *)sc, error)) { g_prefix_error(error, "failed to enable remote control: "); return FALSE; } } return TRUE; } gboolean fu_synaptics_mst_connection_disable_rc(FuSynapticsMstConnection *self, GError **error) { for (gint i = self->layer; i >= 0; i--) { g_autoptr(FuSynapticsMstConnection) connection_tmp = NULL; connection_tmp = fu_synaptics_mst_connection_new(self->fd, i, self->rad); if (!fu_synaptics_mst_connection_rc_set_command(connection_tmp, UPDC_DISABLE_RC, 0, 0, NULL, error)) { g_prefix_error(error, "failed to disable remote control: "); return FALSE; } } return TRUE; } fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-connection.h000066400000000000000000000060761420024370600247130ustar00rootroot00000000000000/* * 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 #define FU_TYPE_SYNAPTICS_MST_CONNECTION (fu_synaptics_mst_connection_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsMstConnection, fu_synaptics_mst_connection, FU, SYNAPTICS_MST_CONNECTION, GObject) #define ADDR_CUSTOMER_ID 0X10E #define ADDR_BOARD_ID 0x10F #define ADDR_MEMORY_CUSTOMER_ID_CAYENNE 0x9000024E #define ADDR_MEMORY_BOARD_ID_CAYENNE 0x9000024F #define ADDR_MEMORY_CUSTOMER_ID 0x170E #define ADDR_MEMORY_BOARD_ID 0x170F #define REG_RC_CAP 0x4B0 #define REG_RC_STATE 0X4B1 #define REG_RC_CMD 0x4B2 #define REG_RC_RESULT 0x4B3 #define REG_RC_LEN 0x4B8 #define REG_RC_OFFSET 0x4BC #define REG_RC_DATA 0x4C0 #define REG_VENDOR_ID 0x500 #define REG_CHIP_ID 0x507 #define REG_FIRMWARE_VERSION 0x50A typedef enum { UPDC_COMMAND_SUCCESS = 0, UPDC_COMMAND_INVALID, UPDC_COMMAND_UNSUPPORT, UPDC_COMMAND_FAILED, UPDC_COMMAND_DISABLED, } SynapticsMstUpdcRc; typedef enum { UPDC_ENABLE_RC = 0x01, UPDC_DISABLE_RC = 0x02, UPDC_GET_ID = 0x03, UPDC_GET_VERSION = 0x04, UPDC_FLASH_MAPPING = 0x07, UPDC_ENABLE_FLASH_CHIP_ERASE = 0x08, UPDC_CAL_EEPROM_CHECKSUM = 0x11, UPDC_FLASH_ERASE = 0x14, UPDC_CAL_EEPROM_CHECK_CRC8 = 0x16, UPDC_CAL_EEPROM_CHECK_CRC16 = 0x17, UPDC_ACTIVATE_FIRMWARE = 0X18, UPDC_WRITE_TO_EEPROM = 0X20, UPDC_WRITE_TO_MEMORY = 0x21, UPDC_WRITE_TO_TX_DPCD = 0x22, UPDC_READ_FROM_EEPROM = 0x30, UPDC_READ_FROM_MEMORY = 0x31, UPDC_READ_FROM_TX_DPCD = 0x32, } SynapticsMstUpdcCmd; FuSynapticsMstConnection * fu_synaptics_mst_connection_new(gint fd, guint8 layer, guint rad); gboolean fu_synaptics_mst_connection_read(FuSynapticsMstConnection *self, guint32 offset, guint8 *buf, guint32 length, GError **error); gboolean fu_synaptics_mst_connection_write(FuSynapticsMstConnection *self, guint32 offset, const guint8 *buf, guint32 length, GError **error); gboolean fu_synaptics_mst_connection_rc_set_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, const guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_rc_get_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 length, guint32 offset, guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_rc_special_get_command(FuSynapticsMstConnection *self, guint32 rc_cmd, guint32 cmd_length, guint32 cmd_offset, guint8 *cmd_data, guint32 length, guint8 *buf, GError **error); gboolean fu_synaptics_mst_connection_enable_rc(FuSynapticsMstConnection *self, GError **error); gboolean fu_synaptics_mst_connection_disable_rc(FuSynapticsMstConnection *self, GError **error); fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-device.c000066400000000000000000001314041420024370600240000ustar00rootroot00000000000000/* * 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-connection.h" #include "fu-synaptics-mst-device.h" #include "fu-synaptics-mst-firmware.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 CRC_8 8 #define CRC_16 16 #define REG_ESM_DISABLE 0x2000fc #define REG_QUAD_DISABLE 0x200fc0 #define REG_HDCP22_DISABLE 0x200f90 #define FLASH_SETTLE_TIME 5000000 /* us */ /** * FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID: * * Ignore board ID firmware mismatch. */ #define FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID (1 << 0) struct _FuSynapticsMstDevice { FuUdevDevice parent_instance; gchar *device_kind; gchar *system_type; guint64 write_block_size; FuSynapticsMstFamily family; FuSynapticsMstMode mode; guint8 active_bank; guint8 layer; guint16 rad; /* relative address */ guint32 board_id; guint16 chip_id; }; G_DEFINE_TYPE(FuSynapticsMstDevice, fu_synaptics_mst_device, FU_TYPE_UDEV_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_init(FuSynapticsMstDevice *self) { FuUdevDeviceFlags flags = FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT; if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) != NULL) flags |= FU_UDEV_DEVICE_FLAG_OPEN_WRITE; fu_udev_device_set_flags(FU_UDEV_DEVICE(self), flags); 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_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); } static void fu_synaptics_mst_device_to_string(FuDevice *device, guint idt, GString *str) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_synaptics_mst_device_parent_class)->to_string(device, idt, str); fu_common_string_append_kv(str, idt, "DeviceKind", self->device_kind); if (self->mode != FU_SYNAPTICS_MST_MODE_UNKNOWN) { fu_common_string_append_kv(str, idt, "Mode", fu_synaptics_mst_mode_to_string(self->mode)); } if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) fu_common_string_append_kx(str, idt, "ActiveBank", self->active_bank); fu_common_string_append_kx(str, idt, "Layer", self->layer); fu_common_string_append_kx(str, idt, "Rad", self->rad); if (self->board_id != 0x0) fu_common_string_append_ku(str, idt, "BoardId", self->board_id); if (self->chip_id != 0x0) fu_common_string_append_kx(str, idt, "ChipId", self->chip_id); } static gboolean fu_synaptics_mst_device_enable_rc(FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; /* in test mode */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); return fu_synaptics_mst_connection_enable_rc(connection, error); } static gboolean fu_synaptics_mst_device_disable_rc(FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; /* in test mode */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); return fu_synaptics_mst_connection_disable_rc(connection, error); } static gboolean fu_synaptics_mst_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_synaptics_mst_device_parent_class)->probe(device, error)) return FALSE; /* get from sysfs if not set from tests */ if (fu_device_get_logical_id(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); } return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci,drm_dp_aux_dev", error); } static gboolean fu_synaptics_mst_device_get_flash_checksum(FuSynapticsMstDevice *self, guint32 length, guint32 offset, guint32 *checksum, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_rc_special_get_command(connection, UPDC_CAL_EEPROM_CHECKSUM, length, offset, NULL, 4, (guint8 *)checksum, error)) { g_prefix_error(error, "failed to get flash checksum: "); return FALSE; } return TRUE; } static guint16 fu_synaptics_mst_device_get_crc(guint16 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 remainder = (guint16)crc; const guint8 *message = payload_data; if (type == CRC_8) { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ remainder); remainder = CRC8_table[val]; } } else { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ (remainder >> 8)); remainder = CRC16_table[val] ^ (remainder << 8); } } return remainder; } static gboolean fu_synaptics_mst_device_set_flash_sector_erase(FuSynapticsMstDevice *self, guint16 rc_cmd, guint16 offset, GError **error) { guint16 us_data; g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); /* Need to add Wp control ? */ us_data = rc_cmd + offset; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_FLASH_ERASE, 2, 0, (guint8 *)&us_data, error)) { g_prefix_error(error, "can't sector erase flash at offset %x: ", offset); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_update_esm(FuSynapticsMstDevice *self, const guint8 *payload_data, FuProgress *progress, GError **error) { guint32 checksum = 0; guint32 esm_sz = ESM_CODE_SIZE; guint32 flash_checksum = 0; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; g_autoptr(FuSynapticsMstConnection) connection = NULL; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); if (!fu_synaptics_mst_device_get_flash_checksum(self, esm_sz, EEPROM_ESM_OFFSET, &flash_checksum, error)) { return FALSE; } /* ESM checksum same */ checksum = fu_common_sum32(payload_data + EEPROM_ESM_OFFSET, esm_sz); if (checksum == flash_checksum) { g_debug("ESM checksum already matches"); return TRUE; } g_debug("ESM checksum %x doesn't match expected %x", flash_checksum, checksum); /* update ESM firmware */ write_loops = esm_sz / unit_sz; for (guint retries_cnt = 0;; retries_cnt++) { guint32 write_idx = 0; guint32 write_offset = EEPROM_ESM_OFFSET; const guint8 *esm_code_ptr = &payload_data[EEPROM_ESM_OFFSET]; /* 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"); g_usleep(FLASH_SETTLE_TIME); /* write firmware */ for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, esm_code_ptr + write_idx, &error_local)) { g_warning("failed to write ESM: %s", error_local->message); break; } write_offset += unit_sz; write_idx += unit_sz; fu_progress_set_percentage_full(progress, (goffset)i + 1, (goffset)write_loops); } /* check ESM checksum */ flash_checksum = 0; if (!fu_synaptics_mst_device_get_flash_checksum(self, esm_sz, EEPROM_ESM_OFFSET, &flash_checksum, error)) return FALSE; /* ESM update done */ if (checksum == flash_checksum) break; g_debug("attempt %u: ESM checksum %x didn't match %x", retries_cnt, flash_checksum, checksum); /* abort */ if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum did not match after %u tries", retries_cnt); return FALSE; } } g_debug("ESM successfully written"); return TRUE; } static gboolean fu_synaptics_mst_device_update_tesla_leaf_firmware(FuSynapticsMstDevice *self, guint32 payload_len, const guint8 *payload_data, FuProgress *progress, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; guint32 data_to_write = 0; guint32 offset = 0; guint32 write_loops = 0; write_loops = (payload_len / BLOCK_UNIT); data_to_write = payload_len; if (payload_len % BLOCK_UNIT) write_loops++; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); for (guint32 retries_cnt = 0;; retries_cnt++) { guint32 checksum; 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"); g_usleep(FLASH_SETTLE_TIME); for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; guint8 length = BLOCK_UNIT; if (data_to_write < BLOCK_UNIT) length = data_to_write; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, &error_local)) { g_warning("Failed to write flash offset 0x%04x: %s, retrying", offset, error_local->message); /* repeat once */ if (!fu_synaptics_mst_connection_rc_set_command( connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, error)) { g_prefix_error(error, "can't write flash offset 0x%04x: ", offset); return FALSE; } } offset += length; data_to_write -= length; fu_progress_set_percentage_full(progress, (goffset)i + 1, (goffset)write_loops); } /* check data just written */ if (!fu_synaptics_mst_device_get_flash_checksum(self, payload_len, 0, &flash_checksum, error)) return FALSE; checksum = fu_common_sum32(payload_data, payload_len); if (checksum == flash_checksum) break; g_debug("attempt %u: checksum %x didn't match %x", retries_cnt, flash_checksum, checksum); if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum %x mismatched %x", flash_checksum, checksum); return FALSE; } } return TRUE; } static gboolean fu_synaptics_mst_device_get_active_bank_panamera(FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; guint32 buf[16]; /* get used bank */ connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_rc_get_command(connection, UPDC_READ_FROM_MEMORY, ((sizeof(buf) / sizeof(buf[0])) * 4), (gint)0x20010c, (guint8 *)buf, 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(FuSynapticsMstDevice *self, guint32 payload_len, const guint8 *payload_data, FuProgress *progress, GError **error) { guint16 crc_tmp = 0; guint32 fw_size; guint32 unit_sz = BLOCK_UNIT; guint32 write_loops = 0; guint8 bank_to_update = BANKTAG_1; guint8 readBuf[256]; guint8 tagData[16]; struct tm *pTM; time_t timeptr; g_autoptr(FuSynapticsMstConnection) connection = NULL; /* get used bank */ if (!fu_synaptics_mst_device_get_active_bank_panamera(self, error)) return FALSE; if (self->active_bank == BANKTAG_1) bank_to_update = BANKTAG_0; g_debug("bank to update:%x", bank_to_update); /* get firmware size */ fw_size = 0x410 + (*(payload_data + 0x400) << 24) + (*(payload_data + 0x401) << 16) + (*(payload_data + 0x402) << 8) + (*(payload_data + 0x403)); /* Current max firmware size is 104K */ if (fw_size < payload_len) fw_size = 104 * 1024; g_debug("Calculated fw size as %u", fw_size); /* Update firmware */ write_loops = fw_size / unit_sz; if (fw_size % unit_sz) write_loops++; for (guint32 retries_cnt = 0;; retries_cnt++) { guint32 checksum = 0; guint32 erase_offset; guint32 flash_checksum = 0; guint32 write_idx; guint32 write_offset; /* erase storage */ erase_offset = bank_to_update * 2; 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"); g_usleep(FLASH_SETTLE_TIME); /* write */ write_idx = 0; write_offset = EEPROM_BANK_OFFSET * bank_to_update; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, payload_data + write_idx, &error_local)) { g_warning("Write failed: %s, retrying", error_local->message); /* repeat once */ if (!fu_synaptics_mst_connection_rc_set_command( connection, UPDC_WRITE_TO_EEPROM, unit_sz, write_offset, payload_data + write_idx, error)) { g_prefix_error(error, "firmware write failed: "); return FALSE; } } write_offset += unit_sz; write_idx += unit_sz; fu_progress_set_percentage_full(progress, (goffset)i + 1, (goffset)write_loops); } /* verify CRC */ checksum = fu_synaptics_mst_device_get_crc(0, 16, fw_size, payload_data); for (guint32 i = 0; i < 4; i++) { g_usleep(1000); /* wait crc calculation */ if (!fu_synaptics_mst_connection_rc_special_get_command( connection, UPDC_CAL_EEPROM_CHECK_CRC16, fw_size, (EEPROM_BANK_OFFSET * bank_to_update), NULL, 4, (guint8 *)(&flash_checksum), error)) { g_prefix_error(error, "Failed to get flash checksum: "); return FALSE; } } if (checksum == flash_checksum) break; if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware update fail"); return FALSE; } g_usleep(2000); } /* set tag valid */ time(&timeptr); pTM = localtime(&timeptr); memset(tagData, 0, sizeof(tagData)); memset(readBuf, 0, sizeof(readBuf)); tagData[1] = pTM->tm_mon + 1; tagData[2] = pTM->tm_mday; tagData[3] = pTM->tm_year + 1900 - 2000; crc_tmp = fu_synaptics_mst_device_get_crc(0, 16, fw_size, payload_data); tagData[0] = bank_to_update; tagData[4] = (crc_tmp >> 8) & 0xff; tagData[5] = crc_tmp & 0xff; tagData[15] = (guint8)fu_synaptics_mst_device_get_crc(0, 8, 15, tagData); g_debug("tag date %x %x %x crc %x %x %x %x", tagData[1], tagData[2], tagData[3], tagData[0], tagData[4], tagData[5], tagData[15]); for (guint32 retries_cnt = 0;; retries_cnt++) { gboolean match = TRUE; if (!fu_synaptics_mst_connection_rc_set_command( connection, UPDC_WRITE_TO_EEPROM, 16, (EEPROM_BANK_OFFSET * bank_to_update + EEPROM_TAG_OFFSET), tagData, error)) { g_prefix_error(error, "failed to write tag: "); return FALSE; } g_usleep(200); if (!fu_synaptics_mst_connection_rc_get_command( connection, UPDC_READ_FROM_EEPROM, 16, (EEPROM_BANK_OFFSET * bank_to_update + EEPROM_TAG_OFFSET), readBuf, error)) { g_prefix_error(error, "failed to read tag: "); return FALSE; } for (guint32 i = 0; i < 16; i++) { if (readBuf[i] != tagData[i]) { match = FALSE; break; } } if (match) break; if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag valid fail"); return FALSE; } } /* set tag invalid*/ if (!fu_synaptics_mst_connection_rc_get_command( connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), tagData, error)) { g_prefix_error(error, "failed to read tag from flash: "); return FALSE; } for (guint32 retries_cnt = 0;; retries_cnt++) { /* CRC8 is not 0xff, erase last 4k of bank# */ if (tagData[0] != 0xff) { guint32 erase_offset; /* offset for last 4k of bank# */ erase_offset = (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_BANK_OFFSET - 0x1000) / 0x1000; if (!fu_synaptics_mst_device_set_flash_sector_erase(self, FLASH_SECTOR_ERASE_4K, erase_offset, error)) return FALSE; /* CRC8 is 0xff, set it to 0x00 */ } else { tagData[1] = 0x00; if (!fu_synaptics_mst_connection_rc_set_command( connection, UPDC_WRITE_TO_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), &tagData[1], error)) { g_prefix_error(error, "failed to clear CRC: "); return FALSE; } } if (!fu_synaptics_mst_connection_rc_get_command( connection, UPDC_READ_FROM_EEPROM, 1, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), readBuf, error)) { g_prefix_error(error, "failed to read CRC from flash: "); return FALSE; } if ((readBuf[0] == 0xff && tagData[0] != 0xff) || (readBuf[0] == 0x00 && tagData[0] == 0xff)) { break; } if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag invalid fail"); return FALSE; } } return TRUE; } static gboolean fu_synaptics_mst_device_panamera_prepare_write(FuSynapticsMstDevice *self, GError **error) { guint32 buf[4] = {0}; g_autoptr(FuSynapticsMstConnection) connection = NULL; /* Need to detect flash mode and ESM first ? */ /* disable flash Quad mode and ESM/HDCP2.2*/ connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); /* disable ESM first */ buf[0] = 0x21; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_ESM_DISABLE, (guint8 *)buf, error)) { g_prefix_error(error, "ESM disable failed: "); return FALSE; } /* wait for ESM exit */ g_usleep(200); /* disable QUAD mode */ if (!fu_synaptics_mst_connection_rc_get_command(connection, UPDC_READ_FROM_MEMORY, ((sizeof(buf) / sizeof(buf[0])) * 4), (gint)REG_QUAD_DISABLE, (guint8 *)buf, error)) { g_prefix_error(error, "quad query failed: "); return FALSE; } buf[0] = 0x00; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_QUAD_DISABLE, (guint8 *)buf, error)) { g_prefix_error(error, "quad disable failed: "); return FALSE; } /* disable HDCP2.2 */ if (!fu_synaptics_mst_connection_rc_get_command(connection, UPDC_READ_FROM_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8 *)buf, error)) { g_prefix_error(error, "HDCP query failed: "); return FALSE; } buf[0] = buf[0] & (~BIT(2)); if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_MEMORY, 4, (gint)REG_HDCP22_DISABLE, (guint8 *)buf, error)) { g_prefix_error(error, "HDCP disable failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_update_cayenne_firmware(FuSynapticsMstDevice *self, guint32 payload_len, const guint8 *payload_data, FuProgress *progress, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; guint32 data_to_write = 0; guint32 offset = 0; guint32 write_loops = 0; payload_len = 0x50000; write_loops = (payload_len / BLOCK_UNIT); data_to_write = payload_len; if (payload_len % BLOCK_UNIT) write_loops++; connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); for (guint32 retries_cnt = 0;; retries_cnt++) { guint32 checksum = 0; 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"); g_usleep(FLASH_SETTLE_TIME); for (guint32 i = 0; i < write_loops; i++) { g_autoptr(GError) error_local = NULL; guint8 length = BLOCK_UNIT; if (data_to_write < BLOCK_UNIT) length = data_to_write; if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, &error_local)) { g_warning("Failed to write flash offset 0x%04x: %s, retrying", offset, error_local->message); /* repeat once */ if (!fu_synaptics_mst_connection_rc_set_command( connection, UPDC_WRITE_TO_EEPROM, length, offset, payload_data + offset, error)) { g_prefix_error(error, "can't write flash offset 0x%04x: ", offset); return FALSE; } } offset += length; data_to_write -= length; fu_progress_set_percentage_full(progress, (goffset)i * 100, (goffset)(write_loops - 1) * 100); } /* verify CRC */ checksum = fu_synaptics_mst_device_get_crc(0, 16, payload_len, payload_data); if (!fu_synaptics_mst_connection_rc_special_get_command(connection, UPDC_CAL_EEPROM_CHECK_CRC16, payload_len, 0, NULL, 4, (guint8 *)(&flash_checksum), error)) { g_prefix_error(error, "Failed to get flash checksum: "); return FALSE; } if (checksum == flash_checksum) break; g_debug("attempt %u: checksum %x didn't match %x", retries_cnt, flash_checksum, checksum); if (retries_cnt > MAX_RETRY_COUNTS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum %x mismatched %x", flash_checksum, checksum); return FALSE; } } if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_ACTIVATE_FIRMWARE, 0, 0, NULL, error)) { g_prefix_error(error, "active firmware failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_restart(FuSynapticsMstDevice *self, GError **error) { g_autoptr(FuSynapticsMstConnection) connection = NULL; 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) */ connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), self->layer, self->rad); if (!fu_synaptics_mst_connection_rc_set_command(connection, UPDC_WRITE_TO_MEMORY, 4, offset, (guint8 *)&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_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); g_autoptr(GBytes) fw = NULL; const guint8 *payload_data; gsize payload_len; 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10); fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; payload_data = g_bytes_get_data(fw, &payload_len); /* 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, payload_len, payload_data, 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, payload_data, progress, error)) { g_prefix_error(error, "ESM update failed: "); return FALSE; } if (!fu_synaptics_mst_device_update_panamera_firmware(self, payload_len, payload_data, 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, payload_len, payload_data, 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_progress_sleep(fu_progress_get_child(progress), 2000); fu_progress_step_done(progress); return TRUE; } FuSynapticsMstDevice * fu_synaptics_mst_device_new(FuUdevDevice *device) { FuSynapticsMstDevice *self = g_object_new(FU_TYPE_SYNAPTICS_MST_DEVICE, NULL); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return self; } static gboolean fu_synaptics_mst_device_read_board_id(FuSynapticsMstDevice *self, FuSynapticsMstConnection *connection, guint8 *byte, GError **error) { gint offset; /* 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, byte, 2) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "error reading EEPROM file %s", filename); close(fd); return FALSE; } 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: case FU_SYNAPTICS_MST_FAMILY_SPYDER: offset = (gint)ADDR_MEMORY_CUSTOMER_ID_CAYENNE; 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_connection_rc_get_command(connection, UPDC_READ_FROM_MEMORY, 2, offset, byte, error)) { g_prefix_error(error, "Memory query failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_scan_cascade(FuSynapticsMstDevice *self, guint8 layer, GError **error) { /* in test mode we skip this */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; /* test each relative address in this layer */ for (guint16 rad = 0; rad <= 2; rad++) { guint8 byte[4]; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapticsMstConnection) connection = NULL; g_autoptr(FuSynapticsMstDevice) device_tmp = NULL; g_autoptr(GError) error_local = NULL; /* enable remote control and disable on exit */ device_tmp = fu_synaptics_mst_device_new(FU_UDEV_DEVICE(self)); device_tmp->layer = layer; device_tmp->rad = rad; locker = fu_device_locker_new_full( device_tmp, (FuDeviceLockerFunc)fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc)fu_synaptics_mst_device_disable_rc, &error_local); if (locker == NULL) { g_debug("no cascade device found: %s", error_local->message); continue; } connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), layer + 1, rad); if (!fu_synaptics_mst_connection_read(connection, REG_RC_CAP, byte, 1, &error_local)) { g_debug("no valid cascade device: %s", error_local->message); continue; } /* check recursively for more devices */ if (!fu_device_locker_close(locker, &error_local)) { g_debug("failed to close parent: %s", error_local->message); continue; } self->mode = FU_SYNAPTICS_MST_MODE_REMOTE; self->layer = layer + 1; self->rad = rad; if (!fu_synaptics_mst_device_scan_cascade(self, layer + 1, error)) return FALSE; } 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_rescan(FuDevice *device, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); guint8 buf_vid[4]; g_autoptr(FuSynapticsMstConnection) connection = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autofree gchar *version = NULL; 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; const gchar *name_parent = NULL; const gchar *name_family; guint8 buf_ver[16]; FuDevice *parent; /* read vendor ID */ connection = fu_synaptics_mst_connection_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(self)), 0, 0); if (!fu_synaptics_mst_connection_read(connection, REG_RC_CAP, buf_vid, 1, error)) { g_prefix_error(error, "failed to read device: "); return FALSE; } if (buf_vid[0] & 0x04) { if (!fu_synaptics_mst_connection_read(connection, REG_VENDOR_ID, buf_vid, 3, error)) { g_prefix_error(error, "failed to read vendor ID: "); return FALSE; } /* not a correct device */ if (buf_vid[0] != 0x90 || buf_vid[1] != 0xCC || buf_vid[2] != 0x24) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no device"); return FALSE; } } /* direct */ self->mode = FU_SYNAPTICS_MST_MODE_DIRECT; self->layer = 0; self->rad = 0; /* 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); if (locker == NULL) return FALSE; /* read firmware version */ if (!fu_synaptics_mst_connection_read(connection, REG_FIRMWARE_VERSION, buf_ver, 3, error)) 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_synaptics_mst_connection_read(connection, REG_CHIP_ID, buf_ver, 2, error)) { g_prefix_error(error, "failed to read chip id: "); return FALSE; } self->chip_id = (buf_ver[0] << 8) | (buf_ver[1]); 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); /* 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_read_board_id(self, connection, buf_ver, error)) return FALSE; self->board_id = fu_common_read_uint16(buf_ver, G_BIG_ENDIAN); /* recursively look for cascade devices */ if (!fu_device_locker_close(locker, error)) { g_prefix_error(error, "failed to close parent: "); return FALSE; } if (!fu_synaptics_mst_device_scan_cascade(self, 0, error)) return FALSE; /* 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); 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); /* 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_add_instance_id_full(device, "MST-tesla", FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); break; case FU_SYNAPTICS_MST_FAMILY_LEAF: fu_device_set_firmware_size_max(device, 0x10000); fu_device_add_instance_id_full(device, "MST-leaf", FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); break; case FU_SYNAPTICS_MST_FAMILY_PANAMERA: fu_device_set_firmware_size_max(device, 0x80000); fu_device_add_instance_id_full(device, "MST-panamera", FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); 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(FU_DEVICE(self), guid3); /* this is not a valid customer ID */ if ((self->board_id >> 8) == 0x0) { fu_device_inhibit(device, "invalid-customer-id", "cannot update as CustomerID is unset"); } 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_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_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->rescan = fu_synaptics_mst_device_rescan; klass_device->write_firmware = fu_synaptics_mst_device_write_firmware; klass_device->prepare_firmware = fu_synaptics_mst_device_prepare_firmware; klass_device->probe = fu_synaptics_mst_device_probe; klass_device->set_progress = fu_synaptics_mst_device_set_progress; } fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-device.h000066400000000000000000000010251420024370600240000ustar00rootroot00000000000000/* * 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, FuUdevDevice) FuSynapticsMstDevice * fu_synaptics_mst_device_new(FuUdevDevice *device); void fu_synaptics_mst_device_set_system_type(FuSynapticsMstDevice *self, const gchar *system_type); fwupd-1.7.5/plugins/synaptics-mst/fu-synaptics-mst-firmware.c000066400000000000000000000056111420024370600243550ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-mst-connection.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) 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, guint64 addr_start, guint64 addr_end, 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_common_read_uint16_safe(buf, bufsz, ADDR_CUSTOMER_ID, &self->board_id, G_BIG_ENDIAN, error)) return FALSE; fu_firmware_set_bytes(firmware, fw); return TRUE; } static GBytes * 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)); if (!fu_common_write_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_byte_array_free_to_bytes(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) { } 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.7.5/plugins/synaptics-mst/fu-synaptics-mst-firmware.h000066400000000000000000000007571420024370600243700ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-mst/meson.build000066400000000000000000000034261420024370600213150ustar00rootroot00000000000000if get_option('plugin_synaptics_mst') if not get_option('gudev') error('gudev is required for plugin_synaptics_mst') endif cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsMST"'] install_data(['synaptics-mst.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_mst', fu_hash, sources : [ 'fu-plugin-synaptics-mst.c', 'fu-synaptics-mst-common.c', 'fu-synaptics-mst-connection.c', 'fu-synaptics-mst-device.c', 'fu-synaptics-mst-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : [ cargs, ], link_with : [ fwupd, fwupdplugin, ], dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-synaptics-mst-common.c', 'fu-synaptics-mst-connection.c', 'fu-synaptics-mst-device.c', 'fu-synaptics-mst-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, valgrind, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], install : true, install_dir : installed_test_bindir, ) test('synaptics-mst-self-test', e, env: env) endif endif fwupd-1.7.5/plugins/synaptics-mst/synaptics-mst-evb.quirk000066400000000000000000000011601420024370600236110ustar00rootroot00000000000000# 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.7.5/plugins/synaptics-mst/synaptics-mst.quirk000066400000000000000000000025161420024370600230450ustar00rootroot00000000000000# match all devices with this udev subsystem [DRM_DP_AUX_DEV] Plugin = synaptics_mst # 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-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 fwupd-1.7.5/plugins/synaptics-mst/tests/000077500000000000000000000000001420024370600203105ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-mst/tests/synaptics-mst.bin000066400000000000000000000004241420024370600236200ustar00rootroot000000000000004davefwupd-1.7.5/plugins/synaptics-mst/tests/synaptics-mst.builder.xml000066400000000000000000000001761420024370600253010ustar00rootroot00000000000000 0x1234 0x42 ZGF2ZQ== fwupd-1.7.5/plugins/synaptics-prometheus/000077500000000000000000000000001420024370600205365ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-prometheus/README.md000066400000000000000000000020251420024370600220140ustar00rootroot00000000000000# 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&REV_0001` * `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`. fwupd-1.7.5/plugins/synaptics-prometheus/data/000077500000000000000000000000001420024370600214475ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-prometheus/data/lsusb.txt000066400000000000000000000044351420024370600233460ustar00rootroot00000000000000Bus 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.7.5/plugins/synaptics-prometheus/fu-plugin-synaptics-prometheus.c000066400000000000000000000010741420024370600270160ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" static void fu_plugin_synaptics_prometheus_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPROM_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPROM_FIRMWARE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_synaptics_prometheus_init; } fwupd-1.7.5/plugins/synaptics-prometheus/fu-self-test.c000066400000000000000000000076671420024370600232400ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_contents_bytes(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, 0, &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) { 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.7.5/plugins/synaptics-prometheus/fu-synaprom-common.c000066400000000000000000000052401420024370600244510ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 gpointer data, gsize len) { GByteArray *blob = g_byte_array_new(); fu_byte_array_append_uint8(blob, cmd); if (data != NULL) g_byte_array_append(blob, data, len); return blob; } GByteArray * fu_synaprom_reply_new(gsize cmdlen) { GByteArray *blob = g_byte_array_new(); fu_byte_array_set_size(blob, cmdlen); 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.7.5/plugins/synaptics-prometheus/fu-synaprom-common.h000066400000000000000000000005751420024370600244640ustar00rootroot00000000000000/* * 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 gpointer data, gsize len); GByteArray * fu_synaprom_reply_new(gsize cmdlen); gboolean fu_synaprom_error_from_status(guint16 status, GError **error); fwupd-1.7.5/plugins/synaptics-prometheus/fu-synaprom-config.c000066400000000000000000000225561420024370600244370ustar00rootroot00000000000000/* * 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" struct _FuSynapromConfig { FuDevice parent_instance; guint32 configid1; /* config ID1 */ guint32 configid2; /* config ID2 */ }; /* Iotas can exceed the size of available RAM in the part. * In order to allow the host to read them the IOTA_FIND command supports * transferring iotas with multiple commands */ typedef struct __attribute__((packed)) { guint16 itype; /* type of iotas to find */ guint16 flags; /* flags, see below */ guint8 maxniotas; /* maximum number of iotas to return, 0 = unlimited */ guint8 firstidx; /* first index of iotas to return */ guint8 dummy[2]; guint32 offset; /* byte offset of data to return */ guint32 nbytes; /* maximum number of bytes to return */ } FuSynapromCmdIotaFind; /* this is followed by a chain of iotas, as follows */ typedef struct __attribute__((packed)) { guint16 status; guint32 fullsize; guint16 nbytes; guint16 itype; } FuSynapromReplyIotaFindHdr; /* this iota contains the configuration id and version */ typedef struct __attribute__((packed)) { guint32 config_id1; /* YYMMDD */ guint32 config_id2; /* HHMMSS */ guint16 version; guint16 unused[3]; } FuSynapromIotaConfigVersion; /* le */ typedef struct __attribute__((packed)) { guint32 product; guint32 id1; /* verification ID */ guint32 id2; /* verification ID */ guint16 version; /* config version */ guint8 unused[2]; } FuSynapromFirmwareCfgHeader; #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); FuSynapromCmdIotaFind cmd = {0x0}; FuSynapromIotaConfigVersion cfg; FuSynapromReplyIotaFindHdr hdr; g_autofree gchar *version = NULL; g_autoptr(GByteArray) reply = NULL; g_autoptr(GByteArray) request = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autofree gchar *devid = NULL; /* get IOTA */ cmd.itype = GUINT16_TO_LE((guint16)FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION); cmd.flags = GUINT16_TO_LE((guint16)FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX); request = fu_synaprom_request_new(FU_SYNAPROM_CMD_IOTA_FIND, &cmd, sizeof(cmd)); reply = fu_synaprom_reply_new(sizeof(FuSynapromReplyIotaFindHdr) + 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 < sizeof(hdr) + sizeof(cfg)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG return data invalid size: 0x%04x", reply->len); return FALSE; } memcpy(&hdr, reply->data, sizeof(hdr)); if (GUINT32_FROM_LE(hdr.itype) != 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", GUINT32_FROM_LE(hdr.itype)); return FALSE; } if (!fu_memcpy_safe((guint8 *)&cfg, sizeof(cfg), 0x0, /* dst */ reply->data, reply->len, sizeof(hdr), /* src */ sizeof(cfg), error)) return FALSE; self->configid1 = GUINT32_FROM_LE(cfg.config_id1); self->configid2 = GUINT32_FROM_LE(cfg.config_id2); g_debug("id1=%u, id2=%u, ver=%u", self->configid1, self->configid2, GUINT16_FROM_LE(cfg.version)); /* append the configid to the generated GUID */ devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&CFG1_%u&CFG2_%u", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent)), self->configid1, self->configid2); fu_device_add_instance_id(FU_DEVICE(self), devid); /* no downgrades are allowed */ version = g_strdup_printf("%04u", GUINT16_FROM_LE(cfg.version)); 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); FuSynapromFirmwareCfgHeader hdr; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new(); guint32 product; guint32 id1; guint32 id2; /* 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; if (g_bytes_get_size(blob) != sizeof(hdr)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "CFG metadata is invalid"); return NULL; } memcpy(&hdr, g_bytes_get_data(blob, NULL), sizeof(hdr)); product = GUINT32_FROM_LE(hdr.product); if (product != FU_SYNAPROM_PRODUCT_PROMETHEUS) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("CFG metadata not compatible, " "got 0x%02x expected 0x%02x", product, (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", product, (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS); return NULL; } } id1 = GUINT32_FROM_LE(hdr.id1); id2 = GUINT32_FROM_LE(hdr.id2); if (id1 != self->configid1 || id2 != self->configid2) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("CFG version not compatible, " "got %u:%u expected %u:%u", id1, id2, 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", id1, id2, 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_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.7.5/plugins/synaptics-prometheus/fu-synaprom-config.h000066400000000000000000000006711420024370600244360ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-prometheus/fu-synaprom-device.c000066400000000000000000000373211420024370600244250ustar00rootroot00000000000000/* * 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; }; /* vendor-specific USB control requets 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 75); if (g_getenv("FWUPD_SYNAPROM_VERBOSE") != NULL) { fu_common_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; } if (g_getenv("FWUPD_SYNAPROM_VERBOSE") != NULL) { fu_common_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; } 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_debug("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) { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else if (product == FU_SYNAPROM_PRODUCT_PROMETHEUSPBL || product == FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL) { 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; } /* 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) { FuSynapromFirmwareMfwHeader hdr; guint32 product; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new(); /* 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, "mfw-update-header", error); if (blob == NULL) return NULL; if (g_bytes_get_size(blob) != sizeof(hdr)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "MFW metadata is invalid"); return NULL; } memcpy(&hdr, g_bytes_get_data(blob, NULL), sizeof(hdr)); product = GUINT32_FROM_LE(hdr.product); if (product != FU_SYNAPROM_PRODUCT_PROMETHEUS) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("MFW metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "MFW metadata not compatible, " "got 0x%02x expected 0x%02x", product, (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS); 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99); /* 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_common_read_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_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_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_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.7.5/plugins/synaptics-prometheus/fu-synaprom-device.h000066400000000000000000000024621420024370600244300ustar00rootroot00000000000000/* * 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_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); fwupd-1.7.5/plugins/synaptics-prometheus/fu-synaprom-firmware.c000066400000000000000000000136371420024370600250060ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-synaprom-firmware.h" struct _FuSynapromFirmware { FuFirmware parent_instance; guint32 product_id; }; G_DEFINE_TYPE(FuSynapromFirmware, fu_synaprom_firmware, FU_TYPE_FIRMWARE) #define FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER 0x0001 #define FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD 0x0002 #define FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER 0x0003 #define FU_SYNAPROM_FIRMWARE_TAG_CFG_PAYLOAD 0x0004 typedef struct __attribute__((packed)) { guint16 tag; guint32 bufsz; } FuSynapromFirmwareHdr; /* use only first 12 bit of 16 bits as tag value */ #define FU_SYNAPROM_FIRMWARE_TAG_MAX 0xfff0 #define FU_SYNAPROM_FIRMWARE_SIGSIZE 0x0100 #define FU_SYNAPROM_FIRMWARE_COUNT_MAX 64 static const gchar * fu_synaprom_firmware_tag_to_string(guint16 tag) { if (tag == FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER) return "mfw-update-header"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD) return "mfw-update-payload"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_CFG_HEADER) return "cfg-update-header"; if (tag == FU_SYNAPROM_FIRMWARE_TAG_CFG_PAYLOAD) return "cfg-update-payload"; return NULL; } 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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; gsize offset = 0; guint img_cnt = 0; g_return_val_if_fail(fw != NULL, FALSE); buf = g_bytes_get_data(fw, &bufsz); /* 256 byte signature as footer */ if (bufsz < FU_SYNAPROM_FIRMWARE_SIGSIZE + sizeof(FuSynapromFirmwareHdr)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob is too small to be firmware"); return FALSE; } bufsz -= FU_SYNAPROM_FIRMWARE_SIGSIZE; /* parse each chunk */ while (offset != bufsz) { FuSynapromFirmwareHdr header; guint32 hdrsz; guint32 tag; g_autoptr(GBytes) bytes = NULL; g_autoptr(FuFirmware) img = NULL; /* verify item header */ memcpy(&header, buf, sizeof(header)); tag = GUINT16_FROM_LE(header.tag); 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; } hdrsz = GUINT32_FROM_LE(header.bufsz); 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 += sizeof(header) + hdrsz; if (offset > bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data is corrupted 0x%04x > 0x%04x", (guint)offset, (guint)bufsz); return FALSE; } /* move pointer to data */ buf += sizeof(header); bytes = g_bytes_new(buf, hdrsz); 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)); fu_firmware_add_image(firmware, img); /* sanity check */ if (img_cnt++ > FU_SYNAPROM_FIRMWARE_COUNT_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "maximum number of images exceeded, " "maximum is 0x%02x", (guint)FU_SYNAPROM_FIRMWARE_COUNT_MAX); return FALSE; } /* next item */ buf += hdrsz; } return TRUE; } static GBytes * fu_synaprom_firmware_write(FuFirmware *firmware, GError **error) { FuSynapromFirmware *self = FU_SYNAPROM_FIRMWARE(firmware); GByteArray *blob = g_byte_array_new(); g_autoptr(GBytes) payload = NULL; FuSynapromFirmwareMfwHeader hdr = { .product = GUINT32_TO_LE(self->product_id), .id = GUINT32_TO_LE(0xff), .buildtime = GUINT32_TO_LE(0xff), .buildnum = GUINT32_TO_LE(0xff), .vmajor = 10, .vminor = 1, }; /* add header */ fu_byte_array_append_uint16(blob, FU_SYNAPROM_FIRMWARE_TAG_MFW_HEADER, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(blob, sizeof(hdr), G_LITTLE_ENDIAN); g_byte_array_append(blob, (const guint8 *)&hdr, sizeof(hdr)); /* add payload */ payload = fu_firmware_get_bytes_with_patches(firmware, error); if (payload == NULL) return NULL; fu_byte_array_append_uint16(blob, FU_SYNAPROM_FIRMWARE_TAG_MFW_PAYLOAD, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(blob, g_bytes_get_size(payload), G_LITTLE_ENDIAN); fu_byte_array_append_bytes(blob, payload); /* add signature */ for (guint i = 0; i < FU_SYNAPROM_FIRMWARE_SIGSIZE; i++) fu_byte_array_append_uint8(blob, 0xff); return g_byte_array_free_to_bytes(blob); } 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); } 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.7.5/plugins/synaptics-prometheus/fu-synaprom-firmware.h000066400000000000000000000013511420024370600250010ustar00rootroot00000000000000/* * 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()) G_DECLARE_FINAL_TYPE(FuSynapromFirmware, fu_synaprom_firmware, FU, SYNAPROM_FIRMWARE, FuFirmware) /* le */ typedef struct __attribute__((packed)) { guint32 product; guint32 id; /* MFW unique id used for compat verification */ guint32 buildtime; /* unix-style build time */ guint32 buildnum; /* build number */ guint8 vmajor; /* major version */ guint8 vminor; /* minor version */ guint8 unused[6]; } FuSynapromFirmwareMfwHeader; FuFirmware * fu_synaprom_firmware_new(void); fwupd-1.7.5/plugins/synaptics-prometheus/meson.build000066400000000000000000000030511420024370600226770ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsPrometheus"'] install_data(['synaptics-prometheus.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_prometheus', fu_hash, sources : [ 'fu-plugin-synaptics-prometheus.c', 'fu-synaprom-common.c', 'fu-synaprom-config.c', 'fu-synaprom-device.c', 'fu-synaprom-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-synaprom-common.c', 'fu-synaprom-config.c', 'fu-synaprom-device.c', 'fu-synaprom-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, install : true, install_dir : installed_test_bindir, ) test('synaptics-prometheus-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/synaptics-prometheus/synaptics-prometheus.quirk000066400000000000000000000017161420024370600260260ustar00rootroot00000000000000[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 fwupd-1.7.5/plugins/synaptics-prometheus/tests/000077500000000000000000000000001420024370600217005ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-prometheus/tests/synaptics-prometheus.bin000066400000000000000000000004571420024370600266060ustar00rootroot00000000000000B  hello worldfwupd-1.7.5/plugins/synaptics-prometheus/tests/synaptics-prometheus.builder.xml000066400000000000000000000002331420024370600302530ustar00rootroot00000000000000 1.2 0x42 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/synaptics-rmi/000077500000000000000000000000001420024370600171325ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-rmi/README.md000066400000000000000000000020161420024370600204100ustar00rootroot00000000000000# 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 docucumented) file format. This plugin supports the following protocol ID: * com.synaptics.rmi ## External Interface Access This plugin requires ioctl access to `HIDIOCSFEATURE` and `HIDIOCGFEATURE`. fwupd-1.7.5/plugins/synaptics-rmi/fu-plugin-synaptics-rmi.c000066400000000000000000000014331420024370600240050ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-hid-device.h" #include "fu-synaptics-rmi-ps2-device.h" static void fu_plugin_synaptics_rmi_init(FuPlugin *plugin) { 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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_synaptics_rmi_init; } fwupd-1.7.5/plugins/synaptics-rmi/fu-self-test.c000066400000000000000000000061521420024370600216200ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-common.c000066400000000000000000000115451420024370600240040ustar00rootroot00000000000000/* * 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 #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_common_read_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 (guint8 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-common.h000066400000000000000000000017651420024370600240140ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-device.c000066400000000000000000000646731420024370600237650ustar00rootroot00000000000000/* * 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-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_common_string_append_kv(str, idt, "BootloaderId", tmp); } fu_common_string_append_kx(str, idt, "BlockSize", flash->block_size); fu_common_string_append_kx(str, idt, "BlockCountFw", flash->block_count_fw); fu_common_string_append_kx(str, idt, "BlockCountCfg", flash->block_count_cfg); fu_common_string_append_kx(str, idt, "FlashConfigLength", flash->config_length); fu_common_string_append_kx(str, idt, "PayloadLength", flash->payload_length); fu_common_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_common_string_append_kx(str, idt, "CurrentPage", priv->current_page); fu_common_string_append_kx(str, idt, "InIepMode", priv->in_iep_mode); fu_common_string_append_kx(str, idt, "MaxPage", priv->max_page); fu_common_string_append_kx(str, idt, "SigSize", priv->sig_size); if (priv->f34 != NULL) { fu_common_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_get_iepmode(FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); return priv->in_iep_mode; } 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; g_usleep(1000 * 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 */ if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { 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; } guint8 fu_synaptics_rmi_device_get_max_page(FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); return priv->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_common_read_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); 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++) { g_usleep(1000 * 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 ellapse 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_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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-device.h000066400000000000000000000111351420024370600237530ustar00rootroot00000000000000/* * 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; } 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 20 /* ms */ #define RMI_DEVICE_PAGE_SELECT_REGISTER 0xff #define RMI_DEVICE_BUS_SELECT_REGISTER 0xfe 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_get_iepmode(FuSynapticsRmiDevice *self); 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); guint8 fu_synaptics_rmi_device_get_max_page(FuSynapticsRmiDevice *self); 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.c000066400000000000000000000560551420024370600243350ustar00rootroot00000000000000/* * 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 "fu-synaptics-rmi-common.h" #include "fu-synaptics-rmi-firmware.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_CHECKSUM_OFFSET 0x00 #define RMI_IMG_IO_OFFSET 0x06 #define RMI_IMG_BOOTLOADER_VERSION_OFFSET 0x07 #define RMI_IMG_IMAGE_SIZE_OFFSET 0x08 #define RMI_IMG_CONFIG_SIZE_OFFSET 0x0c #define RMI_IMG_PACKAGE_ID_OFFSET 0x1a #define RMI_IMG_FW_BUILD_ID_OFFSET 0x50 #define RMI_IMG_SIGNATURE_SIZE_OFFSET 0x54 #define RMI_IMG_PRODUCT_ID_OFFSET 0x10 #define RMI_IMG_PRODUCT_INFO_OFFSET 0x1e #define RMI_IMG_FW_OFFSET 0x100 #define RMI_IMG_V10_CNTR_ADDR_OFFSET 0x0c #define RMI_IMG_MAX_CONTAINERS 1024 typedef struct __attribute__((packed)) { guint32 content_checksum; guint16 container_id; guint8 minor_version; guint8 major_version; guint8 reserved_08; guint8 reserved_09; guint8 reserved_0a; guint8 reserved_0b; guint32 container_option_flags; guint32 content_options_length; guint32 content_options_address; guint32 content_length; guint32 content_address; } RmiFirmwareContainerDescriptor; typedef enum { RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL = 0, RMI_FIRMWARE_CONTAINER_ID_UI, RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG, RMI_FIRMWARE_CONTAINER_ID_BL, RMI_FIRMWARE_CONTAINER_ID_BL_IMAGE, RMI_FIRMWARE_CONTAINER_ID_BL_CONFIG, RMI_FIRMWARE_CONTAINER_ID_BL_LOCKDOWN_INFO, RMI_FIRMWARE_CONTAINER_ID_PERMANENT_CONFIG, RMI_FIRMWARE_CONTAINER_ID_GUEST_CODE, RMI_FIRMWARE_CONTAINER_ID_BL_PROTOCOL_DESCRIPTOR, RMI_FIRMWARE_CONTAINER_ID_UI_PROTOCOL_DESCRIPTOR, RMI_FIRMWARE_CONTAINER_ID_RMI_SELF_DISCOVERY, RMI_FIRMWARE_CONTAINER_ID_RMI_PAGE_CONTENT, RMI_FIRMWARE_CONTAINER_ID_GENERAL_INFORMATION, RMI_FIRMWARE_CONTAINER_ID_DEVICE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG, RMI_FIRMWARE_CONTAINER_ID_GUEST_SERIALIZATION, RMI_FIRMWARE_CONTAINER_ID_GLOBAL_PARAMETERS, RMI_FIRMWARE_CONTAINER_ID_CORE_CODE, RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_DISPLAY_CONFIG, RMI_FIRMWARE_CONTAINER_ID_EXTERNAL_TOUCH_AFE_CONFIG, RMI_FIRMWARE_CONTAINER_ID_UTILITY, RMI_FIRMWARE_CONTAINER_ID_UTILITY_PARAMETER, } RmiFirmwareContainerId; static const gchar * rmi_firmware_container_id_to_string(RmiFirmwareContainerId container_id) { if (container_id == RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL) return "top-level"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI) return "ui"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG) return "ui-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL) return "bl"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_IMAGE) return "bl-image"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_CONFIG) return "bl-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_LOCKDOWN_INFO) return "bl-lockdown-info"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_PERMANENT_CONFIG) return "permanent-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GUEST_CODE) return "guest-code"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_BL_PROTOCOL_DESCRIPTOR) return "bl-protocol-descriptor"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UI_PROTOCOL_DESCRIPTOR) return "ui-protocol-descriptor"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_RMI_SELF_DISCOVERY) return "rmi-self-discovery"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_RMI_PAGE_CONTENT) return "rmi-page-content"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GENERAL_INFORMATION) return "general-information"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_DEVICE_CONFIG) return "device-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG) return "flash-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GUEST_SERIALIZATION) return "guest-serialization"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_GLOBAL_PARAMETERS) return "global-parameters"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_CORE_CODE) return "core-code"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG) return "core-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_DISPLAY_CONFIG) return "display-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_EXTERNAL_TOUCH_AFE_CONFIG) return "external-touch-afe-config"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UTILITY) return "utility"; if (container_id == RMI_FIRMWARE_CONTAINER_ID_UTILITY_PARAMETER) return "utility-parameter"; return NULL; } static gboolean fu_synaptics_rmi_firmware_add_image(FuFirmware *firmware, const gchar *id, GBytes *fw, gsize offset, gsize sz, GError **error) { g_autoptr(GBytes) bytes = NULL; g_autoptr(FuFirmware) img = NULL; bytes = fu_common_bytes_new_offset(fw, offset, sz, error); if (bytes == NULL) return FALSE; img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_id(img, 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); RmiFirmwareContainerDescriptor desc = {0x0}; guint16 container_id; guint32 cntrs_len; guint32 offset; guint32 cntr_addr; guint8 product_id[RMI_PRODUCT_ID_LENGTH] = {0x0}; gsize sz = 0; const guint8 *data = g_bytes_get_data(fw, &sz); if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_V10_CNTR_ADDR_OFFSET, &cntr_addr, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("v10 RmiFirmwareContainerDescriptor at 0x%x", cntr_addr); if (!fu_memcpy_safe((guint8 *)&desc, sizeof(desc), 0x0, /* dst */ data, sz, cntr_addr, /* src */ sizeof(desc), error)) { g_prefix_error(error, "RmiFirmwareContainerDescriptor invalid: "); return FALSE; } container_id = GUINT16_FROM_LE(desc.container_id); if (container_id != RMI_FIRMWARE_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)RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL); return FALSE; } offset = GUINT32_FROM_LE(desc.content_address); if (offset > sz - sizeof(guint32) - sizeof(desc)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "image offset invalid, got 0x%x, size 0x%x", (guint)offset, (guint)sz); return FALSE; } cntrs_len = GUINT32_FROM_LE(desc.content_length) / 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; if (!fu_common_read_uint32_safe(data, sz, offset, &addr, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("parsing RmiFirmwareContainerDescriptor at 0x%x", addr); if (!fu_memcpy_safe((guint8 *)&desc, sizeof(desc), 0x0, /* dst */ data, sz, addr, /* src */ sizeof(desc), error)) return FALSE; container_id = GUINT16_FROM_LE(desc.container_id); content_addr = GUINT32_FROM_LE(desc.content_address); length = GUINT32_FROM_LE(desc.content_length); g_debug("RmiFirmwareContainerDescriptor 0x%02x @ 0x%x (len 0x%x)", container_id, content_addr, length); if (length == 0 || length > sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "length invalid, length 0x%x, size 0x%x", (guint)length, (guint)sz); return FALSE; } if (content_addr > sz - 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)sz); return FALSE; } switch (container_id) { case RMI_FIRMWARE_CONTAINER_ID_BL: if (!fu_common_read_uint8_safe(data, sz, content_addr, &self->bootloader_version, error)) return FALSE; break; case RMI_FIRMWARE_CONTAINER_ID_UI: case RMI_FIRMWARE_CONTAINER_ID_CORE_CODE: if (!fu_synaptics_rmi_firmware_add_image(firmware, "ui", fw, content_addr, length, error)) return FALSE; break; case RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG: if (!fu_synaptics_rmi_firmware_add_image(firmware, "flash-config", fw, content_addr, length, error)) return FALSE; break; case RMI_FIRMWARE_CONTAINER_ID_UI_CONFIG: case RMI_FIRMWARE_CONTAINER_ID_CORE_CONFIG: if (!fu_synaptics_rmi_firmware_add_image(firmware, "config", fw, content_addr, length, error)) return FALSE; break; case RMI_FIRMWARE_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_common_read_uint32_safe(data, sz, content_addr, &self->package_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(data, sz, content_addr + 0x04, &self->build_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memcpy_safe(product_id, sizeof(product_id), 0x0, /* dst */ data, sz, content_addr + 0x18, /* src */ sizeof(product_id), error)) return FALSE; break; default: g_debug("unsupported container %s [0x%02x]", rmi_firmware_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 = 0; guint32 sig_offset = 0; gsize sz = 0; const guint8 *data = g_bytes_get_data(fw, &sz); /* main firmware */ if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_IMAGE_SIZE_OFFSET, &img_sz, G_LITTLE_ENDIAN, error)) return FALSE; if (img_sz > 0) { /* payload, then signature appended */ if (self->sig_size > 0) { 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 */ if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_CONFIG_SIZE_OFFSET, &cfg_sz, G_LITTLE_ENDIAN, error)) return FALSE; 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, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); gsize sz = 0; guint32 checksum_calculated; guint32 firmware_size = 0; const guint8 *data = g_bytes_get_data(fw, &sz); /* check minimum size */ if (sz < RMI_IMG_FW_OFFSET) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not enough data to parse header"); return FALSE; } if (sz % 2 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 16 bits"); return FALSE; } /* verify checksum */ if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_CHECKSUM_OFFSET, &self->checksum, G_LITTLE_ENDIAN, error)) return FALSE; checksum_calculated = fu_synaptics_rmi_generate_checksum(data + 4, sz - 4); if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { 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 = data[RMI_IMG_IO_OFFSET]; self->bootloader_version = data[RMI_IMG_BOOTLOADER_VERSION_OFFSET]; if (self->io == 1) { if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_FW_BUILD_ID_OFFSET, &self->build_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_PACKAGE_ID_OFFSET, &self->package_id, G_LITTLE_ENDIAN, error)) return FALSE; } self->product_id = g_strndup((const gchar *)data + RMI_IMG_PRODUCT_ID_OFFSET, RMI_PRODUCT_ID_LENGTH); if (!fu_common_read_uint16_safe(data, sz, RMI_IMG_PRODUCT_INFO_OFFSET, &self->product_info, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_IMAGE_SIZE_OFFSET, &firmware_size, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_size(firmware, firmware_size); /* parse partitions, but ignore lockdown */ switch (self->bootloader_version) { case 2: case 3: case 4: case 5: case 6: if ((self->io & 0x10) >> 1) { if (!fu_common_read_uint32_safe(data, sz, RMI_IMG_SIGNATURE_SIZE_OFFSET, &self->sig_size, G_LITTLE_ENDIAN, error)) return FALSE; } if (!fu_synaptics_rmi_firmware_parse_v0x(firmware, fw, error)) return FALSE; self->kind = RMI_FIRMWARE_KIND_0X; break; case 16: 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 GBytes * 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(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_byte_array_set_size(buf, RMI_IMG_FW_OFFSET + 0x4 + bufsz); buf->data[RMI_IMG_IO_OFFSET] = 0x0; /* no build_id or package_id */ buf->data[RMI_IMG_BOOTLOADER_VERSION_OFFSET] = 0x2; /* not hierarchical */ if (self->product_id != NULL) { gsize product_id_sz = strlen(self->product_id); if (!fu_memcpy_safe(buf->data, buf->len, RMI_IMG_PRODUCT_ID_OFFSET, /* dst */ (const guint8 *)self->product_id, product_id_sz, 0x0, /* src */ product_id_sz, error)) return NULL; } fu_common_write_uint16(buf->data + RMI_IMG_PRODUCT_INFO_OFFSET, 0x1234, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_IMAGE_SIZE_OFFSET, bufsz, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_CONFIG_SIZE_OFFSET, bufsz, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_FW_OFFSET + 0x0, 0xdead, G_LITTLE_ENDIAN); /* img */ fu_common_write_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_common_write_uint32(buf->data + RMI_IMG_CHECKSUM_OFFSET, csum, G_LITTLE_ENDIAN); /* success */ return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } static GBytes * 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(GBytes) buf_blob = NULL; /* header | desc_hdr | offset_table | desc | flash_config | * \0x0 \0x20 \0x24 \0x44 |0x48 */ RmiFirmwareContainerDescriptor desc_hdr = { .container_id = GUINT16_TO_LE(RMI_FIRMWARE_CONTAINER_ID_TOP_LEVEL), .content_length = GUINT32_TO_LE(0x1 * 4), /* size of offset table in bytes */ .content_address = GUINT32_TO_LE(RMI_IMG_FW_OFFSET + 0x20), /* offset to table */ }; guint32 offset_table[] = { GUINT32_TO_LE(RMI_IMG_FW_OFFSET + 0x24)}; /* offset to first descriptor */ RmiFirmwareContainerDescriptor desc = { .container_id = GUINT16_TO_LE(RMI_FIRMWARE_CONTAINER_ID_FLASH_CONFIG), .content_length = GUINT32_TO_LE(0x0), .content_address = GUINT32_TO_LE(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); desc.content_length = GUINT32_TO_LE(bufsz); /* create empty block */ fu_byte_array_set_size(buf, RMI_IMG_FW_OFFSET + 0x48); buf->data[RMI_IMG_IO_OFFSET] = 0x1; buf->data[RMI_IMG_BOOTLOADER_VERSION_OFFSET] = 16; /* hierarchical */ if (self->product_id != NULL) { gsize product_id_sz = strlen(self->product_id); if (!fu_memcpy_safe(buf->data, buf->len, RMI_IMG_PRODUCT_ID_OFFSET, /* dst */ (const guint8 *)self->product_id, product_id_sz, 0x0, /* src */ product_id_sz, error)) return NULL; } fu_common_write_uint32(buf->data + RMI_IMG_FW_BUILD_ID_OFFSET, 0x1234, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_PACKAGE_ID_OFFSET, 0x4321, G_LITTLE_ENDIAN); fu_common_write_uint16(buf->data + RMI_IMG_PRODUCT_INFO_OFFSET, 0x3456, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_IMAGE_SIZE_OFFSET, bufsz, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_CONFIG_SIZE_OFFSET, bufsz, G_LITTLE_ENDIAN); fu_common_write_uint32(buf->data + RMI_IMG_V10_CNTR_ADDR_OFFSET, RMI_IMG_FW_OFFSET, G_LITTLE_ENDIAN); /* hierarchical section */ memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x00, &desc_hdr, sizeof(desc_hdr)); memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x20, offset_table, sizeof(offset_table)); memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x24, &desc, sizeof(desc)); fu_common_write_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_common_write_uint32(buf->data + RMI_IMG_CHECKSUM_OFFSET, csum, G_LITTLE_ENDIAN); /* success */ return g_byte_array_free_to_bytes(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 GBytes * 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); } 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.h000066400000000000000000000007571420024370600243400ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-hid-device.c000066400000000000000000000427311420024370600245160ustar00rootroot00000000000000/* * 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 #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; FuIOChannel *io_channel; }; 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_INTERUPT_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 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); 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); if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full(G_LOG_DOMAIN, "ReportWrite", req->data, req->len, 80, FU_DUMP_FLAGS_NONE); } if (!fu_io_channel_write_byte_array(self->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(self->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; } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_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); } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_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); 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); if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full(G_LOG_DOMAIN, "DeviceWrite", buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); } return fu_io_channel_write_byte_array(self->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); 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(self->io_channel, HID_RMI4_ATTN_INTERUPT_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; } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full(G_LOG_DOMAIN, "ReportRead", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); } if (res->len < HID_RMI4_ATTN_INTERUPT_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_INTERUPT_SOURCES]) return TRUE; /* wrong mask */ g_debug("source mask did not match: 0x%x", res->data[HID_RMI4_ATTN_INTERUPT_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}; if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "SetMode", data, sizeof(data)); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(sizeof(data)), (guint8 *)data, NULL, 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 */ self->io_channel = fu_io_channel_unix_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(device))); 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); } fu_udev_device_set_fd(FU_UDEV_DEVICE(device), -1); g_clear_object(&self->io_channel); /* 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_i2c = 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; } /* find the physical ID to use for the rebind */ hid_id = g_udev_device_get_property(parent_hid, "HID_PHYS"); 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_hid)); return FALSE; } g_debug("HID_PHYS: %s", hid_id); /* build paths */ parent_i2c = g_udev_device_get_parent_with_subsystem(udev_device, "i2c", NULL); if (parent_i2c == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no I2C parent device for %s", g_udev_device_get_sysfs_path(udev_device)); return FALSE; } driver = g_udev_device_get_driver(parent_i2c); subsystem = g_udev_device_get_subsystem(parent_i2c); 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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_synaptics_rmi_hid_device_parent_class)->probe(device, error)) return FALSE; 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_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_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_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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-hid-device.h000066400000000000000000000006731420024370600245220ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-ps2-device.c000066400000000000000000000710771420024370600244630ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (c) 2020 Synaptics Incorporated. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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; FuIOChannel *io_channel; }; 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) { for (guint i = 0; i < 60; i++) { g_autoptr(GError) error_local = NULL; if (!fu_io_channel_read_raw(self->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); g_usleep(30); 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) { g_return_val_if_fail(timeout > 0, FALSE); return fu_io_channel_read_raw(self->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) { 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(self->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"); g_usleep(G_USEC_PER_SEC); break; } if (res == edpsError) { do_write = TRUE; g_debug("error"); g_usleep(1000 * 10); break; } g_debug("other response: 0x%x", res); g_usleep(1000 * 10); } 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 */ g_usleep(1000 * 20); 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 */ g_usleep(1000 * 20); 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); } g_usleep(1000 * 20); 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); if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_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; 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; } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { g_autofree gchar *dump = g_strdup_printf("R %x", addr); fu_common_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); 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; } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { g_autofree gchar *str = g_strdup_printf("W %x", addr); fu_common_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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->probe(device, error)) return FALSE; /* 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; /* create channel */ self->io_channel = fu_io_channel_unix_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(device))); /* 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_close(FuDevice *device, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(device); fu_udev_device_set_fd(FU_UDEV_DEVICE(device), -1); g_clear_object(&self->io_channel); /* FuUdevDevice->close */ return FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->close(device, error); } 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_progress_sleep(progress, 2000); /* 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; } /* delay after reset */ fu_progress_sleep(progress, 5000); /* 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) { g_usleep(1000 * 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_device->close = fu_synaptics_rmi_ps2_device_close; 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-ps2-device.h000066400000000000000000000006731420024370600244620ustar00rootroot00000000000000/* * 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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.c000066400000000000000000000424721420024370600243060ustar00rootroot00000000000000/* * 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-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; } g_usleep(1000 * 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; } g_usleep(1000 * 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; if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) fu_common_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; } if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_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(GPtrArray) chunks_bin = NULL; g_autoptr(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10); /* 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 = g_bytes_new_from_bytes(bytes_bin, 0, firmware_length); 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, /* start addr */ 0x00, /* page_sz */ flash->block_size); chunks_cfg = fu_chunk_array_new_from_bytes(bytes_cfg, 0x00, /* start addr */ 0x00, /* page_sz */ flash->block_size); for (guint i = 0; i < chunks_bin->len; i++) { FuChunk *chk = g_ptr_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)chunks_bin->len); } fu_progress_step_done(progress); /* payload signature */ if (signature_bin != NULL && fu_synaptics_rmi_device_get_sig_size(self) != 0) { g_autoptr(GPtrArray) chunks_sig = NULL; chunks_sig = fu_chunk_array_new_from_bytes(signature_bin, 0x00, /* start addr */ 0x00, /* page_sz */ 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; } for (guint i = 0; i < chunks_sig->len; i++) { FuChunk *chk = g_ptr_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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks_sig->len); } g_usleep(1000 * 1000); } 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 < chunks_cfg->len; i++) { FuChunk *chk = g_ptr_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)chunks_cfg->len); } 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 properties */ 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_common_read_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_common_read_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); } else { fu_synaptics_rmi_device_set_sig_size(self, 0); } /* get flash properties */ f34_data2 = fu_synaptics_rmi_device_read(self, f34->query_base + 0x2, 0x7, error); if (f34_data2 == NULL) return FALSE; if (!fu_common_read_uint16_safe(f34_data2->data, f34_data2->len, RMI_F34_BLOCK_SIZE_OFFSET, &flash->block_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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_common_read_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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.h000066400000000000000000000012121420024370600242760ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.c000066400000000000000000000040721420024370600243010ustar00rootroot00000000000000/* * 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-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_common_read_uint8_safe(f34_data0->data, f34_data0->len, 0x0, &flash->bootloader_id[0], error)) return FALSE; if (!fu_common_read_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_common_read_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_common_read_uint16_safe(f34_data3->data, f34_data3->len, 0x0, &flash->block_count_fw, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.h000066400000000000000000000004211420024370600243000ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v6_device_setup(FuSynapticsRmiDevice *self, GError **error); fwupd-1.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.c000066400000000000000000000512051420024370600243020ustar00rootroot00000000000000/* * 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-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, } RmiFlashCommand; typedef enum { RMI_PARTITION_ID_NONE = 0x00, RMI_PARTITION_ID_BOOTLOADER = 0x01, RMI_PARTITION_ID_DEVICE_CONFIG, RMI_PARTITION_ID_FLASH_CONFIG, RMI_PARTITION_ID_MANUFACTURING_BLOCK, RMI_PARTITION_ID_GUEST_SERIALIZATION, RMI_PARTITION_ID_GLOBAL_PARAMETERS, RMI_PARTITION_ID_CORE_CODE, RMI_PARTITION_ID_CORE_CONFIG, RMI_PARTITION_ID_GUEST_CODE, RMI_PARTITION_ID_DISPLAY_CONFIG, RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG, RMI_PARTITION_ID_UTILITY_PARAMETER, } RmiPartitionId; static const gchar * rmi_firmware_partition_id_to_string(RmiPartitionId partition_id) { if (partition_id == RMI_PARTITION_ID_NONE) return "none"; if (partition_id == RMI_PARTITION_ID_BOOTLOADER) return "bootloader"; if (partition_id == RMI_PARTITION_ID_DEVICE_CONFIG) return "device-config"; if (partition_id == RMI_PARTITION_ID_FLASH_CONFIG) return "flash-config"; if (partition_id == RMI_PARTITION_ID_MANUFACTURING_BLOCK) return "manufacturing-block"; if (partition_id == RMI_PARTITION_ID_GUEST_SERIALIZATION) return "guest-serialization"; if (partition_id == RMI_PARTITION_ID_GLOBAL_PARAMETERS) return "global-parameters"; if (partition_id == RMI_PARTITION_ID_CORE_CODE) return "core-code"; if (partition_id == RMI_PARTITION_ID_CORE_CONFIG) return "core-config"; if (partition_id == RMI_PARTITION_ID_GUEST_CODE) return "guest-code"; if (partition_id == RMI_PARTITION_ID_DISPLAY_CONFIG) return "display-config"; if (partition_id == RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG) return "external-touch-afe-config"; if (partition_id == RMI_PARTITION_ID_UTILITY_PARAMETER) return "utility-parameter"; return NULL; } 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, 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; g_usleep(1000 * RMI_F34_ENABLE_WAIT_MS); 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, 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) g_usleep(1000 * 1000); 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; } g_usleep(1000 * 100); 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, 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); g_usleep(1000 * 100); 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 */ g_usleep(1000 * 100); 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, const guint8 *data, guint32 datasz, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GPtrArray) chunks = NULL; /* write FW blocks */ chunks = fu_chunk_array_new(data, datasz, 0x00, /* start addr */ 0x00, /* page_sz */ flash->block_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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(FuSynapticsRmiDevice *self, RmiPartitionId 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(GPtrArray) chunks = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* write partition id */ g_debug("writing partition %s…", rmi_firmware_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, /* start addr */ 0x00, /* page_sz */ (gsize)flash->payload_length * (gsize)flash->block_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); 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, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len); } 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; /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20); /* 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; } /* disable powersaving */ if (!fu_synaptics_rmi_device_disable_sleep(self, 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 (bytes_flashcfg != NULL) { if (!fu_synaptics_rmi_v7_device_write_partition(self, 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, 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, RMI_PARTITION_ID_CORE_CONFIG, bytes_cfg, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } typedef struct __attribute__((packed)) { guint16 partition_id; guint16 partition_len; guint16 partition_addr; guint16 partition_prop; } RmiPartitionTbl; G_STATIC_ASSERT(sizeof(RmiPartitionTbl) == 8); 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; /* 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, 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 */ if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) { fu_common_dump_full(G_LOG_DOMAIN, "FlashConfig", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); } /* parse the config length */ for (guint i = 0x2; i < res->len; i += sizeof(RmiPartitionTbl)) { RmiPartitionTbl tbl; if (!fu_memcpy_safe((guint8 *)&tbl, sizeof(tbl), 0x0, /* dst */ res->data, res->len, i, /* src */ sizeof(tbl), error)) return FALSE; g_debug("found partition %s (0x%02x)", rmi_firmware_partition_id_to_string(tbl.partition_id), tbl.partition_id); if (tbl.partition_id == RMI_PARTITION_ID_CORE_CONFIG) { flash->block_count_cfg = tbl.partition_len; continue; } if (tbl.partition_id == RMI_PARTITION_ID_CORE_CODE) { flash->block_count_fw = tbl.partition_len; continue; } if (tbl.partition_id == 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_common_read_uint8_safe(f34_dataX->data, f34_dataX->len, 0x0, &flash->bootloader_id[0], error)) return FALSE; if (!fu_common_read_uint8_safe(f34_dataX->data, f34_dataX->len, 0x1, &flash->bootloader_id[1], error)) return FALSE; if (!fu_common_read_uint16_safe(f34_dataX->data, f34_dataX->len, 0x07, &flash->block_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(f34_dataX->data, f34_dataX->len, 0x0d, &flash->config_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(f34_dataX->data, f34_dataX->len, 0x0f, &flash->payload_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_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.7.5/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.h000066400000000000000000000012121420024370600243000ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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.7.5/plugins/synaptics-rmi/meson.build000066400000000000000000000033641420024370600213020ustar00rootroot00000000000000if get_option('plugin_synaptics_rmi') if not get_option('gudev') error('gudev is required for plugin_synaptics_rmi') endif cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsRmi"'] install_data(['synaptics-rmi.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_synaptics_rmi', fu_hash, sources : [ 'fu-plugin-synaptics-rmi.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, gnutls, ], link_with : [ fwupd, fwupdplugin, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-synaptics-rmi-common.c', 'fu-synaptics-rmi-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, gnutls, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('synaptics-rmi-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/synaptics-rmi/synaptics-rmi.quirk000066400000000000000000000005661420024370600230200ustar00rootroot00000000000000# 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.7.5/plugins/synaptics-rmi/tests/000077500000000000000000000000001420024370600202745ustar00rootroot00000000000000fwupd-1.7.5/plugins/synaptics-rmi/tests/synaptics-rmi-0x.bin000066400000000000000000000004101420024370600241100ustar00rootroot00000000000000V"Example4fwupd-1.7.5/plugins/synaptics-rmi/tests/synaptics-rmi-0x.builder.xml000066400000000000000000000003361420024370600255740ustar00rootroot00000000000000 0x01 Example ui ZGF2ZQ== fwupd-1.7.5/plugins/synaptics-rmi/tests/synaptics-rmi-10.bin000066400000000000000000000005101420024370600240020ustar00rootroot00000000000000dExample!CV44 $Dfwupd-1.7.5/plugins/synaptics-rmi/tests/synaptics-rmi-10.builder.xml000066400000000000000000000003361420024370600254650ustar00rootroot00000000000000 0x10 Example ui ZGF2ZQ== fwupd-1.7.5/plugins/system76-launch/000077500000000000000000000000001420024370600173015ustar00rootroot00000000000000fwupd-1.7.5/plugins/system76-launch/README.md000066400000000000000000000016351420024370600205650ustar00rootroot00000000000000# System76 Launch ## Introduction This plugin is used to detach the System76 Launch device to DFU 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 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:0x3384` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. fwupd-1.7.5/plugins/system76-launch/fu-plugin-system76-launch.c000066400000000000000000000010201420024370600243130ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-system76-launch-device.h" static void fu_plugin_system76_launch_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_SYSTEM76_LAUNCH_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_system76_launch_init; } fwupd-1.7.5/plugins/system76-launch/fu-system76-launch-device.c000066400000000000000000000135421420024370600242700ustar00rootroot00000000000000/* * 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_TIMEOUT 1000 struct _FuSystem76LaunchDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuSystem76LaunchDevice, fu_system76_launch_device, FU_TYPE_USB_DEVICE) 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_in = 0x82; const guint8 ep_out = 0x03; gsize actual_len = 0; /* 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; } /* receive response */ if (!g_usb_device_interrupt_transfer(usb_device, ep_in, data, len, &actual_len, SYSTEM76_LAUNCH_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read response: "); return FALSE; } if (actual_len < len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "response truncated: received %" G_GSIZE_FORMAT " bytes", actual_len); return FALSE; } return TRUE; } 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_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) return TRUE; /* generate a message if not already set */ if (fu_device_get_update_message(device) == NULL) { g_autofree gchar *msg = NULL; msg = g_strdup_printf( "To ensure you have physical access, %s needs to be manually unlocked. " "Please press Fn+Esc to unlock and re-run the update.", fu_device_get_name(device)); 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)); fu_device_emit_request(device, request); /* poll for the user-unlock */ do { g_usleep(G_USEC_PER_SEC); 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_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_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.7.5/plugins/system76-launch/fu-system76-launch-device.h000066400000000000000000000006511420024370600242720ustar00rootroot00000000000000/* * 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.7.5/plugins/system76-launch/meson.build000066400000000000000000000010701420024370600214410ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginSystem76Launch"'] install_data(['system76-launch.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_system76_launch', fu_hash, sources : [ 'fu-plugin-system76-launch.c', 'fu-system76-launch-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/system76-launch/system76-launch.quirk000066400000000000000000000001641420024370600233300ustar00rootroot00000000000000# System76 Launch [USB\VID_3384&PID_0001&REV_0001] Plugin = system76_launch CounterpartGuid = USB\VID_03EB&PID_2FF4 fwupd-1.7.5/plugins/test/000077500000000000000000000000001420024370600153075ustar00rootroot00000000000000fwupd-1.7.5/plugins/test/README.md000066400000000000000000000007731420024370600165750ustar00rootroot00000000000000# 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. fwupd-1.7.5/plugins/test/fu-plugin-invalid.c000066400000000000000000000003631420024370600210070ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = "invalid"; } fwupd-1.7.5/plugins/test/fu-plugin-test-ble.c000066400000000000000000000006721420024370600211030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-test-ble-device.h" static void fu_plugin_test_ble_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_TEST_BLE_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_test_ble_init; } fwupd-1.7.5/plugins/test/fu-plugin-test.c000066400000000000000000000251451420024370600203450ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { guint delay_decompress_ms; guint delay_write_ms; guint delay_verify_ms; }; static void fu_plugin_test_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); g_debug("init"); } static void fu_plugin_test_destroy(FuPlugin *plugin) { // FuPluginData *data = fu_plugin_get_data (plugin); g_debug("destroy"); } static gboolean fu_plugin_test_load_xml(FuPlugin *plugin, const gchar *xml, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) delay_decompress_ms = NULL; g_autoptr(XbNode) delay_verify_ms = NULL; g_autoptr(XbNode) delay_write_ms = NULL; g_autoptr(XbSilo) silo = NULL; /* build silo */ if (!xb_builder_source_load_xml(source, 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; /* parse markup */ delay_decompress_ms = xb_silo_query_first(silo, "config/delay_decompress_ms", NULL); if (delay_decompress_ms != NULL) data->delay_decompress_ms = xb_node_get_text_as_uint(delay_decompress_ms); delay_write_ms = xb_silo_query_first(silo, "config/delay_write_ms", NULL); if (delay_write_ms != NULL) data->delay_write_ms = xb_node_get_text_as_uint(delay_write_ms); delay_verify_ms = xb_silo_query_first(silo, "config/delay_verify_ms", NULL); if (delay_verify_ms != NULL) data->delay_verify_ms = xb_node_get_text_as_uint(delay_verify_ms); /* success */ return TRUE; } static gboolean fu_plugin_test_startup(FuPlugin *plugin, GError **error) { const gchar *xml = g_getenv("FWUPD_TEST_PLUGIN_XML"); if (xml != NULL) { if (!fu_plugin_test_load_xml(plugin, xml, error)) return FALSE; } return TRUE; } static gboolean fu_plugin_test_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuDevice) device = NULL; device = fu_device_new_with_context(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_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 (g_strcmp0(g_getenv("FWUPD_PLUGIN_TEST"), "registration") == 0) { 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 (g_strcmp0(g_getenv("FWUPD_PLUGIN_TEST"), "composite") == 0) { g_autoptr(FuDevice) child1 = NULL; g_autoptr(FuDevice) child2 = NULL; child1 = fu_device_new_with_context(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_plugin_device_add(plugin, child1); child2 = fu_device_new_with_context(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_plugin_device_add(plugin, child2); } return TRUE; } static void fu_plugin_test_device_registered(FuPlugin *plugin, FuDevice *device) { fu_device_set_metadata(device, "BestDevice", "/dev/urandom"); } static gboolean fu_plugin_test_verify(FuPlugin *plugin, FuDevice *device, 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_plugin_test_get_version(GBytes *blob_fw) { const gchar *str = g_bytes_get_data(blob_fw, NULL); guint64 val = 0; if (str == NULL) return NULL; val = fu_common_strtoull(str); if (val == 0x0) return NULL; return fu_common_version_from_uint32(val, FWUPD_VERSION_FORMAT_TRIPLET); } static gboolean fu_plugin_test_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); const gchar *test = g_getenv("FWUPD_PLUGIN_TEST"); gboolean requires_activation = g_strcmp0(test, "requires-activation") == 0; gboolean requires_reboot = g_strcmp0(test, "requires-reboot") == 0; if (g_strcmp0(test, "fail") == 0) { 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); for (guint i = 0; i <= data->delay_decompress_ms; i++) { g_usleep(1000); fu_progress_set_percentage_full(progress, i, data->delay_decompress_ms); } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i <= data->delay_write_ms; i++) { g_usleep(1000); fu_progress_set_percentage_full(progress, i, data->delay_write_ms); } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_VERIFY); for (guint i = 0; i <= data->delay_verify_ms; i++) { g_usleep(1000); fu_progress_set_percentage_full(progress, i, data->delay_verify_ms); } /* composite test, upgrade composite devices */ if (g_strcmp0(test, "composite") == 0) { 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; } else if (g_strcmp0(fu_device_get_logical_id(device), "child2") == 0) { fu_device_set_version(device, "11"); return TRUE; } } /* upgrade, or downgrade */ if (requires_activation) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } else if (requires_reboot) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { g_autofree gchar *ver = fu_plugin_test_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 (g_strcmp0(test, "another-write-required") == 0) { g_unsetenv("FWUPD_PLUGIN_TEST"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* 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_plugin_test_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_plugin_test_get_results(FuPlugin *plugin, FuDevice *device, GError **error) { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); fu_device_set_update_error(device, NULL); return TRUE; } static gboolean fu_plugin_test_composite_prepare(FuPlugin *plugin, GPtrArray *devices, GError **error) { if (g_strcmp0(g_getenv("FWUPD_PLUGIN_TEST"), "composite") == 0) { 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_plugin_test_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { if (g_strcmp0(g_getenv("FWUPD_PLUGIN_TEST"), "composite") == 0) { 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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_test_init; vfuncs->destroy = fu_plugin_test_destroy; vfuncs->composite_cleanup = fu_plugin_test_composite_cleanup; vfuncs->composite_prepare = fu_plugin_test_composite_prepare; vfuncs->get_results = fu_plugin_test_get_results; vfuncs->activate = fu_plugin_test_activate; vfuncs->write_firmware = fu_plugin_test_write_firmware; vfuncs->verify = fu_plugin_test_verify; vfuncs->startup = fu_plugin_test_startup; vfuncs->coldplug = fu_plugin_test_coldplug; vfuncs->device_registered = fu_plugin_test_device_registered; } fwupd-1.7.5/plugins/test/fu-test-ble-device.c000066400000000000000000000010661420024370600210420ustar00rootroot00000000000000/* * 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); } static void fu_test_ble_device_class_init(FuTestBleDeviceClass *klass) { } fwupd-1.7.5/plugins/test/fu-test-ble-device.h000066400000000000000000000004621420024370600210460ustar00rootroot00000000000000/* * 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.7.5/plugins/test/meson.build000066400000000000000000000024601420024370600174530ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginTest"'] install_dummy = false if get_option('plugin_dummy') install_dummy = true endif if get_option('bluez') install_data(['test-ble.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) endif shared_module('fu_plugin_test', fu_hash, sources : [ 'fu-plugin-test.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : install_dummy, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) shared_module('fu_plugin_invalid', fu_hash, sources : [ 'fu-plugin-invalid.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : install_dummy, install_dir: plugin_dir, link_with : [ fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('bluez') shared_module('fu_plugin_test_ble', fu_hash, sources : [ 'fu-plugin-test-ble.c', 'fu-test-ble-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : install_dummy, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/test/test-ble.quirk000066400000000000000000000001621420024370600201020ustar00rootroot00000000000000[BLUETOOTH\VID_0461&PID_4EEE&REV_0001] Plugin = test_ble [BLUETOOTH\VID_0461&PID_4EEF&REV_0001] Plugin = test_ble fwupd-1.7.5/plugins/thelio-io/000077500000000000000000000000001420024370600162215ustar00rootroot00000000000000fwupd-1.7.5/plugins/thelio-io/README.md000066400000000000000000000015741420024370600175070ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/thelio-io/fu-plugin-thelio-io.c000066400000000000000000000007701420024370600221660ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-thelio-io-device.h" static void fu_plugin_thelio_io_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_THELIO_IO_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_thelio_io_init; } fwupd-1.7.5/plugins/thelio-io/fu-thelio-io-device.c000066400000000000000000000076001420024370600221260ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); 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.7.5/plugins/thelio-io/fu-thelio-io-device.h000066400000000000000000000005571420024370600221370ustar00rootroot00000000000000/* * 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.7.5/plugins/thelio-io/meson.build000066400000000000000000000010331420024370600203600ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginThelioIo"'] install_data(['thelio-io.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_thelio_io', fu_hash, sources : [ 'fu-plugin-thelio-io.c', 'fu-thelio-io-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/thelio-io/thelio-io.quirk000066400000000000000000000001111420024370600211600ustar00rootroot00000000000000# System76 Thelio IO [USB\VID_1209&PID_1776&REV_0001] Plugin = thelio_io fwupd-1.7.5/plugins/thunderbolt/000077500000000000000000000000001420024370600166625ustar00rootroot00000000000000fwupd-1.7.5/plugins/thunderbolt/README.md000066400000000000000000000077561420024370600201600ustar00rootroot00000000000000# 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 disambiguiate 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". ## External Interface Access This plugin requires read/write access to `/sys/bus/thunderbolt`. fwupd-1.7.5/plugins/thunderbolt/fu-plugin-thunderbolt.c000066400000000000000000000070001420024370600232610ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-thunderbolt-controller.h" #include "fu-thunderbolt-firmware-update.h" #include "fu-thunderbolt-firmware.h" #include "fu-thunderbolt-retimer.h" static gboolean fu_plugin_thunderbolt_safe_kernel(FuPlugin *plugin, GError **error) { g_autofree gchar *minimum_kernel = NULL; minimum_kernel = fu_plugin_get_config_value(plugin, "MinimumKernelVersion"); if (minimum_kernel == NULL) { g_debug("Ignoring kernel safety checks"); return TRUE; } return fu_common_check_kernel_version(minimum_kernel, error); } static gboolean fu_plugin_thunderbolt_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"); fu_device_set_context(dev, ctx); return TRUE; } static void fu_plugin_thunderbolt_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_debug("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 void fu_plugin_thunderbolt_init(FuPlugin *plugin) { 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); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_THUNDERBOLT_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE); } static gboolean fu_plugin_thunderbolt_startup(FuPlugin *plugin, GError **error) { return fu_plugin_thunderbolt_safe_kernel(plugin, error); } static gboolean fu_plugin_thunderbolt_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_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_plugin_thunderbolt_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_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE)) { return fu_thunderbolt_retimer_set_parent_port_online(dev, error); } } return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_thunderbolt_init; vfuncs->startup = fu_plugin_thunderbolt_startup; vfuncs->device_registered = fu_plugin_thunderbolt_device_registered; vfuncs->device_created = fu_plugin_thunderbolt_device_created; vfuncs->composite_prepare = fu_plugin_thunderbolt_composite_prepare; vfuncs->composite_cleanup = fu_plugin_thunderbolt_composite_cleanup; } fwupd-1.7.5/plugins/thunderbolt/fu-self-test.c000066400000000000000000001076241420024370600213560ustar00rootroot00000000000000/* * 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 #include #include "fu-context-private.h" #include "fu-plugin-private.h" #include "fu-thunderbolt-firmware-update.h" #include "fu-thunderbolt-firmware.h" #include "fu-udev-device-private.h" static gchar * udev_mock_add_domain(UMockdevTestbed *bed, int id) { gchar *path; g_autofree gchar *name = NULL; name = g_strdup_printf("domain%d", id); path = umockdev_testbed_add_device(bed, "thunderbolt", name, NULL, "security", "secure", NULL, "DEVTYPE", "thunderbolt_domain", NULL); g_assert_nonnull(path); return path; } 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; } else 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_domain(bed, root->device->domain_id); 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; device = fu_udev_device_new_with_context(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), &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 *pluginfn = NULL; g_autofree gchar *sysfs = NULL; g_autoptr(GError) error = NULL; 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(tt->ctx); g_assert_nonnull(tt->plugin); pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_thunderbolt." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(tt->plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(tt->plugin, &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 gboolean _compare_images(FuThunderboltFirmware *firmware_old, FuThunderboltFirmware *firmware, GError **error) { if (fu_thunderbolt_firmware_is_host(firmware) != fu_thunderbolt_firmware_is_host(firmware_old)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect firmware mode, got %s, expected %s", fu_thunderbolt_firmware_is_host(firmware) ? "host" : "device", fu_thunderbolt_firmware_is_host(firmware_old) ? "host" : "device"); return FALSE; } if (fu_thunderbolt_firmware_get_vendor_id(firmware) != fu_thunderbolt_firmware_get_vendor_id(firmware_old)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device vendor, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_vendor_id(firmware), fu_thunderbolt_firmware_get_vendor_id(firmware_old)); return FALSE; } if (fu_thunderbolt_firmware_get_device_id(firmware) != fu_thunderbolt_firmware_get_device_id(firmware_old)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device type, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_device_id(firmware), fu_thunderbolt_firmware_get_device_id(firmware_old)); return FALSE; } if (fu_thunderbolt_firmware_get_model_id(firmware) != fu_thunderbolt_firmware_get_model_id(firmware_old)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device model, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_model_id(firmware), fu_thunderbolt_firmware_get_model_id(firmware_old)); return FALSE; } if (fu_thunderbolt_firmware_get_has_pd(firmware_old) && !fu_thunderbolt_firmware_get_has_pd(firmware)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "new firmware is missing PD"); return FALSE; } if (fu_thunderbolt_firmware_get_flash_size(firmware) != fu_thunderbolt_firmware_get_flash_size(firmware_old)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect flash size"); return FALSE; } return TRUE; } 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(FuThunderboltFirmwareUpdate) firmware_fwi = fu_thunderbolt_firmware_update_new(); g_autoptr(FuThunderboltFirmware) firmware_ctl = fu_thunderbolt_firmware_new(); g_autoptr(FuThunderboltFirmware) firmware_bad = fu_thunderbolt_firmware_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(FU_FIRMWARE(firmware_ctl), ctl_data, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_true(ret); g_assert_no_error(error); /* 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(FU_FIRMWARE(firmware_fwi), fwi_data, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_true(ret); g_assert_no_error(error); /* 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(FU_FIRMWARE(firmware_bad), bad_data, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ); g_debug("expected image validation error [ctl]: %s", error->message); g_clear_error(&error); /* now for some testing ... this should work */ ret = _compare_images(firmware_ctl, FU_THUNDERBOLT_FIRMWARE(firmware_fwi), &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, 0, &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, 0, &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, 0, &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, 0, &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 *quirkdatadir = NULL; g_test_init(&argc, &argv, NULL); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); 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.7.5/plugins/thunderbolt/fu-thunderbolt-common.c000066400000000000000000000053221420024370600232600ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_offline = NULL; g_autoptr(GError) error_rescan = NULL; if (fu_thunderbolt_device_check_usb4_port_path(device, offline, &error_offline)) { if (!fu_udev_device_write_sysfs(device, offline, "1", error)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Setting usb4 port offline failed"); return FALSE; } } else { g_debug("%s", error_offline->message); return TRUE; } if (fu_thunderbolt_device_check_usb4_port_path(device, rescan, &error_rescan)) { if (!fu_udev_device_write_sysfs(device, rescan, "1", error)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Rescan on port failed"); return FALSE; } } else { g_debug("%s", error_rescan->message); return TRUE; } 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_offline = NULL; if (fu_thunderbolt_device_check_usb4_port_path(device, offline, &error_offline)) { 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.7.5/plugins/thunderbolt/fu-thunderbolt-common.h000066400000000000000000000007501420024370600232650ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include 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.7.5/plugins/thunderbolt/fu-thunderbolt-controller.c000066400000000000000000000263251420024370600241610ustar00rootroot00000000000000/* * 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 #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-controller.h" #include "fu-thunderbolt-firmware.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) 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"; else return "Thunderbolt host controller"; } if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE) { if (self->gen >= 4) return "USB4 device controller"; else 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_common_string_append_kv(str, idt, "Device Type", fu_thunderbolt_controller_kind_to_string(self)); fu_common_string_append_kb(str, idt, "Safe Mode", self->safe_mode); fu_common_string_append_kb(str, idt, "Native mode", self->is_native); fu_common_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(FuThunderboltFirmware) firmware = fu_thunderbolt_firmware_new(); 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; if (!fu_firmware_parse(FU_FIRMWARE(firmware), controller_fw, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; self->is_native = fu_thunderbolt_firmware_is_native(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 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); /* check if device is authorized */ if (!fu_thunderbolt_device_check_authorized(FU_THUNDERBOLT_DEVICE(self), error)) return FALSE; } else { device_id = g_strdup("TBT-fixed"); } 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) { 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); } /* 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; 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); } 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.7.5/plugins/thunderbolt/fu-thunderbolt-controller.h000066400000000000000000000006341420024370600241610ustar00rootroot00000000000000/* * 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.7.5/plugins/thunderbolt/fu-thunderbolt-device.c000066400000000000000000000351751420024370600232400ustar00rootroot00000000000000/* * 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 #include "fu-thunderbolt-device.h" #include "fu-thunderbolt-firmware-update.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); g_usleep(TBT_NVM_RETRY_TIMEOUT * 1000); /* 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_common_string_append_kv(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_common_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(FuThunderboltFirmwareUpdate) firmware = fu_thunderbolt_firmware_update_new(); g_autoptr(FuThunderboltFirmware) firmware_old = fu_thunderbolt_firmware_new(); g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GFile) nvmem = NULL; /* parse */ if (!fu_firmware_parse(FU_FIRMWARE(firmware), fw, flags, error)) 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; if (!fu_firmware_parse(FU_FIRMWARE(firmware_old), controller_fw, flags, error)) return NULL; if (fu_thunderbolt_firmware_is_host(FU_THUNDERBOLT_FIRMWARE(firmware)) != fu_thunderbolt_firmware_is_host(firmware_old)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect firmware mode, got %s, expected %s", fu_thunderbolt_firmware_is_host(FU_THUNDERBOLT_FIRMWARE(firmware)) ? "host" : "device", fu_thunderbolt_firmware_is_host(firmware_old) ? "host" : "device"); return NULL; } if (fu_thunderbolt_firmware_get_vendor_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != fu_thunderbolt_firmware_get_vendor_id(firmware_old)) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device vendor, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_vendor_id(FU_THUNDERBOLT_FIRMWARE(firmware)), fu_thunderbolt_firmware_get_vendor_id(firmware_old)); return NULL; } if (fu_thunderbolt_firmware_get_device_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != fu_thunderbolt_firmware_get_device_id(firmware_old)) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device type, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_device_id(FU_THUNDERBOLT_FIRMWARE(firmware)), fu_thunderbolt_firmware_get_device_id(firmware_old)); return NULL; } if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { if (fu_thunderbolt_firmware_get_model_id(FU_THUNDERBOLT_FIRMWARE(firmware)) != fu_thunderbolt_firmware_get_model_id(firmware_old)) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device model, got 0x%04x, expected 0x%04x", fu_thunderbolt_firmware_get_model_id(FU_THUNDERBOLT_FIRMWARE(firmware)), fu_thunderbolt_firmware_get_model_id(firmware_old)); return NULL; } /* old firmware has PD but new doesn't (we don't care about other way around) */ if (fu_thunderbolt_firmware_get_has_pd(firmware_old) && !fu_thunderbolt_firmware_get_has_pd(FU_THUNDERBOLT_FIRMWARE(firmware))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect PD section"); return NULL; } if (fu_thunderbolt_firmware_get_flash_size(FU_THUNDERBOLT_FIRMWARE(firmware)) != fu_thunderbolt_firmware_get_flash_size(firmware_old)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect flash size"); return NULL; } } /* success */ return FU_FIRMWARE(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 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_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_icon(FU_DEVICE(self), "thunderbolt"); fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt"); 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->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.7.5/plugins/thunderbolt/fu-thunderbolt-device.h000066400000000000000000000015351420024370600232360ustar00rootroot00000000000000/* * 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.7.5/plugins/thunderbolt/fu-thunderbolt-firmware-update.c000066400000000000000000000056431420024370600250720ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-thunderbolt-firmware-update.h" #include "fu-thunderbolt-firmware.h" struct _FuThunderboltFirmwareUpdate { FuThunderboltFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuThunderboltFirmwareUpdate, fu_thunderbolt_firmware_update, FU_TYPE_THUNDERBOLT_FIRMWARE) static inline gboolean fu_thunderbolt_firmware_valid_farb_pointer(guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFF; } static gboolean fu_thunderbolt_firmware_read_farb_pointer_impl(FuThunderboltFirmwareUpdate *self, FuThunderboltSection section, guint32 offset, guint32 *value, GError **error) { FuThunderboltFirmware *tbt = FU_THUNDERBOLT_FIRMWARE(self); guint32 tmp = 0; if (!fu_thunderbolt_firmware_read_location(tbt, section, offset, (guint8 *)&tmp, 3, /* 24 bits */ error)) { g_prefix_error(error, "failed to read farb pointer: "); return FALSE; } *value = GUINT32_FROM_LE(tmp); return TRUE; } /* returns invalid FARB pointer on error */ static guint32 fu_thunderbolt_firmware_read_farb_pointer(FuThunderboltFirmwareUpdate *self, GError **error) { guint32 value; if (!fu_thunderbolt_firmware_read_farb_pointer_impl(self, _SECTION_DIGITAL, 0x0, &value, error)) return 0; if (fu_thunderbolt_firmware_valid_farb_pointer(value)) return value; if (!fu_thunderbolt_firmware_read_farb_pointer_impl(self, _SECTION_DIGITAL, 0x1000, &value, error)) return 0; if (!fu_thunderbolt_firmware_valid_farb_pointer(value)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid FW image file format"); return 0; } return value; } static gboolean fu_thunderbolt_firmware_update_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuThunderboltFirmwareUpdate *self = FU_THUNDERBOLT_FIRMWARE_UPDATE(firmware); guint32 offset = fu_thunderbolt_firmware_read_farb_pointer(self, error); if (offset == 0) return FALSE; g_debug("detected digital section begins at 0x%x", offset); fu_thunderbolt_firmware_set_digital(FU_THUNDERBOLT_FIRMWARE(firmware), offset); return TRUE; } static void fu_thunderbolt_firmware_update_init(FuThunderboltFirmwareUpdate *self) { } static void fu_thunderbolt_firmware_update_class_init(FuThunderboltFirmwareUpdateClass *klass) { FuThunderboltFirmwareClass *klass_firmware = FU_THUNDERBOLT_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_thunderbolt_firmware_update_parse; } FuThunderboltFirmwareUpdate * fu_thunderbolt_firmware_update_new(void) { return g_object_new(FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE, NULL); } fwupd-1.7.5/plugins/thunderbolt/fu-thunderbolt-firmware-update.h000066400000000000000000000010131420024370600250620ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-thunderbolt-firmware.h" #define FU_TYPE_THUNDERBOLT_FIRMWARE_UPDATE (fu_thunderbolt_firmware_update_get_type()) G_DECLARE_FINAL_TYPE(FuThunderboltFirmwareUpdate, fu_thunderbolt_firmware_update, FU, THUNDERBOLT_FIRMWARE_UPDATE, FuThunderboltFirmware) FuThunderboltFirmwareUpdate * fu_thunderbolt_firmware_update_new(void); fwupd-1.7.5/plugins/thunderbolt/fu-thunderbolt-firmware.c000066400000000000000000000361241420024370600236100ustar00rootroot00000000000000/* * Copyright (C) 2017 Intel Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-thunderbolt-firmware.h" typedef struct { guint32 sections[_SECTION_LAST]; FuThunderboltFamily family; gboolean is_host; gboolean is_native; gboolean has_pd; guint16 device_id; guint16 vendor_id; guint16 model_id; guint gen; guint ports; guint8 flash_size; } FuThunderboltFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuThunderboltFirmware, fu_thunderbolt_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_thunderbolt_firmware_get_instance_private(o)) typedef struct { guint16 id; guint gen; FuThunderboltFamily family; guint ports; } FuThunderboltHwInfo; enum { DROM_ENTRY_MC = 0x6, }; gboolean fu_thunderbolt_firmware_is_host(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); priv = GET_PRIVATE(self); return priv->is_host; } gboolean fu_thunderbolt_firmware_is_native(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); priv = GET_PRIVATE(self); return priv->is_native; } gboolean fu_thunderbolt_firmware_get_has_pd(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), FALSE); priv = GET_PRIVATE(self); return priv->has_pd; } guint16 fu_thunderbolt_firmware_get_device_id(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); priv = GET_PRIVATE(self); return priv->device_id; } guint16 fu_thunderbolt_firmware_get_vendor_id(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); priv = GET_PRIVATE(self); return priv->vendor_id; } guint16 fu_thunderbolt_firmware_get_model_id(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); priv = GET_PRIVATE(self); return priv->model_id; } guint8 fu_thunderbolt_firmware_get_flash_size(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv; g_return_val_if_fail(FU_IS_THUNDERBOLT_FIRMWARE(self), 0x0); priv = GET_PRIVATE(self); return priv->flash_size; } static const gchar * fu_thunderbolt_firmware_family_to_string(FuThunderboltFamily family) { if (family == _FAMILY_FR) return "Falcon Ridge"; if (family == _FAMILY_WR) return "Win Ridge"; if (family == _FAMILY_AR) return "Alpine Ridge"; if (family == _FAMILY_AR_C) return "Alpine Ridge C"; if (family == _FAMILY_TR) return "Titan Ridge"; if (family == _FAMILY_BB) return "BB"; if (family == _FAMILY_MR) return "Maple Ridge"; return "Unknown"; } static void fu_thunderbolt_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuThunderboltFirmware *self = FU_THUNDERBOLT_FIRMWARE(firmware); FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kv(bn, "family", fu_thunderbolt_firmware_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, "device_id", priv->device_id); fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id); fu_xmlb_builder_insert_kx(bn, "model_id", priv->model_id); 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 < _SECTION_LAST; i++) { g_autofree gchar *tmp = g_strdup_printf("%x", priv->sections[i]); xb_builder_node_insert_text(bn, "section", tmp, NULL); } } static inline gboolean fu_thunderbolt_firmware_valid_pd_pointer(guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFFFF; } gboolean fu_thunderbolt_firmware_read_location(FuThunderboltFirmware *self, FuThunderboltSection section, guint32 offset, guint8 *buf, guint32 len, GError **error) { const guint8 *srcbuf; gsize srcbufsz = 0; FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); guint32 location_start = priv->sections[section] + offset; g_autoptr(GBytes) fw = NULL; /* get blob */ fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); if (fw == NULL) return FALSE; srcbuf = g_bytes_get_data(fw, &srcbufsz); if (!fu_memcpy_safe(buf, len, 0x0, /* dst */ srcbuf, srcbufsz, location_start, /* src */ len, error)) { g_prefix_error(error, "location is outside of the given image: "); return FALSE; } return TRUE; } static gboolean fu_thunderbolt_firmware_read_uint8(FuThunderboltFirmware *self, FuThunderboltSection section, guint32 offset, guint8 *value, GError **error) { return fu_thunderbolt_firmware_read_location(self, section, offset, value, 1, error); } static gboolean fu_thunderbolt_firmware_read_uint16(FuThunderboltFirmware *self, FuThunderboltSection section, guint32 offset, guint16 *value, GError **error) { guint16 tmp = 0; if (!fu_thunderbolt_firmware_read_location(self, section, offset, (guint8 *)&tmp, sizeof(tmp), error)) { g_prefix_error(error, "failed to read uint16: "); return FALSE; } *value = GUINT16_FROM_LE(tmp); return TRUE; } static gboolean fu_thunderbolt_firmware_read_uint32(FuThunderboltFirmware *self, FuThunderboltSection section, guint32 offset, guint32 *value, GError **error) { guint32 tmp = 0; if (!fu_thunderbolt_firmware_read_location(self, section, offset, (guint8 *)&tmp, sizeof(tmp), error)) { g_prefix_error(error, "failed to read uint32: "); return FALSE; } *value = GUINT32_FROM_LE(tmp); return TRUE; } /* * 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_thunderbolt_firmware_read_ucode_section_len(FuThunderboltFirmware *self, guint32 offset, guint16 *value, GError **error) { if (!fu_thunderbolt_firmware_read_uint16(self, _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[_SECTION_DIGITAL].offset is already set */ static gboolean fu_thunderbolt_firmware_read_sections(FuThunderboltFirmware *self, GError **error) { guint32 offset; FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); if (priv->gen >= 3 || priv->gen == 0) { if (!fu_thunderbolt_firmware_read_uint32(self, _SECTION_DIGITAL, 0x10e, &offset, error)) return FALSE; priv->sections[_SECTION_DROM] = offset + priv->sections[_SECTION_DIGITAL]; if (!fu_thunderbolt_firmware_read_uint32(self, _SECTION_DIGITAL, 0x75, &offset, error)) return FALSE; priv->sections[_SECTION_ARC_PARAMS] = offset + priv->sections[_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_thunderbolt_firmware_read_uint8(self, _SECTION_DIGITAL, 0x2, &available_sections, error)) { g_prefix_error(error, "failed to read available sections: "); return FALSE; } if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DIGITAL, 0x3, &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, "Can't 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_thunderbolt_firmware_read_ucode_section_len(self, offset, &ucode_offset, error)) return FALSE; offset += ucode_offset; } } priv->sections[_SECTION_DRAM_UCODE] = offset + priv->sections[_SECTION_DIGITAL]; } return TRUE; } static gboolean fu_thunderbolt_firmware_missing_needed_drom(FuThunderboltFirmware *self) { FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); if (priv->sections[_SECTION_DROM] != 0) return FALSE; if (priv->is_host && priv->gen < 3) return FALSE; return TRUE; } void fu_thunderbolt_firmware_set_digital(FuThunderboltFirmware *self, guint32 offset) { FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); priv->sections[_SECTION_DIGITAL] = offset; } static gboolean fu_thunderbolt_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuThunderboltFirmware *self = FU_THUNDERBOLT_FIRMWARE(firmware); FuThunderboltFirmwarePrivate *priv = GET_PRIVATE(self); FuThunderboltFirmwareClass *klass_firmware = FU_THUNDERBOLT_FIRMWARE_GET_CLASS(firmware); guint8 tmp = 0; guint16 version = 0; static const FuThunderboltHwInfo hw_info_arr[] = { {0x156D, 2, _FAMILY_FR, 2}, /* FR 4C */ {0x156B, 2, _FAMILY_FR, 1}, /* FR 2C */ {0x157E, 2, _FAMILY_WR, 1}, /* WR */ {0x1578, 3, _FAMILY_AR, 2}, /* AR 4C */ {0x1576, 3, _FAMILY_AR, 1}, /* AR 2C */ {0x15C0, 3, _FAMILY_AR, 1}, /* AR LP */ {0x15D3, 3, _FAMILY_AR_C, 2}, /* AR-C 4C */ {0x15DA, 3, _FAMILY_AR_C, 1}, /* AR-C 2C */ {0x15E7, 3, _FAMILY_TR, 1}, /* TR 2C */ {0x15EA, 3, _FAMILY_TR, 2}, /* TR 4C */ {0x15EF, 3, _FAMILY_TR, 2}, /* TR 4C device */ {0x15EE, 3, _FAMILY_BB, 0}, /* BB device */ /* 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, _FAMILY_MR, 2}, {0x1137, 4, _FAMILY_MR, 2}, {0}}; g_autofree gchar *version_str = NULL; /* add this straight away so we can read it without a self */ fu_firmware_set_bytes(firmware, fw); /* subclassed */ if (klass_firmware->parse != NULL) { if (!klass_firmware->parse(firmware, fw, addr_start, addr_end, flags, error)) return FALSE; } /* is native */ if (!fu_thunderbolt_firmware_read_uint8(self, _SECTION_DIGITAL, FU_TBT_OFFSET_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_thunderbolt_firmware_read_uint8(self, _SECTION_DIGITAL, 0x10, &tmp, error)) { g_prefix_error(error, "failed to read is-host: "); return FALSE; } priv->is_host = tmp & (1 << 1); /* device ID */ if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DIGITAL, 0x5, &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].id != 0; i++) { if (hw_info_arr[i].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->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_thunderbolt_firmware_read_sections(self, error)) return FALSE; if (fu_thunderbolt_firmware_missing_needed_drom(self)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "Can't find required FW sections"); return FALSE; } /* vendor:model */ if (priv->sections[_SECTION_DROM] != 0) { if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DROM, 0x10, &priv->vendor_id, error)) { g_prefix_error(error, "failed to read vendor-id: "); return FALSE; } if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DROM, 0x12, &priv->model_id, error)) { g_prefix_error(error, "failed to read model-id: "); return FALSE; } } /* has PD */ if (priv->sections[_SECTION_ARC_PARAMS] != 0) { guint32 pd_pointer = 0x0; if (!fu_thunderbolt_firmware_read_uint32(self, _SECTION_ARC_PARAMS, 0x10C, &pd_pointer, error)) { g_prefix_error(error, "failed to read pd-pointer: "); return FALSE; } priv->has_pd = fu_thunderbolt_firmware_valid_pd_pointer(pd_pointer); } /* versions */ switch (priv->family) { case _FAMILY_TR: if (!fu_thunderbolt_firmware_read_uint16(self, _SECTION_DIGITAL, 0x09, &version, error)) { g_prefix_error(error, "failed to read version: "); return FALSE; } version_str = fu_common_version_from_uint16(version, FWUPD_VERSION_FORMAT_BCD); fu_firmware_set_version(FU_FIRMWARE(self), version_str); break; default: break; } if (priv->is_host) { switch (priv->family) { case _FAMILY_AR: case _FAMILY_AR_C: case _FAMILY_TR: /* This is used for comparison between old and new image, not a raw number */ if (!fu_thunderbolt_firmware_read_uint8(self, _SECTION_DIGITAL, 0x45, &tmp, error)) { g_prefix_error(error, "failed to read flash size: "); return FALSE; } priv->flash_size = tmp & 0x07; break; default: break; } } /* success */ return TRUE; } static void fu_thunderbolt_firmware_init(FuThunderboltFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_thunderbolt_firmware_class_init(FuThunderboltFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_thunderbolt_firmware_parse; klass_firmware->export = fu_thunderbolt_firmware_export; } FuThunderboltFirmware * fu_thunderbolt_firmware_new(void) { return g_object_new(FU_TYPE_THUNDERBOLT_FIRMWARE, NULL); } fwupd-1.7.5/plugins/thunderbolt/fu-thunderbolt-firmware.h000066400000000000000000000036111420024370600236100ustar00rootroot00000000000000/* * Copyright (C) 2017 Intel Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_THUNDERBOLT_FIRMWARE (fu_thunderbolt_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuThunderboltFirmware, fu_thunderbolt_firmware, FU, THUNDERBOLT_FIRMWARE, FuFirmware) typedef enum { _SECTION_DIGITAL, _SECTION_DROM, _SECTION_ARC_PARAMS, _SECTION_DRAM_UCODE, _SECTION_LAST } FuThunderboltSection; typedef enum { _FAMILY_UNKNOWN, _FAMILY_FR, _FAMILY_WR, _FAMILY_AR, _FAMILY_AR_C, _FAMILY_TR, _FAMILY_BB, _FAMILY_MR, } FuThunderboltFamily; struct _FuThunderboltFirmwareClass { FuFirmwareClass parent_class; gboolean (*parse)(FuFirmware *self, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error); /*< private >*/ gpointer padding[28]; }; /* byte offsets in firmware image */ #define FU_TBT_OFFSET_NATIVE 0x7B #define FU_TBT_CHUNK_SZ 0x40 FuThunderboltFirmware * fu_thunderbolt_firmware_new(void); gboolean fu_thunderbolt_firmware_is_host(FuThunderboltFirmware *self); gboolean fu_thunderbolt_firmware_is_native(FuThunderboltFirmware *self); gboolean fu_thunderbolt_firmware_get_has_pd(FuThunderboltFirmware *self); guint16 fu_thunderbolt_firmware_get_device_id(FuThunderboltFirmware *self); guint16 fu_thunderbolt_firmware_get_vendor_id(FuThunderboltFirmware *self); guint16 fu_thunderbolt_firmware_get_model_id(FuThunderboltFirmware *self); guint8 fu_thunderbolt_firmware_get_flash_size(FuThunderboltFirmware *self); void fu_thunderbolt_firmware_set_digital(FuThunderboltFirmware *self, guint32 offset); gboolean fu_thunderbolt_firmware_read_location(FuThunderboltFirmware *self, FuThunderboltSection section, guint32 offset, guint8 *buf, guint32 len, GError **error); fwupd-1.7.5/plugins/thunderbolt/fu-thunderbolt-retimer.c000066400000000000000000000110731420024370600234370ustar00rootroot00000000000000/* * 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 #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_with_context(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); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_thunderbolt_retimer_parent_class)->probe(device, error)) return FALSE; /* 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_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.7.5/plugins/thunderbolt/fu-thunderbolt-retimer.h000066400000000000000000000012111420024370600234350ustar00rootroot00000000000000/* * 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.7.5/plugins/thunderbolt/meson.build000066400000000000000000000042341420024370600210270ustar00rootroot00000000000000if get_option('plugin_thunderbolt') if not get_option('gudev') error('gudev is required for plugin_thunderbolt') endif cargs = ['-DG_LOG_DOMAIN="FuPluginThunderbolt"'] install_data([ 'thunderbolt.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) fu_plugin_thunderbolt = shared_module('fu_plugin_thunderbolt', fu_hash, sources : [ 'fu-plugin-thunderbolt.c', 'fu-thunderbolt-common.c', 'fu-thunderbolt-device.c', 'fu-thunderbolt-retimer.c', 'fu-thunderbolt-controller.c', 'fu-thunderbolt-firmware.c', 'fu-thunderbolt-firmware-update.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) install_data(['thunderbolt.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) # we use functions from 2.52 in the tests if get_option('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', fu_hash, sources : [ 'fu-self-test.c', 'fu-plugin-thunderbolt.c', 'fu-thunderbolt-common.c', 'fu-thunderbolt-device.c', 'fu-thunderbolt-retimer.c', 'fu-thunderbolt-controller.c', 'fu-thunderbolt-firmware.c', 'fu-thunderbolt-firmware-update.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ gudev, plugin_deps, umockdev, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) 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.7.5/plugins/thunderbolt/tests/000077500000000000000000000000001420024370600200245ustar00rootroot00000000000000fwupd-1.7.5/plugins/thunderbolt/tests/COPYING000066400000000000000000000027321420024370600210630ustar00rootroot00000000000000Copyright(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.7.5/plugins/thunderbolt/tests/colorhug.bin000077700000000000000000000000001420024370600312552../../../src/tests/colorhug/firmware.binustar00rootroot00000000000000fwupd-1.7.5/plugins/thunderbolt/tests/minimal-fw-controller.bin000066400000000000000000000004771420024370600247470ustar00rootroot00000000000000$40`fwupd-1.7.5/plugins/thunderbolt/tests/minimal-fw.bin000066400000000000000000000005001420024370600225510ustar00rootroot00000000000000$40`fwupd-1.7.5/plugins/thunderbolt/thunderbolt.conf000066400000000000000000000004221420024370600220610ustar00rootroot00000000000000[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.7.5/plugins/thunderbolt/thunderbolt.quirk000066400000000000000000000002651420024370600222740ustar00rootroot00000000000000[THUNDERBOLT\TYPE_THUNDERBOLT_DEVICE] Plugin = thunderbolt GType = FuThunderboltController [THUNDERBOLT\TYPE_THUNDERBOLT_RETIMER] Plugin = thunderbolt GType = FuThunderboltRetimer fwupd-1.7.5/plugins/tpm/000077500000000000000000000000001420024370600151305ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/README.md000066400000000000000000000025431420024370600164130ustar00rootroot00000000000000# 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. The device exposed is not upgradable in any way and is just for debugging. The created device will be a child device of the system TPM device, which may or may not be upgradable. ## 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 ``` ## Vendor ID Security The device is not upgradable and thus requires no vendor ID set. ## 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`. fwupd-1.7.5/plugins/tpm/fu-plugin-tpm.c000066400000000000000000000260111420024370600200000ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-tpm-eventlog-parser.h" #include "fu-tpm-v1-device.h" #include "fu-tpm-v2-device.h" struct FuPluginData { FuTpmDevice *tpm_device; FuDevice *bios_device; GPtrArray *ev_items; /* of FuTpmEventlogItem */ }; static void fu_plugin_tpm_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "tpm_eventlog"); /* old name */ fu_plugin_add_udev_subsystem(plugin, "tpm"); fu_plugin_add_device_gtype(plugin, FU_TYPE_TPM_V2_DEVICE); } static void fu_plugin_tpm_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->tpm_device != NULL) g_object_unref(data->tpm_device); if (data->bios_device != NULL) g_object_unref(data->bios_device); if (data->ev_items != NULL) g_ptr_array_unref(data->ev_items); } static void fu_plugin_tpm_set_bios_pcr0s(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(GPtrArray) pcr0s = NULL; if (data->tpm_device == NULL) return; if (data->bios_device == NULL) return; /* add all the PCR0s */ pcr0s = fu_tpm_device_get_checksums(data->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(data->bios_device, checksum); } fu_device_add_flag(data->bios_device, FWUPD_DEVICE_FLAG_CAN_VERIFY); } /* set the PCR0 as the device checksum */ static void fu_plugin_tpm_device_registered(FuPlugin *plugin, FuDevice *device) { FuPluginData *data = fu_plugin_get_data(plugin); if (fu_device_has_instance_id(device, "main-system-firmware")) { g_set_object(&data->bios_device, device); fu_plugin_tpm_set_bios_pcr0s(plugin); } } static void fu_plugin_tpm_device_added(FuPlugin *plugin, FuDevice *dev) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(GPtrArray) pcr0s = NULL; g_set_object(&data->tpm_device, FU_TPM_DEVICE(dev)); fu_plugin_add_report_metadata(plugin, "TpmFamily", fu_tpm_device_get_family(FU_TPM_DEVICE(dev))); /* ensure */ fu_plugin_tpm_set_bios_pcr0s(plugin); /* add extra plugin metadata */ pcr0s = fu_tpm_device_get_checksums(data->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; } } } static void fu_plugin_tpm_add_security_attr_version(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fu_security_attrs_append(attrs, attr); /* check exists, and in v2.0 mode */ if (data->tpm_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } if (g_strcmp0(fu_tpm_device_get_family(data->tpm_device), "2.0") != 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(data->tpm_device))); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_FOUND); } static void fu_plugin_tpm_add_security_attr_eventlog(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(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 (data->tpm_device == NULL) return; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fwupd_security_attr_add_guids(attr, fu_device_get_guids(data->tpm_device)); fu_security_attrs_append(attrs, attr); /* check reconstructed to PCR0 */ if (data->ev_items == NULL || data->bios_device == 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(data->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); return; } /* compare against the real PCR0s */ pcr0s_real = fu_tpm_device_get_checksums(data->tpm_device, 0); for (guint i = 0; i < pcr0s_real->len; i++) { const gchar *checksum = g_ptr_array_index(pcr0s_real, i); reconstructed = FALSE; for (guint j = 0; j < pcr0s_calc->len; j++) { const gchar *checksum_tmp = g_ptr_array_index(pcr0s_calc, 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); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_plugin_tpm_add_security_attr_empty(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *data = fu_plugin_get_data(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* no TPM device */ if (data->tpm_device == NULL) return; /* add attributes */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_guids(attr, fu_device_get_guids(data->tpm_device)); 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(data->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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_plugin_tpm_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) return; fu_plugin_tpm_add_security_attr_version(plugin, attrs); fu_plugin_tpm_add_security_attr_eventlog(plugin, attrs); fu_plugin_tpm_add_security_attr_empty(plugin, attrs); } static gchar * fu_plugin_tpm_eventlog_report_metadata(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); GString *str = g_string_new(""); g_autoptr(GPtrArray) pcrs = NULL; for (guint i = 0; i < data->ev_items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index(data->ev_items, i); g_autofree gchar *blobstr = fu_tpm_eventlog_blobstr(item->blob); g_autofree gchar *checksum = NULL; 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 continue; g_string_append_printf(str, "0x%08x %s", item->kind, checksum); if (blobstr != NULL) g_string_append_printf(str, " [%s]", blobstr); g_string_append(str, "\n"); } pcrs = fu_tpm_eventlog_calc_checksums(data->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_plugin_tpm_coldplug_eventlog(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); gsize bufsz = 0; const gchar *fn = "/sys/kernel/security/tpm0/binary_bios_measurements"; g_autofree gchar *str = NULL; g_autofree guint8 *buf = NULL; 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; } data->ev_items = fu_tpm_eventlog_parser_new(buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_NONE, error); if (data->ev_items == NULL) return FALSE; /* add optional report metadata */ str = fu_plugin_tpm_eventlog_report_metadata(plugin); fu_plugin_add_report_metadata(plugin, "TpmEventLog", str); return TRUE; } static gboolean fu_plugin_tpm_coldplug(FuPlugin *plugin, GError **error) { g_autoptr(GError) error_local = NULL; /* best effort */ if (!fu_plugin_tpm_coldplug_eventlog(plugin, &error_local)) g_warning("failed to load eventlog: %s", error_local->message); /* success */ return TRUE; } static gboolean fu_plugin_tpm_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *sysfstpmdir = NULL; g_autofree gchar *fn_pcrs = NULL; /* look for TPM v1.2 */ sysfstpmdir = fu_common_get_path(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) { data->tpm_device = fu_tpm_v1_device_new(fu_plugin_get_context(plugin)); g_object_set(data->tpm_device, "device-file", fn_pcrs, NULL); fu_device_set_physical_id(FU_DEVICE(data->tpm_device), "tpm"); if (!fu_device_probe(FU_DEVICE(data->tpm_device), error)) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(data->tpm_device)); } /* success */ return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_tpm_init; vfuncs->destroy = fu_plugin_tpm_destroy; vfuncs->startup = fu_plugin_tpm_startup; vfuncs->coldplug = fu_plugin_tpm_coldplug; vfuncs->device_added = fu_plugin_tpm_device_added; vfuncs->device_registered = fu_plugin_tpm_device_registered; vfuncs->add_security_attrs = fu_plugin_tpm_add_security_attrs; } fwupd-1.7.5/plugins/tpm/fu-self-test.c000066400000000000000000000177241420024370600176250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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-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_autofree gchar *pluginfn = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = fu_plugin_new(ctx); 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 */ pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_tpm." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(plugin, &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); 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); 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) { g_autoptr(FuContext) ctx = fu_context_new(); 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"); 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 if (!fu_device_setup(FU_DEVICE(device), &error)) { if (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); 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 *pluginfn = NULL; g_autofree gchar *testdatadir = NULL; g_auto(GStrv) environ_old = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = fu_plugin_new(ctx); 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); g_setenv("FWUPD_SYSFSTPMDIR", testdatadir, TRUE); /* load the plugin */ pluginfn = g_test_build_filename(G_TEST_BUILT, "libfu_plugin_tpm." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(plugin, &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); 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 */ g_setenv("FWUPD_SYSFSTPMDIR", g_environ_getenv(environ_old, "FWUPD_SYSFSTPMDIR"), TRUE); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; g_test_init(&argc, &argv, NULL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_SYSFSTPMDIR", testdatadir, TRUE); 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); 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.7.5/plugins/tpm/fu-tpm-device.c000066400000000000000000000063471420024370600177530ustar00rootroot00000000000000/* * 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_new0(FuTpmDevicePcrItem, 1); g_return_if_fail(FU_IS_TPM_DEVICE(self)); g_return_if_fail(checksum != NULL); 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_common_string_append_kv(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); fu_device_add_instance_id_full(FU_DEVICE(self), "system-tpm", FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS); } 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.7.5/plugins/tpm/fu-tpm-device.h000066400000000000000000000012231420024370600177440ustar00rootroot00000000000000/* * 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; gpointer __reserved[31]; }; 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.7.5/plugins/tpm/fu-tpm-eventlog-common.c000066400000000000000000000152321420024370600216160ustar00rootroot00000000000000/* * 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"; } const gchar * fu_tpm_eventlog_hash_to_string(TPM2_ALG_ID hash_kind) { if (hash_kind == TPM2_ALG_SHA1) return "SHA1"; if (hash_kind == TPM2_ALG_SHA256) return "SHA256"; if (hash_kind == TPM2_ALG_SHA384) return "SHA384"; if (hash_kind == TPM2_ALG_SHA512) return "SHA512"; return NULL; } 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; return 0; } const gchar * fu_tpm_eventlog_item_kind_to_string(FuTpmEventlogItemKind event_type) { if (event_type == EV_PREBOOT_CERT) return "EV_PREBOOT_CERT"; if (event_type == EV_POST_CODE) return "EV_POST_CODE"; if (event_type == EV_NO_ACTION) return "EV_NO_ACTION"; if (event_type == EV_SEPARATOR) return "EV_SEPARATOR"; if (event_type == EV_ACTION) return "EV_ACTION"; if (event_type == EV_EVENT_TAG) return "EV_EVENT_TAG"; if (event_type == EV_S_CRTM_CONTENTS) return "EV_S_CRTM_CONTENTS"; if (event_type == EV_S_CRTM_VERSION) return "EV_S_CRTM_VERSION"; if (event_type == EV_CPU_MICROCODE) return "EV_CPU_MICROCODE"; if (event_type == EV_PLATFORM_CONFIG_FLAGS) return "EV_PLATFORM_CONFIG_FLAGS"; if (event_type == EV_TABLE_OF_DEVICES) return "EV_TABLE_OF_DEVICES"; if (event_type == EV_COMPACT_HASH) return "EV_COMPACT_HASH"; if (event_type == EV_NONHOST_CODE) return "EV_NONHOST_CODE"; if (event_type == EV_NONHOST_CONFIG) return "EV_NONHOST_CONFIG"; if (event_type == EV_NONHOST_INFO) return "EV_NONHOST_INFO"; if (event_type == EV_OMIT_BOOT_DEVICE_EVENTS) return "EV_OMIT_BOOT_DEVICE_EVENTS"; if (event_type == EV_EFI_EVENT_BASE) return "EV_EFI_EVENT_BASE"; if (event_type == EV_EFI_VARIABLE_DRIVER_CONFIG) return "EV_EFI_VARIABLE_DRIVER_CONFIG"; if (event_type == EV_EFI_VARIABLE_BOOT) return "EV_EFI_VARIABLE_BOOT"; if (event_type == EV_EFI_BOOT_SERVICES_APPLICATION) return "EV_BOOT_SERVICES_APPLICATION"; if (event_type == EV_EFI_BOOT_SERVICES_DRIVER) return "EV_EFI_BOOT_SERVICES_DRIVER"; if (event_type == EV_EFI_RUNTIME_SERVICES_DRIVER) return "EV_EFI_RUNTIME_SERVICES_DRIVER"; if (event_type == EV_EFI_GPT_EVENT) return "EV_EFI_GPT_EVENT"; if (event_type == EV_EFI_ACTION) return "EV_EFI_ACTION"; if (event_type == EV_EFI_PLATFORM_FIRMWARE_BLOB) return "EV_EFI_PLATFORM_FIRMWARE_BLOB"; if (event_type == EV_EFI_HANDOFF_TABLES) return "EV_EFI_HANDOFF_TABLES"; if (event_type == EV_EFI_HCRTM_EVENT) return "EV_EFI_HCRTM_EVENT"; if (event_type == EV_EFI_VARIABLE_AUTHORITY) return "EV_EFI_EFI_VARIABLE_AUTHORITY"; return NULL; } 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) { gboolean has_printable = FALSE; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); g_autoptr(GString) str = g_string_new(NULL); for (gsize i = 0; i < bufsz; i++) { gchar chr = buf[i]; if (g_ascii_isprint(chr)) { g_string_append_c(str, chr); has_printable = TRUE; } else { g_string_append_c(str, '.'); } } if (!has_printable) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } GPtrArray * fu_tpm_eventlog_calc_checksums(GPtrArray *items, guint8 pcr, GError **error) { guint cnt_sha1 = 0; guint cnt_sha256 = 0; guint8 digest_sha1[TPM2_SHA1_DIGEST_SIZE] = {0x0}; guint8 digest_sha256[TPM2_SHA256_DIGEST_SIZE] = {0x0}; gsize digest_sha1_len = sizeof(digest_sha1); gsize digest_sha256_len = sizeof(digest_sha256); 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 (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 (cnt_sha1 == 0 && cnt_sha256 == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no SHA1 or SHA256 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)); } return g_steal_pointer(&csums); } fwupd-1.7.5/plugins/tpm/fu-tpm-eventlog-common.h000066400000000000000000000034371420024370600216270ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include typedef enum { 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 } FuTpmEventlogItemKind; typedef struct { guint8 pcr; FuTpmEventlogItemKind kind; GBytes *checksum_sha1; GBytes *checksum_sha256; GBytes *blob; } FuTpmEventlogItem; const gchar * fu_tpm_eventlog_pcr_to_string(gint pcr); const gchar * fu_tpm_eventlog_hash_to_string(TPM2_ALG_ID hash_kind); guint32 fu_tpm_eventlog_hash_get_size(TPM2_ALG_ID hash_kind); const gchar * fu_tpm_eventlog_item_kind_to_string(FuTpmEventlogItemKind event_type); 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.7.5/plugins/tpm/fu-tpm-eventlog-parser.c000066400000000000000000000211541420024370600216220ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-tpm-eventlog-parser.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_IDX_SIGNATURE 0x00 #define FU_TPM_EVENTLOG_V2_HDR_IDX_PLATFORM_CLASS 0x10 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_VERSION_MINOR 0x14 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_VERSION_MAJOR 0X15 #define FU_TPM_EVENTLOG_V2_HDR_IDX_SPEC_ERRATA 0x16 #define FU_TPM_EVENTLOG_V2_HDR_IDX_UINTN_SIZE 0x17 #define FU_TPM_EVENTLOG_V2_HDR_IDX_NUMBER_OF_ALGS 0x18 #define FU_TPM_EVENTLOG_V2_HDR_SIGNATURE "Spec ID Event03" #define FU_TPM_EVENTLOG_V2_IDX_PCR 0x00 #define FU_TPM_EVENTLOG_V2_IDX_TYPE 0x04 #define FU_TPM_EVENTLOG_V2_IDX_DIGEST_COUNT 0x08 #define FU_TPM_EVENTLOG_V2_SIZE 0x0c 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); g_free(item); } 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_common_string_append_kv(str, idt, "PCR", pcrstr); fu_common_string_append_kx(str, idt, "Type", item->kind); tmp = fu_tpm_eventlog_item_kind_to_string(item->kind); if (tmp != NULL) fu_common_string_append_kv(str, idt, "Description", tmp); if (item->checksum_sha1 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex(item->checksum_sha1); fu_common_string_append_kv(str, idt, "ChecksumSha1", csum); } if (item->checksum_sha256 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex(item->checksum_sha256); fu_common_string_append_kv(str, idt, "ChecksumSha256", csum); } if (item->blob != NULL) { g_autofree gchar *blobstr = fu_tpm_eventlog_blobstr(item->blob); if (blobstr != NULL) fu_common_string_append_kv(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_common_read_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 = 0; guint32 event_type = 0; guint32 digestcnt = 0; guint32 datasz = 0; g_autoptr(GBytes) checksum_sha1 = NULL; g_autoptr(GBytes) checksum_sha256 = NULL; /* read entry */ if (!fu_common_read_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_PCR, &pcr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_TYPE, &event_type, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V2_IDX_DIGEST_COUNT, &digestcnt, G_LITTLE_ENDIAN, error)) return NULL; /* read checksum block */ idx += FU_TPM_EVENTLOG_V2_SIZE; 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_common_read_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); /* next block */ idx += alg_size; } /* read data block */ if (!fu_common_read_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); if (pcr == ESYS_TR_PCR0 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS) { FuTpmEventlogItem *item; /* build item */ item = g_new0(FuTpmEventlogItem, 1); item->pcr = pcr; item->kind = event_type; item->checksum_sha1 = g_steal_pointer(&checksum_sha1); item->checksum_sha256 = g_steal_pointer(&checksum_sha256); 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); if (g_getenv("FWUPD_TPM_EVENTLOG_VERBOSE") != NULL) fu_common_dump_bytes(G_LOG_DOMAIN, "TpmEvent", item->blob); } g_ptr_array_add(items, 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_common_read_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_PCR, &pcr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_TYPE, &event_type, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_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) { FuTpmEventlogItem *item; 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); if (g_getenv("FWUPD_TPM_EVENTLOG_VERBOSE") != NULL) fu_common_dump_bytes(G_LOG_DOMAIN, "TpmEvent", item->blob); } g_ptr_array_add(items, item); } idx += datasz; } return g_steal_pointer(&items); } fwupd-1.7.5/plugins/tpm/fu-tpm-eventlog-parser.h000066400000000000000000000010771420024370600216310ustar00rootroot00000000000000/* * 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.7.5/plugins/tpm/fu-tpm-eventlog.c000066400000000000000000000103171420024370600203270ustar00rootroot00000000000000/* * 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 #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_common_string_append_kv(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_common_string_append_kv(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 */ _("Show extra debugging information"), NULL}, {"pcr", 'p', 0, G_OPTION_ARG_INT, &pcr, /* TRANSLATORS: command line option */ _("Only show single PCR value"), NULL}, {NULL}}; #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 setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); /* 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) { g_setenv("G_MESSAGES_DEBUG", "all", FALSE); 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.7.5/plugins/tpm/fu-tpm-v1-device.c000066400000000000000000000046261420024370600202750ustar00rootroot00000000000000/* * 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; /* 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_common_strstrip(split[0]); idx = fu_common_strtoull(idxstr); if (idx > 64) { g_debug("unexpected index, skipping: %s", idxstr); return; } /* parse hash */ str = g_string_new(split[1]); fu_common_string_replace(str, " ", ""); 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; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_tpm_v1_device_parent_class)->probe(device, error)) return FALSE; /* 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.7.5/plugins/tpm/fu-tpm-v1-device.h000066400000000000000000000005671420024370600203020ustar00rootroot00000000000000/* * 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.7.5/plugins/tpm/fu-tpm-v2-device.c000066400000000000000000000242431420024370600202730ustar00rootroot00000000000000/* * 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; }; G_DEFINE_TYPE(FuTpmV2Device, fu_tpm_v2_device, FU_TYPE_TPM_DEVICE) static void Esys_Finalize_autoptr_cleanup(ESYS_CONTEXT *esys_context) { Esys_Finalize(&esys_context); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup) static gboolean fu_tpm_v2_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_tpm_v2_device_parent_class)->probe(device, error)) return FALSE; return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "tpm", error); } static gboolean fu_tpm_v2_device_get_uint32(ESYS_CONTEXT *ctx, 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(ctx, 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(ESYS_CONTEXT *ctx, guint32 query, GError **error) { guint32 val_be = 0; guint32 val; gchar result[5] = {'\0'}; /* return four bytes */ if (!fu_tpm_v2_device_get_uint32(ctx, 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_common_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 "AMD"; 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, ESYS_CONTEXT *ctx, 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(ctx, 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(ctx, 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_setup(FuDevice *device, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); FwupdVersionFormat verfmt; TSS2_RC rc; const gchar *tmp; guint32 tpm_type = 0; guint32 version1 = 0; guint32 version2 = 0; guint64 version_raw; g_autofree gchar *id1 = NULL; g_autofree gchar *id2 = NULL; g_autofree gchar *id3 = NULL; g_autofree gchar *id4 = NULL; 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 *version = NULL; g_autofree gchar *family = NULL; g_autoptr(ESYS_CONTEXT) ctx = NULL; /* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */ if (g_getenv("FWUPD_UEFI_VERBOSE") == NULL) g_setenv("TSS2_LOG", "esys+error,tcti+none", FALSE); /* setup TSS */ rc = Esys_Initialize(&ctx, 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; } rc = Esys_Startup(ctx, 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(ctx, 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(ctx, 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(ctx, 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(ctx, 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(ctx, TPM2_PT_VENDOR_STRING_2, error); model3 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_3, error); model4 = fu_tpm_v2_device_get_string(ctx, TPM2_PT_VENDOR_STRING_4, error); model = g_strjoin("", model1, model2, model3, model4, NULL); /* add GUIDs to daemon */ id1 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X", manufacturer, tpm_type); fu_device_add_instance_id(device, id1); id2 = g_strdup_printf("TPM\\VEN_%s&MOD_%s", manufacturer, model); fu_device_add_instance_id(device, id2); id3 = g_strdup_printf("TPM\\VEN_%s&DEV_%04X&VER_%s", manufacturer, tpm_type, family); fu_device_add_instance_id(device, id3); id4 = g_strdup_printf("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, family); fu_device_add_instance_id(device, id4); /* 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(ctx, TPM2_PT_FIRMWARE_VERSION_1, &version1, error)) return FALSE; if (!fu_tpm_v2_device_get_uint32(ctx, TPM2_PT_FIRMWARE_VERSION_2, &version2, error)) return FALSE; version_raw = ((guint64)version1) << 32 | ((guint64)version2); fu_device_set_version_raw(device, version_raw); /* this has to be done after _add_instance_id() sets the quirks */ verfmt = fu_device_get_version_format(device); version = fu_common_version_from_uint64(version_raw, verfmt); fu_device_set_version_format(device, verfmt); fu_device_set_version(device, version); /* get PCRs */ return fu_tpm_v2_device_setup_pcrs(self, ctx, error); } static void fu_tpm_v2_device_init(FuTpmV2Device *self) { } 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; } 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.7.5/plugins/tpm/fu-tpm-v2-device.h000066400000000000000000000005671420024370600203030ustar00rootroot00000000000000/* * 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.7.5/plugins/tpm/fuzzing/000077500000000000000000000000001420024370600166245ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/fuzzing/v2.bin000066400000000000000000000000201420024370600176350ustar00rootroot00000000000000Spec ID Event03 fwupd-1.7.5/plugins/tpm/meson.build000066400000000000000000000035021420024370600172720ustar00rootroot00000000000000if get_option('hsi') and get_option('plugin_tpm') if not get_option('gudev') error('gudev is required for tpm') endif cargs = ['-DG_LOG_DOMAIN="FuPluginTpm"'] install_data([ 'tpm.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) plugin_tpm = shared_module('fu_plugin_tpm', fu_hash, sources : [ 'fu-plugin-tpm.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupdplugin, fwupd, ], c_args : cargs, dependencies : [ plugin_deps, tpm2tss, ], ) 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', fu_hash, sources : [ 'fu-self-test.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('tpm-self-test', e, env: env) endif executable( 'fwupdtpmevlog', fu_hash, sources : [ 'fu-tpm-eventlog.c', 'fu-tpm-eventlog-common.c', 'fu-tpm-eventlog-parser.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, tpm2tss, ], link_with : [ fwupd, fwupdplugin, ], ) endif fwupd-1.7.5/plugins/tpm/tests/000077500000000000000000000000001420024370600162725ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/tests/empty_pcr/000077500000000000000000000000001420024370600202745ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/000077500000000000000000000000001420024370600211545ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/active000066400000000000000000000000021420024370600223420ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/caps000066400000000000000000000001011420024370600220150ustar00rootroot00000000000000Manufacturer: 0x49465800 TCG version: 1.2 Firmware version: 6.40 fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/enabled000066400000000000000000000000021420024370600224610ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/owned000066400000000000000000000000021420024370600222030ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/empty_pcr/tpm0/pcrs000066400000000000000000000031701420024370600220470ustar00rootroot00000000000000PCR-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.7.5/plugins/tpm/tests/tpm0/000077500000000000000000000000001420024370600171525ustar00rootroot00000000000000fwupd-1.7.5/plugins/tpm/tests/tpm0/active000066400000000000000000000000021420024370600203400ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/tpm0/caps000066400000000000000000000001011420024370600200130ustar00rootroot00000000000000Manufacturer: 0x49465800 TCG version: 1.2 Firmware version: 6.40 fwupd-1.7.5/plugins/tpm/tests/tpm0/enabled000066400000000000000000000000021420024370600204570ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/tpm0/owned000066400000000000000000000000021420024370600202010ustar00rootroot000000000000001 fwupd-1.7.5/plugins/tpm/tests/tpm0/pcrs000066400000000000000000000031701420024370600200450ustar00rootroot00000000000000PCR-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.7.5/plugins/tpm/tpm.quirk000066400000000000000000000000231420024370600170000ustar00rootroot00000000000000[TPM] Plugin = tpm fwupd-1.7.5/plugins/uefi-capsule/000077500000000000000000000000001420024370600167125ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/README.md000066400000000000000000000114141420024370600201720ustar00rootroot00000000000000# 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=false` 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 ## 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. 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` GUID 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. ## UEFI Unlock Support On some Dell systems it is possible to turn on and off UEFI capsule support from within the BIOS. This functionality can also be adjusted from within the OS by fwupd. This requires compiling with libsmbios support. When fwupd has been compiled with this support you will be able to enable UEFI support on the device by using the `unlock` command. ## 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 *OverrideESPMountPoint* in `/etc/fwupd/uefi_capsule.conf`. Setting an invalid directory will disable the fwupd plugin. ## 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` fwupd-1.7.5/plugins/uefi-capsule/fu-plugin-uefi-capsule.c000066400000000000000000000634571420024370600233630ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-uefi-backend.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" #include "fu-uefi-grub-device.h" struct FuPluginData { FuUefiBgrt *bgrt; FuVolume *esp; FuBackend *backend; }; static void fu_plugin_uefi_capsule_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); data->backend = fu_uefi_backend_new(ctx); data->bgrt = fu_uefi_bgrt_new(); 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 */ } static void fu_plugin_uefi_capsule_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->esp != NULL) g_object_unref(data->esp); g_object_unref(data->backend); g_object_unref(data->bgrt); } static gboolean fu_plugin_uefi_capsule_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 void fu_plugin_uefi_capsule_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { #ifdef HAVE_HSI g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); fu_security_attrs_append(attrs, attr); /* SB not available or disabled */ if (!fu_efivar_secure_boot_enabled_full(&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; } fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); #endif } static GBytes * fu_plugin_uefi_capsule_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_common_get_path(FU_PATH_KIND_DATADIR_PKG); filename_archive = g_build_filename(datadir_pkg, "uefi-capsule-ux.tar.xz", NULL); blob_archive = fu_common_get_contents_bytes(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++) { GBytes *blob_tmp; g_autofree gchar *fn = 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_plugin_uefi_capsule_write_splash_data(FuPlugin *plugin, FuDevice *device, GBytes *blob, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); guint32 screen_x, screen_y; gsize buf_size = g_bytes_get_size(blob); gssize size; guint32 height, width; guint8 csum = 0; efi_ux_capsule_header_t header = {0}; efi_capsule_header_t capsule_header = {.flags = EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET, .guid = {0x0}, .header_size = sizeof(efi_capsule_header_t), .capsule_image_size = 0}; g_autofree gchar *esp_path = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *basename = NULL; 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 predicatable filename */ esp_path = fu_volume_get_mount_point(data->esp); directory = fu_uefi_get_esp_path_for_os(device, esp_path); basename = g_strdup_printf("fwupd-%s.cap", FU_EFIVAR_GUID_UX_CAPSULE); fn = g_build_filename(directory, "fw", basename, NULL); if (!fu_common_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; if (!fwupd_guid_from_string(FU_EFIVAR_GUID_UX_CAPSULE, &capsule_header.guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; capsule_header.capsule_image_size = g_bytes_get_size(blob) + sizeof(efi_capsule_header_t) + sizeof(efi_ux_capsule_header_t); header.version = 1; header.image_type = 0; header.reserved = 0; header.x_offset = (screen_x / 2) - (width / 2); if (screen_y == fu_uefi_bgrt_get_height(data->bgrt)) { header.y_offset = (gdouble)screen_y * 0.8f; } else { header.y_offset = fu_uefi_bgrt_get_yoffset(data->bgrt) + fu_uefi_bgrt_get_height(data->bgrt); }; /* header, payload and image has to add to zero */ csum += fu_common_sum8((guint8 *)&capsule_header, sizeof(capsule_header)); csum += fu_common_sum8((guint8 *)&header, sizeof(header)); csum += fu_common_sum8_bytes(blob); header.checksum = 0x100 - csum; /* write capsule file */ size = g_output_stream_write(ostream, &capsule_header, capsule_header.header_size, NULL, error); if (size < 0) return FALSE; size = g_output_stream_write(ostream, &header, sizeof(header), 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), fn, "fwupd-ux-capsule", FU_EFIVAR_GUID_UX_CAPSULE, error); } static gboolean fu_plugin_uefi_capsule_update_splash(FuPlugin *plugin, FuDevice *device, GError **error) { FuPluginData *data = fu_plugin_get_data(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_debug("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(data->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_plugin_uefi_capsule_get_splash_data(sizes[best_idx].width, sizes[best_idx].height, error); if (image_bmp == NULL) return FALSE; /* perform the upload */ return fu_plugin_uefi_capsule_write_splash_data(plugin, device, image_bmp, error); } static gboolean fu_plugin_uefi_capsule_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_plugin_uefi_capsule_update_splash(plugin, device, &error_splash)) { g_debug("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_plugin_uefi_capsule_load_config(FuPlugin *plugin, FuDevice *device) { gboolean disable_shim; gboolean fallback_removable_path; guint64 sz_reqd = FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE; g_autofree gchar *require_esp_free_space = NULL; /* parse free space needed for ESP */ require_esp_free_space = fu_plugin_get_config_value(plugin, "RequireESPFreeSpace"); if (require_esp_free_space != NULL) sz_reqd = fu_common_strtoull(require_esp_free_space); fu_device_set_metadata_integer(device, "RequireESPFreeSpace", sz_reqd); /* shim used for SB or not? */ disable_shim = fu_plugin_get_config_value_boolean(plugin, "DisableShimForSecureBoot"); if (!disable_shim) fu_device_add_private_flag(device, FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB); /* check if using UEFI removable path */ fallback_removable_path = fu_plugin_get_config_value_boolean(plugin, "FallbacktoRemovablePath"); if (fallback_removable_path) fu_device_add_private_flag(device, FU_UEFI_DEVICE_FLAG_FALLBACK_TO_REMOVABLE_PATH); } static void fu_plugin_uefi_capsule_register_proxy_device(FuPlugin *plugin, FuDevice *device) { FuPluginData *data = fu_plugin_get_data(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(data->backend), device); fu_plugin_uefi_capsule_load_config(plugin, FU_DEVICE(dev)); if (data->esp == NULL) data->esp = fu_common_get_esp_default(&error_local); if (data->esp == NULL) { fu_device_inhibit(device, "no-esp", error_local->message); } else { fu_uefi_device_set_esp(dev, data->esp); fu_device_uninhibit(device, "no-esp"); } fu_plugin_device_add(plugin, FU_DEVICE(dev)); } static void fu_plugin_uefi_capsule_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_plugin_uefi_capsule_register_proxy_device(plugin, device); } } static const gchar * fu_plugin_uefi_capsule_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_plugin_uefi_capsule_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_plugin_uefi_capsule_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_plugin_uefi_capsule_coldplug_device(FuPlugin *plugin, FuUefiDevice *dev, GError **error) { 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); /* 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_plugin_uefi_capsule_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); } } /* success */ return TRUE; } static void fu_plugin_uefi_capsule_test_secure_boot(FuPlugin *plugin) { const gchar *result_str = "Disabled"; if (fu_efivar_secure_boot_enabled()) result_str = "Enabled"; fu_plugin_add_report_metadata(plugin, "SecureBoot", result_str); } static gboolean fu_plugin_uefi_capsule_startup(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_get_data(plugin); guint64 nvram_total; g_autofree gchar *esp_path = NULL; g_autofree gchar *nvram_total_str = NULL; g_autoptr(GError) error_local = 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(data->backend), FU_TYPE_UEFI_GRUB_DEVICE); } /* check we can use this backend */ if (!fu_backend_setup(data->backend, &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); /* override the default ESP path */ esp_path = fu_plugin_get_config_value(plugin, "OverrideESPMountPoint"); if (esp_path != NULL) { data->esp = fu_common_get_esp_for_path(esp_path, error); if (data->esp == NULL) { g_prefix_error(error, "invalid OverrideESPMountPoint=%s " "specified in config: ", esp_path); return FALSE; } } /* 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_plugin_uefi_capsule_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_plugin_uefi_capsule_check_cod_support(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_common_read_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; } /* success */ return TRUE; } static gboolean fu_plugin_uefi_capsule_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_get_data(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; if (data->esp == NULL) { data->esp = fu_common_get_esp_default(&error_udisks2); if (data->esp == NULL) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING); g_warning("cannot find default ESP: %s", error_udisks2->message); } } /* firmware may lie */ if (!fu_plugin_get_config_value_boolean(plugin, "DisableCapsuleUpdateOnDisk")) { g_autoptr(GError) error_cod = NULL; if (!fu_plugin_uefi_capsule_check_cod_support(&error_cod)) { g_debug("not using CapsuleOnDisk support: %s", error_cod->message); } else { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(data->backend), FU_TYPE_UEFI_COD_DEVICE); } } /* 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; } /* add each device */ if (!fu_backend_coldplug(data->backend, error)) return FALSE; devices = fu_backend_get_devices(data->backend); for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GError) error_device = NULL; fu_device_set_context(FU_DEVICE(dev), ctx); if (data->esp != NULL) fu_uefi_device_set_esp(dev, data->esp); if (!fu_plugin_uefi_capsule_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_plugin_uefi_capsule_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)); } /* for debugging problems later */ fu_plugin_uefi_capsule_test_secure_boot(plugin); if (!fu_uefi_bgrt_setup(data->bgrt, &error_local)) g_debug("BGRT setup failed: %s", error_local->message); str = fu_uefi_bgrt_get_supported(data->bgrt) ? "Enabled" : "Disabled"; g_debug("UX Capsule support : %s", str); fu_plugin_add_report_metadata(plugin, "UEFIUXCapsule", str); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_uefi_capsule_init; vfuncs->destroy = fu_plugin_uefi_capsule_destroy; vfuncs->clear_results = fu_plugin_uefi_capsule_clear_results; vfuncs->add_security_attrs = fu_plugin_uefi_capsule_add_security_attrs; vfuncs->device_registered = fu_plugin_uefi_capsule_device_registered; vfuncs->startup = fu_plugin_uefi_capsule_startup; vfuncs->unlock = fu_plugin_uefi_capsule_unlock; vfuncs->coldplug = fu_plugin_uefi_capsule_coldplug; vfuncs->write_firmware = fu_plugin_uefi_capsule_write_firmware; } fwupd-1.7.5/plugins/uefi-capsule/fu-self-test.c000066400000000000000000000243031420024370600213760ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-context-private.h" #include "fu-ucs2.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_ucs2_func(void) { g_autofree guint16 *str1 = NULL; g_autofree gchar *str2 = NULL; str1 = fu_uft8_to_ucs2("hw!", -1); g_assert_cmpint(fu_ucs2_strlen(str1, -1), ==, 3); str2 = fu_ucs2_to_uft8(str1, -1); g_assert_cmpstr("hw!", ==, str2); } 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: battery level is too low"); g_assert_cmpint(fu_uefi_device_get_status(FU_UEFI_DEVICE(dev)), ==, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT); } static void fu_uefi_device_func(void) { /* check enums all converted */ for (guint i = 0; i < FU_UEFI_DEVICE_STATUS_LAST; i++) g_assert_nonnull(fu_uefi_device_status_to_string(i)); } 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(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, &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(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, &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_uefi_update_info_get_version(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; g_test_init(&argc, &argv, NULL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); g_setenv("FWUPD_SYSFSDRIVERDIR", testdatadir, TRUE); 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); g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/uefi/ucs2", fu_uefi_ucs2_func); 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/device", fu_uefi_device_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.7.5/plugins/uefi-capsule/fu-ucs2.c000066400000000000000000000032101420024370600203360ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ucs2.h" #define ev_bits(val, mask, shift) (((val) & ((mask) << (shift))) >> (shift)) gchar * fu_ucs2_to_uft8(const guint16 *str, gssize max) { gssize i, j; gchar *ret; if (max < 0) max = fu_ucs2_strlen(str, max); ret = g_malloc0(max * 3 + 1); /* would be s/3/6 if this were UCS-4 */ for (i = 0, j = 0; i < max && str[i]; i++, j++) { if (str[i] <= 0x7f) { ret[j] = str[i]; } else if (str[i] <= 0x7ff) { ret[j++] = 0xc0 | ev_bits(str[i], 0x1f, 6); ret[j] = 0x80 | ev_bits(str[i], 0x3f, 0); } else { ret[j++] = 0xe0 | ev_bits(str[i], 0xf, 12); ret[j++] = 0x80 | ev_bits(str[i], 0x3f, 6); ret[j] = 0x80 | ev_bits(str[i], 0x3f, 0); } } return ret; } guint16 * fu_uft8_to_ucs2(const gchar *str, gssize max) { gssize i, j; guint16 *ret = g_new0(guint16, g_utf8_strlen(str, max) + 1); for (i = 0, j = 0; i < (max >= 0 ? max : i + 1) && str[i] != '\0'; j++) { guint32 val = 0; if ((str[i] & 0xe0) == 0xe0 && !(str[i] & 0x10)) { val = ((str[i + 0] & 0x0f) << 10) | ((str[i + 1] & 0x3f) << 6) | ((str[i + 2] & 0x3f) << 0); i += 3; } else if ((str[i] & 0xc0) == 0xc0 && !(str[i] & 0x20)) { val = ((str[i + 0] & 0x1f) << 6) | ((str[i + 1] & 0x3f) << 0); i += 2; } else { val = str[i] & 0x7f; i += 1; } ret[j] = val; } ret[j] = L'\0'; return ret; } gsize fu_ucs2_strlen(const guint16 *str, gssize limit) { gssize i; for (i = 0; i < (limit >= 0 ? limit : i + 1) && str[i] != L'\0'; i++) ; return i; } fwupd-1.7.5/plugins/uefi-capsule/fu-ucs2.h000066400000000000000000000005551420024370600203540ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gsize fu_ucs2_strlen(const guint16 *str, gssize limit); guint16 * fu_uft8_to_ucs2(const gchar *str, gssize max); gchar * fu_ucs2_to_uft8(const guint16 *str, gssize max); fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-backend-freebsd.c000066400000000000000000000112571420024370600234210ustar00rootroot00000000000000/* * 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 #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, struct efi_esrt_entry_v1 *entry, guint64 idx, GError **error) { g_autoptr(FuUefiDevice) dev = NULL; g_autofree gchar *fw_class = NULL; g_autofree gchar *phys_id = 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 */ phys_id = g_strdup_printf("ESRT/%u", (guint)idx); fu_device_set_physical_id(FU_DEVICE(dev), phys_id); 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_common_vercmp_full(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); 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("/dev/efi", O_RDONLY, 0); if (efi_fd < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Cannot open /dev/efi"); 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, &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.7.5/plugins/uefi-capsule/fu-uefi-backend-freebsd.h000066400000000000000000000006051420024370600234210ustar00rootroot00000000000000/* * 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.7.5/plugins/uefi-capsule/fu-uefi-backend-linux.c000066400000000000000000000155641420024370600231530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-uefi-backend-linux.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" #include "fu-uefi-nvram-device.h" #ifndef HAVE_GIO_2_55_0 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free) #pragma clang diagnostic pop #endif 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 *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_physical_id(FU_DEVICE(dev), path); return g_steal_pointer(&dev); } static gboolean fu_uefi_backend_linux_check_efivarfs(FuUefiBackendLinux *self, GError **error) { g_autofree gchar *sysfsfwdir = fu_common_get_path(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, 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_common_get_path(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, 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) { const guint8 *data; gsize sz; g_autoptr(GBytes) bios_information = fu_context_get_smbios_data(ctx, 0); if (bios_information == 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; } data = g_bytes_get_data(bios_information, &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, 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_common_get_path(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.7.5/plugins/uefi-capsule/fu-uefi-backend-linux.h000066400000000000000000000005731420024370600231520ustar00rootroot00000000000000/* * 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.7.5/plugins/uefi-capsule/fu-uefi-backend.c000066400000000000000000000041141420024370600220030ustar00rootroot00000000000000/* * 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; } /* 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) { } fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-backend.h000066400000000000000000000013651420024370600220150ustar00rootroot00000000000000/* * 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.7.5/plugins/uefi-capsule/fu-uefi-bgrt.c000066400000000000000000000055611420024370600213610ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_path(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.7.5/plugins/uefi-capsule/fu-uefi-bgrt.h000066400000000000000000000011361420024370600213600ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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.7.5/plugins/uefi-capsule/fu-uefi-bootmgr.c000066400000000000000000000260521420024370600220720ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-ucs2.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" /* XXX PJFIX: this should be in efiboot-loadopt.h in efivar */ #define LOAD_OPTION_ACTIVE 0x00000001 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); return fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder", (guint8 *)new_boot_order, boot_order_size, attr, error); } 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); efi_load_option *loadopt; gsize var_data_size = 0; guint16 entry; g_autofree guint8 *var_data_tmp = NULL; g_autoptr(GError) error_local = NULL; /* not BootXXXX */ entry = fu_uefi_bootmgr_parse_name(name); if (entry == G_MAXUINT16) continue; /* parse key */ if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, name, &var_data_tmp, &var_data_size, NULL, &error_local)) { g_debug("failed to get data for name %s: %s", name, error_local->message); continue; } loadopt = (efi_load_option *)var_data_tmp; if (!efi_loadopt_is_valid(loadopt, var_data_size)) { g_debug("%s -> load option was invalid", name); continue; } desc = (const gchar *)efi_loadopt_desc(loadopt, var_data_size); 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_dp(const guint8 *dp_buf, guint8 *opt, gssize opt_size, GError **error) { const gchar *desc = NULL; const gchar *name = NULL; efi_load_option *loadopt = NULL; gsize var_data_size = 0; guint32 attr; guint16 boot_next = G_MAXUINT16; g_autofree guint8 *var_data = NULL; g_autofree guint8 *set_entries = g_malloc0(G_MAXUINT16); 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++) { guint16 entry = 0; g_autofree guint8 *var_data_tmp = NULL; g_autoptr(GError) error_local = NULL; /* 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; if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, name, &var_data_tmp, &var_data_size, &attr, &error_local)) { g_debug("failed to get data for name %s: %s", name, error_local->message); continue; } loadopt = (efi_load_option *)var_data_tmp; if (!efi_loadopt_is_valid(loadopt, var_data_size)) { g_debug("%s -> load option was invalid", name); continue; } desc = (const gchar *)efi_loadopt_desc(loadopt, var_data_size); 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; } var_data = g_steal_pointer(&var_data_tmp); boot_next = entry; break; } /* already exists */ if (var_data != NULL) { /* is different than before */ if (var_data_size != (gsize)opt_size || memcmp(var_data, opt, opt_size) != 0) { g_debug("%s -> '%s' : updating existing boot entry", name, desc); efi_loadopt_attr_set(loadopt, LOAD_OPTION_ACTIVE); if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, name, opt, opt_size, attr, error)) { g_prefix_error(error, "could not set boot variable active: "); return FALSE; } } else { g_debug("%s -> %s : re-using existing boot entry", name, desc); } /* 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(FU_EFIVAR_GUID_EFI_GLOBAL, boot_next_name, opt, opt_size, 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 (!fu_uefi_bootmgr_add_to_boot_order(boot_next, error)) return FALSE; /* set the boot next */ if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext", (guint8 *)&boot_next, 2, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "could not set BootNext(%" G_GUINT16_FORMAT "): ", boot_next); return FALSE; } return TRUE; } gboolean fu_uefi_bootmgr_bootnext(FuDevice *device, const gchar *esp_path, const gchar *description, FuUefiBootmgrFlags flags, GError **error) { const gchar *filepath; gboolean use_fwup_path = TRUE; gboolean secure_boot = FALSE; gsize loader_sz = 0; gssize opt_size = 0; gssize sz, dp_size = 0; guint32 attributes = LOAD_OPTION_ACTIVE; g_autofree guint16 *loader_str = NULL; g_autofree gchar *label = NULL; g_autofree gchar *shim_app = NULL; g_autofree gchar *shim_cpy = NULL; g_autofree guint8 *dp_buf = NULL; g_autofree guint8 *opt = NULL; g_autofree gchar *source_app = NULL; g_autofree gchar *target_app = NULL; /* 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(error); if (source_app == NULL) return FALSE; /* test if we should use shim */ secure_boot = fu_efivar_secure_boot_enabled(); if (secure_boot) { /* test to make sure shim is there if we need it */ shim_app = fu_uefi_get_esp_app_path(device, esp_path, "shim", error); if (shim_app == NULL) return FALSE; /* try to fallback to use UEFI removable path if the shim path doesn't exist */ if (!g_file_test(shim_app, G_FILE_TEST_EXISTS)) { if (fu_device_has_private_flag( device, FU_UEFI_DEVICE_FLAG_FALLBACK_TO_REMOVABLE_PATH)) { shim_app = fu_uefi_get_fallback_app_path(device, esp_path, "boot", error); if (shim_app == NULL) return FALSE; } } if (g_file_test(shim_app, G_FILE_TEST_EXISTS)) { /* 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(device, esp_path, "shimfwupd", error); if (shim_cpy == NULL) return FALSE; if (!fu_uefi_cmp_asset(shim_app, shim_cpy)) { if (!fu_uefi_copy_asset(shim_app, 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(device, esp_path, "fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_cmp_asset(source_app, target_app)) { if (!fu_uefi_copy_asset(source_app, target_app, error)) return FALSE; } /* no shim, so use this directly */ if (use_fwup_path) filepath = target_app; /* generate device path for target */ sz = efi_generate_file_device_path(dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (sz < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } /* add the fwupdx64.efi ESP path as the shim loadopt data */ dp_size = sz; dp_buf = g_malloc0(dp_size); if (!use_fwup_path) { g_autofree gchar *fwup_fs_basename = g_path_get_basename(target_app); g_autofree gchar *fwup_esp_path = g_strdup_printf("\\%s", fwup_fs_basename); loader_str = fu_uft8_to_ucs2(fwup_esp_path, -1); loader_sz = fu_ucs2_strlen(loader_str, -1) * 2; if (loader_sz) loader_sz += 2; } sz = efi_generate_file_device_path(dp_buf, dp_size, filepath, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (sz != dp_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_generate_file_device_path(%s) failed", filepath); return FALSE; } label = g_strdup(description); sz = efi_loadopt_create(opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "efi_loadopt_create(%s) failed", label); return FALSE; } opt = g_malloc0(sz); opt_size = sz; sz = efi_loadopt_create(opt, opt_size, attributes, (efidp)dp_buf, dp_size, (guint8 *)label, (guint8 *)loader_str, loader_sz); if (sz != opt_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "loadopt size was unreasonable."); return FALSE; } return fu_uefi_setup_bootnext_with_dp(dp_buf, opt, opt_size, error); } fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-bootmgr.h000066400000000000000000000012111420024370600220650ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #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_LAST } FuUefiBootmgrFlags; gboolean fu_uefi_bootmgr_verify_fwupd(GError **error); gboolean fu_uefi_bootmgr_bootnext(FuDevice *device, const gchar *esp_path, const gchar *description, FuUefiBootmgrFlags flags, GError **error); fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-cod-device.c000066400000000000000000000147101420024370600224210ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_read_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_common_read_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) { gsize bufsz = 0; g_autofree guint8 *buf = NULL; g_autofree gchar *str = NULL; gunichar2 buf16[VARIABLE_IDX_SIZE] = {0x0}; if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_CAPSULE_REPORT, name, &buf, &bufsz, NULL, error)) return FALSE; if (!fu_memcpy_safe((guint8 *)buf16, sizeof(buf16), 0x0, /* dst */ buf, bufsz, 0x0, /* src */ sizeof(buf16), error)) return FALSE; /* parse the value */ str = g_utf16_to_utf8(buf16, VARIABLE_IDX_SIZE, NULL, NULL, 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 (value != NULL) *value = fu_common_strtoull(str + strlen("Capsule")); 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 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 *basename = NULL; g_autofree gchar *cod_path = NULL; g_autofree gchar *esp_path = fu_uefi_device_get_esp_path(self); 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; basename = g_strdup_printf("fwupd-%s.cap", fu_uefi_device_get_guid(self)); cod_path = g_build_filename(esp_path, "EFI", "UpdateCapsule", basename, NULL); if (!fu_common_mkdir_parent(cod_path, error)) return FALSE; if (!fu_common_set_contents_bytes(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; if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndications", &buf, &bufsz, NULL, error)) { g_prefix_error(error, "failed to read EFI variable: "); return FALSE; } if (!fu_common_read_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) { } 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.7.5/plugins/uefi-capsule/fu-uefi-cod-device.h000066400000000000000000000006221420024370600224230ustar00rootroot00000000000000/* * 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) struct _FuUefiCodDeviceClass { FuUefiDeviceClass parent_class; }; fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-common.c000066400000000000000000000175311420024370600217130ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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"}, #endif #if defined(__x86_64__) || defined(__i386__) || defined(__i686__) {32, "ia32"}, #endif {0, NULL} }; g_autofree gchar *sysfsfwdir = fu_common_get_path(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; } gchar * fu_uefi_get_fallback_app_path(FuDevice *device, const gchar *esp_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 = g_build_filename(esp_path, "EFI", "boot", NULL); return g_strdup_printf("%s/%s%s.efi", base, cmd, suffix); } gchar * fu_uefi_get_esp_app_path(FuDevice *device, const gchar *esp_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(device, esp_path); return g_strdup_printf("%s/%s%s.efi", base, cmd, suffix); } gchar * fu_uefi_get_built_app_path(GError **error) { const gchar *extension = ""; const gchar *suffix; g_autofree gchar *source_path = NULL; g_autofree gchar *prefix = NULL; if (fu_efivar_secure_boot_enabled()) extension = ".signed"; suffix = fu_uefi_bootmgr_get_suffix(error); if (suffix == NULL) return NULL; prefix = fu_common_get_path(FU_PATH_KIND_EFIAPPDIR); source_path = g_strdup_printf("%s/fwupd%s.efi%s", prefix, suffix, extension); if (!g_file_test(source_path, G_FILE_TEST_EXISTS)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s cannot be found", source_path); return NULL; } return g_steal_pointer(&source_path); } 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_common_get_path(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_common_read_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_common_read_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_common_read_uint32_safe(buf, bufsz, 18, width, G_LITTLE_ENDIAN, error)) return FALSE; } if (height != NULL) { if (!fu_common_read_uint32_safe(buf, bufsz, 22, height, G_LITTLE_ENDIAN, error)) return FALSE; } return TRUE; } gchar * fu_uefi_get_esp_path_for_os(FuDevice *device, const gchar *base) { #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(base, "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(base, "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(base, "EFI", EFI_OS_DIR, NULL); #endif } guint64 fu_uefi_read_file_as_uint64(const gchar *path, const gchar *attr_name) { g_autofree gchar *data = NULL; g_autofree gchar *fn = g_build_filename(path, attr_name, NULL); if (!g_file_get_contents(fn, &data, NULL, NULL)) return 0x0; return fu_common_strtoull(data); } gboolean fu_uefi_cmp_asset(const gchar *source, const gchar *target) { 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; /* nothing in target yet */ if (!g_file_test(target, G_FILE_TEST_EXISTS)) return FALSE; /* test if the file needs to be updated */ if (!g_file_get_contents(source, &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, &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_copy_asset(const gchar *source, const gchar *target, GError **error) { g_autoptr(GFile) source_file = g_file_new_for_path(source); g_autoptr(GFile) target_file = g_file_new_for_path(target); 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, target); return FALSE; } return TRUE; } fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-common.h000066400000000000000000000044341420024370600217160ustar00rootroot00000000000000/* * 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 #include #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 #ifndef HAVE_EFI_TIME_T typedef struct __attribute__((__packed__)) { guint16 year; guint8 month; guint8 day; guint8 hour; guint8 minute; guint8 second; guint8 pad1; guint32 nanosecond; guint16 timezone; guint8 daylight; guint8 pad2; } efi_time_t; #endif typedef struct __attribute__((__packed__)) { fwupd_guid_t guid; guint32 header_size; guint32 flags; guint32 capsule_image_size; } efi_capsule_header_t; typedef struct __attribute__((__packed__)) { guint8 version; guint8 checksum; guint8 image_type; guint8 reserved; guint32 mode; guint32 x_offset; guint32 y_offset; } efi_ux_capsule_header_t; typedef struct __attribute__((__packed__)) { guint32 update_info_version; fwupd_guid_t guid; guint32 capsule_flags; guint64 hw_inst; efi_time_t time_attempted; guint32 status; } efi_update_info_t; /* the biggest size SPI part currently seen */ #define FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE (32 * 1024 * 1024) gchar * fu_uefi_get_fallback_app_path(FuDevice *device, const gchar *esp_path, const gchar *cmd, GError **error); gchar * fu_uefi_get_esp_app_path(FuDevice *device, const gchar *esp_path, const gchar *cmd, GError **error); gchar * fu_uefi_get_built_app_path(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(FuDevice *device, const gchar *esp_path); guint64 fu_uefi_read_file_as_uint64(const gchar *path, const gchar *attr_name); gboolean fu_uefi_cmp_asset(const gchar *source, const gchar *target); gboolean fu_uefi_copy_asset(const gchar *source, const gchar *target, GError **error); fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-device.c000066400000000000000000000642701420024370600216640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-devpath.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; gboolean requires_header; } FuUefiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuUefiDevice, fu_uefi_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_uefi_device_get_instance_private(o)) 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); } const gchar * fu_uefi_device_kind_to_string(FuUefiDeviceKind kind) { if (kind == FU_UEFI_DEVICE_KIND_UNKNOWN) return "unknown"; if (kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) return "system-firmware"; if (kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return "device-firmware"; if (kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER) return "uefi-driver"; if (kind == FU_UEFI_DEVICE_KIND_FMP) return "fmp"; if (kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) return "dell-tpm-firmware"; return NULL; } FuUefiDeviceKind fu_uefi_device_kind_from_string(const gchar *kind) { if (g_strcmp0(kind, "system-firmware") == 0) return FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE; if (g_strcmp0(kind, "device-firmware") == 0) return FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE; if (g_strcmp0(kind, "uefi-driver") == 0) return FU_UEFI_DEVICE_KIND_UEFI_DRIVER; if (g_strcmp0(kind, "fmp") == 0) return FU_UEFI_DEVICE_KIND_FMP; if (g_strcmp0(kind, "dell-tpm-firmware") == 0) return FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE; return FU_UEFI_DEVICE_KIND_UNKNOWN; } const gchar * fu_uefi_device_status_to_string(FuUefiDeviceStatus status) { if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) return "success"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL) return "unsuccessful"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INSUFFICIENT_RESOURCES) return "insufficient resources"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INCORRECT_VERSION) return "incorrect version"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_INVALID_FORMAT) return "invalid firmware format"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_AUTH_ERROR) return "authentication signing error"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC) return "AC power required"; if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT) return "battery level is too low"; return NULL; } 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_common_string_append_kv(str, idt, "Kind", fu_uefi_device_kind_to_string(priv->kind)); fu_common_string_append_kv(str, idt, "FwClass", priv->fw_class); fu_common_string_append_kx(str, idt, "CapsuleFlags", priv->capsule_flags); fu_common_string_append_kx(str, idt, "FwVersion", priv->fw_version); fu_common_string_append_kx(str, idt, "FwVersionLowest", priv->fw_version_lowest); fu_common_string_append_kv(str, idt, "LastAttemptStatus", fu_uefi_device_status_to_string(priv->last_attempt_status)); fu_common_string_append_kx(str, idt, "LastAttemptVersion", priv->last_attempt_version); if (priv->esp != NULL) { fu_common_string_append_kv(str, idt, "EspId", fu_volume_get_id(priv->esp)); } fu_common_string_append_ku(str, idt, "RequireESPFreeSpace", fu_device_get_metadata_integer(device, "RequireESPFreeSpace")); } static void fu_uefi_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* 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 the ESP was mounted during installation */ g_hash_table_insert(metadata, g_strdup("EspPath"), fu_volume_get_mount_point(priv->esp)); } 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); } 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) { gsize datasz = 0; g_autofree gchar *varname = fu_uefi_device_build_varname(self); g_autofree guint8 *data = NULL; g_autoptr(FuUefiUpdateInfo) info = fu_uefi_update_info_new(); 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 */ if (!fu_efivar_get_data(FU_EFIVAR_GUID_FWUPDATE, varname, &data, &datasz, NULL, error)) return NULL; if (!fu_uefi_update_info_parse(info, data, datasz, error)) return NULL; return g_steal_pointer(&info); } gboolean fu_uefi_device_clear_status(FuUefiDevice *self, GError **error) { efi_update_info_t info; gsize datasz = 0; g_autofree gchar *varname = fu_uefi_device_build_varname(self); g_autofree guint8 *data = 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; if (datasz < sizeof(efi_update_info_t)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI variable is corrupt"); return FALSE; } /* just copy the efi_update_info_t, ignore devpath then save it back */ memcpy(&info, data, sizeof(info)); info.status = FU_UEFI_DEVICE_STATUS_SUCCESS; memcpy(data, &info, sizeof(info)); return 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); } static guint8 * fu_uefi_device_build_dp_buf(const gchar *path, gsize *bufsz, GError **error) { gssize req; gssize sz; g_autofree guint8 *dp_buf = NULL; g_autoptr(GPtrArray) dps = NULL; /* get the size of the path first */ req = efi_generate_file_device_path(NULL, 0, path, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (req < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to efi_generate_file_device_path(%s)", path); return NULL; } /* if we just have an end device path, it's not going to work */ if (req <= 4) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get valid device_path for (%s)", path); return NULL; } /* actually get the path this time */ dp_buf = g_malloc0(req); sz = efi_generate_file_device_path(dp_buf, req, path, EFIBOOT_OPTIONS_IGNORE_FS_ERROR | EFIBOOT_ABBREV_HD); if (sz < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to efi_generate_file_device_path(%s)", path); return NULL; } /* parse what we got back from efivar */ dps = fu_uefi_devpath_parse(dp_buf, (gsize)sz, FU_UEFI_DEVPATH_PARSE_FLAG_NONE, error); if (dps == NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "dp_buf", dp_buf, (gsize)sz); return NULL; } /* success */ if (bufsz != NULL) *bufsz = sz; return g_steal_pointer(&dp_buf); } GBytes * fu_uefi_device_fixup_firmware(FuUefiDevice *self, GBytes *fw, GError **error) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); gsize fw_length; const guint8 *data = g_bytes_get_data(fw, &fw_length); g_autofree gchar *guid_new = NULL; priv->missing_header = FALSE; /* GUID is the first 16 bytes */ if (fw_length < 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 *)data, 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); /* Type that doesn't require a header */ } else if (!priv->requires_header) { return g_bytes_ref(fw); /* Missing, add a header */ } else { guint header_size = getpagesize(); guint8 *new_data = g_malloc(fw_length + header_size); guint8 *capsule = new_data + header_size; fwupd_guid_t esrt_guid = {0x0}; efi_capsule_header_t *header = (efi_capsule_header_t *)new_data; g_warning("missing or invalid embedded capsule header"); priv->missing_header = TRUE; header->flags = priv->capsule_flags; header->header_size = header_size; header->capsule_image_size = fw_length + header_size; 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; } memcpy(&header->guid, &esrt_guid, sizeof(fwupd_guid_t)); memcpy(capsule, data, fw_length); return g_bytes_new_take(new_data, fw_length + header_size); } } gboolean fu_uefi_device_write_update_info(FuUefiDevice *self, const gchar *filename, const gchar *varname, const gchar *guid, GError **error) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); gsize datasz = 0; gsize dp_bufsz = 0; g_autofree guint8 *data = NULL; g_autofree guint8 *dp_buf = NULL; efi_update_info_t info = { .update_info_version = 0x7, .guid = {0x0}, .capsule_flags = priv->capsule_flags, .hw_inst = priv->fmp_hardware_instance, .time_attempted = {0x0}, .status = FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE, }; /* 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(filename, &dp_bufsz, error); if (dp_buf == NULL) return FALSE; /* save this header and body to the hardware */ if (!fwupd_guid_from_string(guid, &info.guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; datasz = sizeof(info) + dp_bufsz; data = g_malloc0(datasz); memcpy(data, &info, sizeof(info)); memcpy(data + sizeof(info), dp_buf, dp_bufsz); return 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); } static gboolean fu_uefi_device_check_esp_free(FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); guint64 sz_reqd = fu_device_get_metadata_integer(device, "RequireESPFreeSpace"); if (sz_reqd == G_MAXUINT) { g_debug("maximum size is not configured"); return TRUE; } return fu_volume_check_free_space(priv->esp, sz_reqd, error); } static gboolean fu_uefi_check_asset(FuDevice *device, GError **error) { g_autofree gchar *source_app = fu_uefi_get_built_app_path(error); if (source_app == NULL) { if (fu_efivar_secure_boot_enabled()) g_prefix_error(error, "missing signed bootloader for secure boot: "); return FALSE; } return TRUE; } static gboolean fu_uefi_device_cleanup_esp(FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *esp_path = fu_volume_get_mount_point(priv->esp); g_autofree gchar *pattern = NULL; g_autoptr(GPtrArray) files = NULL; /* in case we call capsule install twice before reboot */ if (fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext")) return TRUE; /* delete any files matching the glob in the ESP */ files = fu_common_get_files_recursive(esp_path, error); if (files == NULL) return FALSE; pattern = g_build_filename(esp_path, "EFI/*/fw/fwupd*.cap", NULL); for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); if (fu_common_fnmatch(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; } } /* delete any old variables */ if (!fu_efivar_delete_with_glob(FU_EFIVAR_GUID_FWUPDATE, "fwupd*-*", error)) return FALSE; return TRUE; } static gboolean fu_uefi_device_prepare(FuDevice *device, 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_device_cleanup_esp(device, error)) return FALSE; if (!fu_uefi_device_check_esp_free(device, error)) return FALSE; if (!fu_uefi_check_asset(device, error)) return FALSE; return TRUE; } static gboolean fu_uefi_device_cleanup(FuDevice *device, 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); FwupdVersionFormat version_format; g_autofree gchar *devid = NULL; g_autofree gchar *guid_strup = NULL; g_autofree gchar *version_lowest = NULL; g_autofree gchar *version = NULL; /* 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 */ version_format = fu_device_get_version_format(device); version = fu_common_version_from_uint32(priv->fw_version, version_format); fu_device_set_version_format(device, version_format); fu_device_set_version_raw(device, priv->fw_version); fu_device_set_version(device, version); if (priv->fw_version_lowest != 0) { version_lowest = fu_common_version_from_uint32(priv->fw_version_lowest, version_format); 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_DEVICE_FIRMWARE) { /* nothing better in the icon naming spec */ fu_device_add_icon(device, "audio-card"); } else { /* this is probably system firmware */ fu_device_add_icon(device, "computer"); fu_device_add_instance_id(device, "main-system-firmware"); } /* whether to create a missing header */ if (priv->kind == FU_UEFI_DEVICE_KIND_FMP || priv->kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) priv->requires_header = FALSE; else priv->requires_header = TRUE; /* Windows seems to be case insensitive, but for convenience we'll * match the upper case values typically specified in the .inf file */ guid_strup = g_ascii_strup(priv->fw_class, -1); devid = g_strdup_printf("UEFI\\RES_{%s}", guid_strup); fu_device_add_instance_id(device, devid); return TRUE; } static gboolean fu_uefi_device_get_results(FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* just set the update error */ fu_uefi_device_set_status(self, priv->last_attempt_status); return TRUE; } gchar * fu_uefi_device_get_esp_path(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); return fu_volume_get_mount_point(priv->esp); } 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_set_summary(FU_DEVICE(self), "UEFI ESRT device"); fu_device_add_protocol(FU_DEVICE(self), "org.uefi.capsule"); 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_FALLBACK_TO_REMOVABLE_PATH, "fallback-to-removable-path"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE, "no-rt-set-variable"); } 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 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 = 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; /** * 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.7.5/plugins/uefi-capsule/fu-uefi-device.h000066400000000000000000000074101420024370600216620ustar00rootroot00000000000000/* * 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; }; typedef enum { FU_UEFI_DEVICE_KIND_UNKNOWN, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE, FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE, FU_UEFI_DEVICE_KIND_UEFI_DRIVER, FU_UEFI_DEVICE_KIND_FMP, FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE, FU_UEFI_DEVICE_KIND_LAST } FuUefiDeviceKind; typedef enum { FU_UEFI_DEVICE_STATUS_SUCCESS = 0x00, FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL = 0x01, FU_UEFI_DEVICE_STATUS_ERROR_INSUFFICIENT_RESOURCES = 0x02, FU_UEFI_DEVICE_STATUS_ERROR_INCORRECT_VERSION = 0x03, FU_UEFI_DEVICE_STATUS_ERROR_INVALID_FORMAT = 0x04, FU_UEFI_DEVICE_STATUS_ERROR_AUTH_ERROR = 0x05, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC = 0x06, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT = 0x07, FU_UEFI_DEVICE_STATUS_LAST } FuUefiDeviceStatus; /** * 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_FALLBACK_TO_REMOVABLE_PATH: * * Try to fallback to use UEFI removable path if the shim path doesn't exist. */ #define FU_UEFI_DEVICE_FLAG_FALLBACK_TO_REMOVABLE_PATH (1 << 4) /** * 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) FuUefiDeviceKind fu_uefi_device_kind_from_string(const gchar *kind); 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); gchar * fu_uefi_device_get_esp_path(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); const gchar * fu_uefi_device_kind_to_string(FuUefiDeviceKind kind); const gchar * fu_uefi_device_status_to_string(FuUefiDeviceStatus status); FuUefiUpdateInfo * fu_uefi_device_load_update_info(FuUefiDevice *self, GError **error); gboolean fu_uefi_device_write_update_info(FuUefiDevice *self, const gchar *filename, 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); fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-devpath.c000066400000000000000000000067251420024370600220610ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #if defined(__linux__) #include #elif defined(__FreeBSD__) /* although against style, the order seems to matter on FreeBSD */ /* clang-format off */ #include #include /* clang-format on */ #endif #include #include "fu-uefi-devpath.h" #if defined(__FreeBSD__) #define EFIDP_END_TYPE 0x7f #define EFIDP_END_ENTIRE 0xff #endif typedef struct { guint8 type; guint8 subtype; GBytes *data; } FuUefiDevPath; static void fu_uefi_efi_dp_free(FuUefiDevPath *dp) { if (dp->data != NULL) g_bytes_unref(dp->data); g_free(dp); } GBytes * fu_uefi_devpath_find_data(GPtrArray *dps, guint8 type, guint8 subtype, GError **error) { for (guint i = 0; i < dps->len; i++) { FuUefiDevPath *dp = g_ptr_array_index(dps, i); if (dp->type == type && dp->subtype == subtype) return dp->data; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no DP with type 0x%02x and subtype 0x%02x", type, subtype); return NULL; } GPtrArray * fu_uefi_devpath_parse(const guint8 *buf, gsize sz, FuUefiDevpathParseFlags flags, GError **error) { guint16 offset = 0; g_autoptr(GPtrArray) dps = NULL; /* sanity check */ if (sz < sizeof(efidp_header)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "const_efidp is corrupt"); return NULL; } dps = g_ptr_array_new_with_free_func((GDestroyNotify)fu_uefi_efi_dp_free); while (1) { FuUefiDevPath *dp; const efidp_header *hdr = (efidp_header *)(buf + offset); guint16 hdr_length = GUINT16_FROM_LE(hdr->length); /* check if last entry */ g_debug("DP type:0x%02x subtype:0x%02x size:0x%04x", hdr->type, hdr->subtype, hdr->length); if (hdr->type == EFIDP_END_TYPE && hdr->subtype == EFIDP_END_ENTIRE) break; /* work around a bug in efi_va_generate_file_device_path_from_esp */ if (offset + sizeof(efidp_header) + hdr->length > sz) { hdr_length = 0; fu_common_dump_full(G_LOG_DOMAIN, "efidp", buf + offset, sz - offset, 32, FU_DUMP_FLAGS_SHOW_ADDRESSES); for (guint16 i = offset + 4; i <= sz - 4; i++) { if (memcmp(buf + i, "\x7f\xff\x04\x00", 4) == 0) { hdr_length = i - offset; g_debug("found END_ENTIRE at 0x%04x", (guint)(i - offset)); break; } } if (hdr_length == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid and no END_ENTIRE " "found, possibly data truncation?"); return NULL; } if ((flags & FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid, reported 0x%04x, maybe 0x%04x", hdr->length, hdr_length); return NULL; } g_debug("DP length invalid! Truncating from 0x%04x to 0x%04x", hdr->length, hdr_length); } /* add new DP */ dp = g_new0(FuUefiDevPath, 1); dp->type = hdr->type; dp->subtype = hdr->subtype; if (hdr_length > 0) dp->data = g_bytes_new(buf + offset + 4, hdr_length); g_ptr_array_add(dps, dp); /* advance to next DP */ offset += hdr_length; if (offset + sizeof(efidp_header) > sz) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "DP length invalid after fixing"); return NULL; } } return g_steal_pointer(&dps); } fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-devpath.h000066400000000000000000000007741420024370600220640ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_UEFI_DEVPATH_PARSE_FLAG_NONE = 0, FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR = 1 << 0, FU_UEFI_DEVPATH_PARSE_FLAG_LAST } FuUefiDevpathParseFlags; GPtrArray * fu_uefi_devpath_parse(const guint8 *buf, gsize sz, FuUefiDevpathParseFlags flags, GError **error); GBytes * fu_uefi_devpath_find_data(GPtrArray *dps, guint8 type, guint8 subtype, GError **error); fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-grub-device.c000066400000000000000000000141131420024370600226100ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_path(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_common_find_program_in_path("grub-mkconfig", NULL); if (grub_mkconfig == NULL) grub_mkconfig = fu_common_find_program_in_path("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_common_find_program_in_path("grub-reboot", NULL); if (grub_reboot == NULL) grub_reboot = fu_common_find_program_in_path("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); fu_common_string_replace(str, esp_path, ""); 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; if (g_getenv("FWUPD_UPDATE_CAPSULE_VERBOSE") != NULL) 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); const gchar *fw_class = fu_uefi_device_get_guid(self); g_autofree gchar *basename = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *esp_path = fu_uefi_device_get_esp_path(self); 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(device, esp_path); basename = g_strdup_printf("fwupd-%s.cap", fw_class); fn = g_build_filename(directory, "fw", basename, NULL); if (!fu_common_mkdir_parent(fn, error)) return FALSE; fixed_fw = fu_uefi_device_fixup_firmware(self, fw, error); if (fixed_fw == NULL) return FALSE; if (!fu_common_set_contents_bytes(fn, fixed_fw, error)) return FALSE; /* skip for self tests */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) return TRUE; /* delete the logs to save space; use fwupdate to debug the EFI binary */ if (fu_efivar_exists(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE")) { if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", error)) return FALSE; } 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, fn, 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(error); if (source_app == NULL) return FALSE; /* test if correct asset in place */ target_app = fu_uefi_get_esp_app_path(device, esp_path, "fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_cmp_asset(source_app, target_app)) { if (!fu_uefi_copy_asset(source_app, 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) { } 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.7.5/plugins/uefi-capsule/fu-uefi-grub-device.h000066400000000000000000000006301420024370600226140ustar00rootroot00000000000000/* * 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) struct _FuUefiGrubDeviceClass { FuUefiDeviceClass parent_class; }; fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-nvram-device.c000066400000000000000000000111461420024370600227770ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2018 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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); g_autofree gchar *esp_path = fu_uefi_device_get_esp_path(self); g_autoptr(GBytes) fixed_fw = NULL; g_autoptr(GBytes) fw = 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(device, esp_path); basename = g_strdup_printf("fwupd-%s.cap", fw_class); fn = g_build_filename(directory, "fw", basename, NULL); if (!fu_common_mkdir_parent(fn, error)) return FALSE; fixed_fw = fu_uefi_device_fixup_firmware(self, fw, error); if (fixed_fw == NULL) return FALSE; if (!fu_common_set_contents_bytes(fn, fixed_fw, error)) return FALSE; /* delete the logs to save space; use fwupdate to debug the EFI binary */ if (fu_efivar_exists(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE")) { if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", error)) return FALSE; } 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, fn, 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; /* 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(device, esp_path, 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) { } 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.7.5/plugins/uefi-capsule/fu-uefi-nvram-device.h000066400000000000000000000006361420024370600230060ustar00rootroot00000000000000/* * 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) struct _FuUefiNvramDeviceClass { FuUefiDeviceClass parent_class; }; fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-tool.c000066400000000000000000000321201420024370600213670ustar00rootroot00000000000000/* * 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-ucs2.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 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(FuVolume) esp = NULL; const GOptionEntry options[] = { {"verbose", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL}, {"version", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_version, /* TRANSLATORS: command line option */ _("Display version"), NULL}, {"log", 'L', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_log, /* TRANSLATORS: command line option */ _("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 */ _("List supported firmware updates"), NULL}, {"supported", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_supported, /* TRANSLATORS: command line option */ _("Query for firmware update support"), NULL}, {"info", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_info, /* TRANSLATORS: command line option */ _("Show the information of firmware update status"), NULL}, {"enable", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_enable, /* TRANSLATORS: command line option */ _("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 */ _("Override the default ESP path"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("PATH")}, {"set-debug", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_set_debug, /* TRANSLATORS: command line option */ _("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 */ _("Unset the debugging flag during update"), NULL}, {"apply", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &apply, /* TRANSLATORS: command line option */ _("Apply firmware updates"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ C_("A single GUID", "GUID")}, {"method", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &type, /* TRANSLATORS: command line option */ _("Device update method"), "nvram|cod|grub"}, {"flags", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &flags, /* TRANSLATORS: command line option */ _("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); /* 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) { 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) { esp = fu_common_get_esp_for_path(esp_path, &error); if (esp == NULL) { /* TRANSLATORS: ESP is EFI System Partition */ g_print("%s: %s\n", _("ESP specified was not valid"), error->message); return EXIT_FAILURE; } } else { esp = fu_common_get_esp_default(&error); if (esp == NULL) { g_printerr("failed: %s\n", error->message); return EXIT_FAILURE; } } /* show the debug action_log from the last attempted update */ if (action_log) { gsize sz = 0; g_autofree guint8 *buf = NULL; g_autofree guint16 *buf_ucs2 = NULL; g_autofree gchar *str = NULL; g_autoptr(GError) error_local = NULL; if (!fu_efivar_get_data(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", &buf, &sz, NULL, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } buf_ucs2 = g_new0(guint16, (sz / 2) + 1); memcpy(buf_ucs2, buf, sz); str = fu_ucs2_to_uft8(buf_ucs2, sz / 2); g_print("%s", str); } if (action_list || action_supported || action_info) { g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuBackend) backend = fu_uefi_backend_new(ctx); g_autoptr(GError) error_local = NULL; /* load SMBIOS */ if (!fu_context_load_hwinfo(ctx, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } /* add each device */ if (!fu_backend_coldplug(backend, &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", fu_uefi_update_info_get_version(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(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(FuUefiDevice) dev = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GBytes) fw = NULL; /* load SMBIOS */ if (!fu_context_load_hwinfo(ctx, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } /* 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_common_get_contents_bytes(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), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } if (!fu_device_write_firmware(FU_DEVICE(dev), fw, progress, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } if (!fu_device_cleanup(FU_DEVICE(dev), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } } /* success */ return EXIT_SUCCESS; } fwupd-1.7.5/plugins/uefi-capsule/fu-uefi-update-info.c000066400000000000000000000105061420024370600226310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ucs2.h" #include "fu-uefi-common.h" #include "fu-uefi-devpath.h" #include "fu-uefi-update-info.h" #define EFIDP_MEDIA_TYPE 0x04 #define EFIDP_MEDIA_FILE 0x4 struct _FuUefiUpdateInfo { GObject parent_instance; guint32 version; gchar *guid; gchar *capsule_fn; guint32 capsule_flags; guint64 hw_inst; FuUefiUpdateInfoStatus status; }; G_DEFINE_TYPE(FuUefiUpdateInfo, fu_uefi_update_info, G_TYPE_OBJECT) const gchar * fu_uefi_update_info_status_to_string(FuUefiUpdateInfoStatus status) { if (status == FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE) return "attempt-update"; if (status == FU_UEFI_UPDATE_INFO_STATUS_ATTEMPTED) return "attempted"; return "unknown"; } static gchar * fu_uefi_update_info_parse_dp(const guint8 *buf, gsize sz, GError **error) { GBytes *dp_data; const gchar *data; gsize ucs2sz = 0; g_autofree gchar *relpath = NULL; g_autofree guint16 *ucs2file = NULL; g_autoptr(GPtrArray) dps = NULL; g_return_val_if_fail(buf != NULL, NULL); g_return_val_if_fail(sz != 0, NULL); /* get all headers */ dps = fu_uefi_devpath_parse(buf, sz, FU_UEFI_DEVPATH_PARSE_FLAG_REPAIR, error); if (dps == NULL) return NULL; dp_data = fu_uefi_devpath_find_data(dps, EFIDP_MEDIA_TYPE, EFIDP_MEDIA_FILE, error); if (dp_data == NULL) return NULL; /* convert to UTF-8 */ data = g_bytes_get_data(dp_data, &ucs2sz); ucs2file = g_new0(guint16, (ucs2sz / 2) + 1); memcpy(ucs2file, data, ucs2sz); relpath = fu_ucs2_to_uft8(ucs2file, ucs2sz / sizeof(guint16)); if (relpath == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot convert to UTF-8"); return NULL; } g_strdelimit(relpath, "\\", '/'); return g_steal_pointer(&relpath); } gboolean fu_uefi_update_info_parse(FuUefiUpdateInfo *self, const guint8 *buf, gsize sz, GError **error) { efi_update_info_t info; fwupd_guid_t guid_tmp; g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), FALSE); if (sz < sizeof(efi_update_info_t)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI variable is corrupt"); return FALSE; } memcpy(&info, buf, sizeof(info)); self->version = info.update_info_version; self->capsule_flags = info.capsule_flags; self->hw_inst = info.hw_inst; self->status = info.status; memcpy(&guid_tmp, &info.guid, sizeof(fwupd_guid_t)); self->guid = fwupd_guid_to_string(&guid_tmp, FWUPD_GUID_FLAG_MIXED_ENDIAN); if (sz > sizeof(efi_update_info_t)) { self->capsule_fn = fu_uefi_update_info_parse_dp(buf + sizeof(efi_update_info_t), sz - sizeof(efi_update_info_t), 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_version(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), 0); return self->version; } 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) { GObjectClass *object_class = G_OBJECT_CLASS(klass); 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.7.5/plugins/uefi-capsule/fu-uefi-update-info.h000066400000000000000000000021041420024370600226310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #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, GObject) typedef enum { FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE = 0x00000001, FU_UEFI_UPDATE_INFO_STATUS_ATTEMPTED = 0x00000002, } FuUefiUpdateInfoStatus; const gchar * fu_uefi_update_info_status_to_string(FuUefiUpdateInfoStatus status); FuUefiUpdateInfo * fu_uefi_update_info_new(void); gboolean fu_uefi_update_info_parse(FuUefiUpdateInfo *self, const guint8 *buf, gsize sz, GError **error); guint32 fu_uefi_update_info_get_version(FuUefiUpdateInfo *self); 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.7.5/plugins/uefi-capsule/fwupd.grub.conf.in000077500000000000000000000013221420024370600222520ustar00rootroot00000000000000#! /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:?} prepare_grub_to_access_device '`${grub_probe} --target=device \${ESP}` | sed -e "s/^/\t/"' cat << EOF chainloader ${EFI_PATH} } EOF fi fi fwupd-1.7.5/plugins/uefi-capsule/fwupdate.1000066400000000000000000000010241420024370600206100ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdate man page" .SH NAME fwupdate \-ddebugging utility for UEFI firmware updates .SH SYNOPSIS fwupdate [CMD] .SH DESCRIPTION fwupdate is a deprecated tool that allows deploying capsule updates. .SH OPTIONS The fwupdate command takes various options depending on the action. Run \fBfwupdate --help\fR for the full list. .SH SEE ALSO fwupdtool(1), fwupdmgr(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/plugins/uefi-capsule/make-images.py000077500000000000000000000163271420024370600214600ustar00rootroot00000000000000#!/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") 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, "{}.po".format(lang))) 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 = "Sans %.2fpx" % (height / 32,) 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) 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 = "fwupd-{}-{}-{}.bmp".format(lang, width, height) 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.7.5/plugins/uefi-capsule/meson.build000066400000000000000000000120411420024370600210520ustar00rootroot00000000000000if get_option('plugin_uefi_capsule') cargs = ['-DG_LOG_DOMAIN="FuPluginUefiCapsule"'] efi_os_dir = get_option('efi_os_dir') if efi_os_dir != '' cargs += '-DEFI_OS_DIR="' + efi_os_dir + '"' endif install_data(['uefi-capsule.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d')) 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 shared_module('fu_plugin_uefi_capsule', fu_hash, sources : [ 'fu-plugin-uefi-capsule.c', 'fu-uefi-bgrt.c', 'fu-ucs2.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-devpath.c', 'fu-uefi-update-info.c', backend_srcs, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, platform_deps, efiboot, ], ) if get_option('compat_cli') fwupdate = executable( 'fwupdate', resources_src, fu_hash, sources : [ 'fu-uefi-tool.c', 'fu-uefi-bgrt.c', 'fu-ucs2.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-device.c', 'fu-uefi-cod-device.c', 'fu-uefi-nvram-device.c', 'fu-uefi-grub-device.c', 'fu-uefi-devpath.c', 'fu-uefi-update-info.c', backend_srcs, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, platform_deps, efiboot, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir, c_args : cargs, ) endif if get_option('compat_cli') and get_option('man') configure_file( input : 'fwupdate.1', output : 'fwupdate.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif install_data(['uefi_capsule.conf'], install_dir: join_paths(sysconfdir, 'fwupd') ) # add all the .po files as inputs to watch ux_linguas = run_command( 'cat', files(join_paths(meson.source_root(), 'po', 'LINGUAS')), ).stdout().strip().split('\n') ux_capsule_pofiles = [] foreach ux_lingua : ux_linguas ux_capsule_pofiles += join_paths(meson.source_root(), 'po', '@0@.po'.format(ux_lingua)) endforeach if get_option('plugin_uefi_capsule_splash') # add the archive of pregenerated images custom_target('ux-capsule-tar', input : [ join_paths(meson.source_root(), 'po', 'LINGUAS'), join_paths(meson.current_source_dir(), 'make-images.py'), ux_capsule_pofiles, ], output : 'uefi-capsule-ux.tar.xz', command : [ python3.path(), join_paths(meson.current_source_dir(), 'make-images.py'), '--podir', join_paths(meson.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', fu_hash, sources : [ 'fu-self-test.c', 'fu-uefi-bgrt.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-device.c', 'fu-uefi-cod-device.c', 'fu-uefi-nvram-device.c', 'fu-uefi-grub-device.c', 'fu-uefi-devpath.c', 'fu-uefi-update-info.c', 'fu-ucs2.c', backend_srcs, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, platform_deps, efiboot, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs ) test('uefi-self-test', e, env: env) endif # 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 fwupd-1.7.5/plugins/uefi-capsule/tests/000077500000000000000000000000001420024370600200545ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/.gitignore000066400000000000000000000001421420024370600220410ustar00rootroot00000000000000EFI efi/efivars/fwupd-c34cb672-a81e-5d32-9d89-cbcabe8ec37b-0-0abba7dc-e516-4167-bbf5-4d9d1c739416 fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/000077500000000000000000000000001420024370600207705ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/000077500000000000000000000000001420024370600217265ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/image000077700000000000000000000000001420024370600250332../../test.bmpustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/status000066400000000000000000000000021420024370600231640ustar00rootroot000000000000001 fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/type000066400000000000000000000000021420024370600226220ustar00rootroot000000000000000 fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/version000066400000000000000000000000021420024370600233260ustar00rootroot000000000000001 fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/xoffset000066400000000000000000000000041420024370600233210ustar00rootroot00000000000000123 fwupd-1.7.5/plugins/uefi-capsule/tests/acpi/bgrt/yoffset000066400000000000000000000000041420024370600233220ustar00rootroot00000000000000456 fwupd-1.7.5/plugins/uefi-capsule/tests/efi-framebuffer/000077500000000000000000000000001420024370600231015ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/000077500000000000000000000000001420024370600262645ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/height000066400000000000000000000000041420024370600274510ustar00rootroot00000000000000789 fwupd-1.7.5/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/width000066400000000000000000000000041420024370600273200ustar00rootroot00000000000000456 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/000077500000000000000000000000001420024370600206175ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars/000077500000000000000000000000001420024370600222565ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars/Capsule0000-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000601420024370600307450ustar00rootroot00000000000000: T+,fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars/Capsule0001-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000601420024370600307460ustar00rootroot00000000000000:L̝ T+,fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars/CapsuleLast-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000321420024370600312700ustar00rootroot00000000000000Capsule0001fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars/CapsuleMax-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000321420024370600311120ustar00rootroot00000000000000Capsule9999fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461420024370600354130ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/000077500000000000000000000000001420024370600215745ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/000077500000000000000000000000001420024370600232455ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/000077500000000000000000000000001420024370600244665ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051420024370600272140ustar00rootroot000000000000000xfe fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451420024370600262110ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021420024370600260560ustar00rootroot000000000000001 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061420024370600265660ustar00rootroot0000000000000065586 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021420024370600305050ustar00rootroot000000000000001 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111420024370600306470ustar00rootroot0000000000000018472960 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061420024370600322700ustar00rootroot0000000000000065582 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/000077500000000000000000000000001420024370600244675ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/capsule_flags000066400000000000000000000000071420024370600272170ustar00rootroot000000000000000x8010 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_class000066400000000000000000000000451420024370600262120ustar00rootroot00000000000000671d19d0-d43c-4852-98d9-1ce16f9967e4 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_type000066400000000000000000000000021420024370600260570ustar00rootroot000000000000002 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_version000066400000000000000000000000131420024370600265650ustar00rootroot000000000000003090287969 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/last_attempt_status000066400000000000000000000000021420024370600305060ustar00rootroot000000000000000 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/last_attempt_version000066400000000000000000000000021420024370600306500ustar00rootroot000000000000000 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/lowest_supported_fw_version000066400000000000000000000000021420024370600322650ustar00rootroot000000000000001 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/000077500000000000000000000000001420024370600244705ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/capsule_flags000077700000000000000000000000001420024370600334062../entry1/capsule_flagsustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_class000066400000000000000000000000451420024370600262130ustar00rootroot0000000000000000000000-0000-0000-0000-000000000000 fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_type000077700000000000000000000000001420024370600311202../entry1/fw_typeustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_version000077700000000000000000000000001420024370600323302../entry1/fw_versionustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/last_attempt_status000077700000000000000000000000001420024370600361762../entry1/last_attempt_statusustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/last_attempt_version000077700000000000000000000000001420024370600365022../entry1/last_attempt_versionustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/lowest_supported_fw_version000077700000000000000000000000001420024370600415342../entry1/lowest_supported_fw_versionustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/efi/fw_platform_size000066400000000000000000000000031420024370600241050ustar00rootroot0000000000000064 fwupd-1.7.5/plugins/uefi-capsule/tests/example/000077500000000000000000000000001420024370600215075ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-capsule/tests/example/.gitignore000066400000000000000000000000321420024370600234720ustar00rootroot00000000000000firmware.bin firmware.cab fwupd-1.7.5/plugins/uefi-capsule/tests/example/build.sh000077500000000000000000000002541420024370600231460ustar00rootroot00000000000000#!/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.7.5/plugins/uefi-capsule/tests/example/firmware.metainfo.xml000066400000000000000000000020251420024370600256450ustar00rootroot00000000000000 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.7.5/plugins/uefi-capsule/tests/test.bmp000066400000000000000000000046721420024370600215440ustar00rootroot00000000000000BM 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}` * `UEFI\CRT_{sha256}&ARCH_{arch}` ...where `arch` is typically one of `IA32`, `X64`, `ARM` or `AA64` ## 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` fwupd-1.7.5/plugins/uefi-dbx/dbxtool.1000066400000000000000000000012251420024370600175700ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "dbxtool man page" .SH NAME dbxtool \- modify the dbx revokation list .SH SYNOPSIS dbxtool [CMD] .SH DESCRIPTION .PP This manual page documents briefly the \fBdbxtool\fR command. .PP \fBdbxtool\fR allows a user to operate on the UEFI dbx revokation list. This tool can be used to list the current dbx contents or update it to a newer version. .SH OPTIONS The dbxtool command takes various options depending on the action. Run \fBdbxtool --help\fR for the full list. .SH SEE ALSO fwupdmgr(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/plugins/uefi-dbx/fu-dbxtool.c000066400000000000000000000225511420024370600202670ustar00rootroot00000000000000/* * Copyright (C) 2015 Peter Jones * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #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_common_get_contents_bytes(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(GOptionContext) context = NULL; g_autofree gchar *tmp = NULL; const GOptionEntry options[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL}, {"version", '\0', 0, G_OPTION_ARG_NONE, &action_version, /* TRANSLATORS: command line option */ _("Show the calculated version of the dbx"), NULL}, {"list", 'l', 0, G_OPTION_ARG_NONE, &action_list, /* TRANSLATORS: command line option */ _("List entries in dbx"), NULL}, {"apply", 'a', 0, G_OPTION_ARG_NONE, &action_apply, /* TRANSLATORS: command line option */ _("Apply update files"), NULL}, {"dbx", 'd', 0, G_OPTION_ARG_STRING, &dbxfile, /* TRANSLATORS: command line option */ _("Specify the dbx database file"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME")}, {"force", 'f', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ _("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); /* 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) { g_setenv("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* 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); } 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_common_get_contents_bytes(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(FU_EFI_SIGNATURE_LIST(dbx_update), &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.7.5/plugins/uefi-dbx/fu-efi-image.c000066400000000000000000000230401420024370600204310ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-image.h" struct _FuEfiImage { GObject parent_instance; gchar *checksum; }; typedef struct { gsize offset; gsize size; gchar *name; } FuEfiImageRegion; typedef struct __attribute__((packed)) { guint32 addr; guint32 size; } FuEfiImageDataDirEntry; 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_common_read_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_common_read_uint32_safe(buf, bufsz, _DOS_OFFSET_TO_PE_HEADER, &baseaddr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_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_common_read_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_common_read_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_common_read_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_common_read_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_common_read_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_common_read_uint32_safe(buf, bufsz, data_dir_debug_offset + sizeof(guint32), &cert_table_size, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_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_common_read_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 + sizeof(FuEfiImageDataDirEntry); /* 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 + sizeof(FuEfiImageDataDirEntry), 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_common_read_uint32_safe(buf, bufsz, offset_tmp + _SECTION_HEADER_OFFSET_PTR, &file_offset, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_read_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.7.5/plugins/uefi-dbx/fu-efi-image.h000066400000000000000000000006041420024370600204370ustar00rootroot00000000000000/* * 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.7.5/plugins/uefi-dbx/fu-plugin-uefi-dbx.c000066400000000000000000000017131420024370600216100ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-dbx-device.h" static void fu_plugin_uefi_dbx_init(FuPlugin *plugin) { 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 gboolean fu_plugin_uefi_dbx_coldplug(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuUefiDbxDevice) device = fu_uefi_dbx_device_new(ctx); if (!fu_device_probe(FU_DEVICE(device), error)) return FALSE; if (!fu_device_setup(FU_DEVICE(device), error)) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(device)); return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_uefi_dbx_init; vfuncs->coldplug = fu_plugin_uefi_dbx_coldplug; } fwupd-1.7.5/plugins/uefi-dbx/fu-self-test.c000066400000000000000000000024611420024370600205200ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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_common_get_contents_bytes(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) { 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); 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.7.5/plugins/uefi-dbx/fu-uefi-dbx-common.c000066400000000000000000000051201420024370600215760ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-image.h" #include "fu-uefi-dbx-common.h" 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("SHA256 was %s", fu_efi_image_get_checksum(img)); return g_strdup(fu_efi_image_get_checksum(img)); } static gboolean fu_uefi_dbx_signature_list_validate_volume(FuEfiSignatureList *siglist, FuVolume *esp, GError **error) { g_autofree gchar *esp_path = NULL; g_autoptr(GPtrArray) files = 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_common_get_files_recursive(esp_path, error); if (files == NULL) return FALSE; /* 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; /* 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(FuEfiSignatureList *siglist, GError **error) { g_autoptr(GPtrArray) volumes = NULL; volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_ESP, 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; locker = fu_volume_locker(esp, error); if (locker == NULL) return FALSE; if (!fu_uefi_dbx_signature_list_validate_volume(siglist, esp, error)) return FALSE; } return TRUE; } fwupd-1.7.5/plugins/uefi-dbx/fu-uefi-dbx-common.h000066400000000000000000000005211420024370600216030ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include gchar * fu_uefi_dbx_get_authenticode_hash(const gchar *fn, GError **error); gboolean fu_uefi_dbx_signature_list_validate(FuEfiSignatureList *siglist, GError **error); fwupd-1.7.5/plugins/uefi-dbx/fu-uefi-dbx-device.c000066400000000000000000000133651420024370600215570ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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(); /* 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; fu_device_set_version(device, fu_firmware_get_version(dbx)); fu_device_set_version_lowest(device, fu_firmware_get_version(dbx)); return TRUE; } static FuFirmware * fu_uefi_dbx_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { 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(FU_EFI_SIGNATURE_LIST(siglist), 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_autofree gchar *arch_up = NULL; 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_NO_SEARCH, error)) return FALSE; arch_up = g_utf8_strup(EFI_MACHINE_TYPE_NAME, -1); 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; g_autofree gchar *checksum_up = NULL; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; checksum = fu_firmware_get_checksum(FU_FIRMWARE(sig), G_CHECKSUM_SHA256, error); if (checksum == NULL) return FALSE; checksum_up = g_utf8_strup(checksum, -1); devid1 = g_strdup_printf("UEFI\\CRT_%s", checksum_up); fu_device_add_instance_id(device, devid1); devid2 = g_strdup_printf("UEFI\\CRT_%s&ARCH_%s", checksum_up, arch_up); fu_device_add_instance_id(device, devid2); } return fu_uefi_dbx_device_set_version_number(device, error); } static void fu_uefi_dbx_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_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_parent_guid(FU_DEVICE(self), "main-system-firmware"); if (!fu_common_is_live_media()) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); } 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; } 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.7.5/plugins/uefi-dbx/fu-uefi-dbx-device.h000066400000000000000000000005501420024370600215540ustar00rootroot00000000000000/* * 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.7.5/plugins/uefi-dbx/fuzzing/000077500000000000000000000000001420024370600175275ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-dbx/fuzzing/example.bin000066400000000000000000000001141420024370600216500ustar00rootroot000000000000000000000000000000L0111111111111111122222222222222222222222222222222fwupd-1.7.5/plugins/uefi-dbx/meson.build000066400000000000000000000033341420024370600202000ustar00rootroot00000000000000if get_option('plugin_uefi_capsule') cargs = ['-DG_LOG_DOMAIN="FuPluginUefiDbx"'] shared_module('fu_plugin_uefi_dbx', fu_hash, sources : [ 'fu-plugin-uefi-dbx.c', 'fu-uefi-dbx-common.c', 'fu-uefi-dbx-device.c', 'fu-efi-image.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-uefi-dbx-common.c', 'fu-efi-image.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, install : true, install_dir : installed_test_bindir, ) test('uefi-dbx-self-test', e, env : env) # added to installed-tests endif dbxtool = executable( 'dbxtool', fu_hash, sources : [ 'fu-dbxtool.c', 'fu-uefi-dbx-common.c', 'fu-efi-image.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir, c_args : cargs, ) if get_option('man') configure_file( input : 'dbxtool.1', output : 'dbxtool.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif endif fwupd-1.7.5/plugins/uefi-pk/000077500000000000000000000000001420024370600156705ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-pk/README.md000066400000000000000000000006151420024370600171510ustar00rootroot00000000000000# 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` fwupd-1.7.5/plugins/uefi-pk/fu-plugin-uefi-pk.c000066400000000000000000000126501420024370600213040ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include struct FuPluginData { gboolean 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_plugin_uefi_pk_parse_buf(FuPlugin *plugin, const gchar *buf, gsize bufsz, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); const gchar *needles[] = { "DO NOT TRUST", "DO NOT SHIP", NULL, }; for (guint i = 0; needles[i] != NULL; i++) { if (g_strstr_len(buf, bufsz, needles[i]) != NULL) { g_debug("got %s, marking unsafe", buf); priv->has_pk_test_key = TRUE; break; } } return TRUE; } static gboolean fu_plugin_uefi_pk_parse_signature(FuPlugin *plugin, FuEfiSignature *sig, GError **error) { gchar buf[1024] = {'\0'}; gnutls_datum_t d = {0}; gnutls_x509_dn_t dn = {0x0}; gsize bufsz = sizeof(buf); int rc; 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) { if (g_getenv("FWUPD_UEFI_PK_VERBOSE") != NULL) g_debug("PK issuer: %s", buf); if (!fu_plugin_uefi_pk_parse_buf(plugin, 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); if (g_getenv("FWUPD_UEFI_PK_VERBOSE") != NULL) g_debug("PK subject: %s", subject->data); if (!fu_plugin_uefi_pk_parse_buf(plugin, (const gchar *)subject->data, subject->size, error)) return FALSE; } /* success, certificate was parsed correctly */ return TRUE; } static gboolean fu_plugin_uefi_pk_coldplug(FuPlugin *plugin, GError **error) { FuPluginData *priv = fu_plugin_get_data(plugin); 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) { g_debug("detected AMI test certificate"); priv->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_plugin_uefi_pk_parse_signature(plugin, sig, error)) return FALSE; } /* success */ return TRUE; } static void fu_plugin_uefi_pk_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_uefi_pk_device_registered(FuPlugin *plugin, FuDevice *device) { if (fu_device_has_instance_id(device, "main-system-firmware")) fu_plugin_cache_add(plugin, "main-system-firmware", device); } static void fu_plugin_uefi_pk_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPluginData *priv = fu_plugin_get_data(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_UEFI_PK); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(plugin)); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fu_security_attrs_append(attrs, attr); /* test key is not secure */ if (priv->has_pk_test_key) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_uefi_pk_init; vfuncs->add_security_attrs = fu_plugin_uefi_pk_add_security_attrs; vfuncs->device_registered = fu_plugin_uefi_pk_device_registered; vfuncs->coldplug = fu_plugin_uefi_pk_coldplug; } fwupd-1.7.5/plugins/uefi-pk/meson.build000066400000000000000000000007061420024370600200350ustar00rootroot00000000000000if get_option('hsi') and get_option('plugin_uefi_pk') cargs = ['-DG_LOG_DOMAIN="FuPluginUefiPk"'] shared_module('fu_plugin_uefi_pk', fu_hash, sources : [ 'fu-plugin-uefi-pk.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, gnutls, ], ) endif fwupd-1.7.5/plugins/uefi-recovery/000077500000000000000000000000001420024370600171145ustar00rootroot00000000000000fwupd-1.7.5/plugins/uefi-recovery/README.md000066400000000000000000000011351420024370600203730ustar00rootroot00000000000000# UEFI ## 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. fwupd-1.7.5/plugins/uefi-recovery/fu-plugin-uefi-recovery.c000066400000000000000000000042341420024370600237530ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include static void fu_plugin_uefi_recovery_init(FuPlugin *plugin) { /* 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 gboolean fu_plugin_uefi_recovery_startup(FuPlugin *plugin, GError **error) { /* are the EFI dirs set up so we can update each device */ if (!fu_efivar_supported(error)) return FALSE; return TRUE; } static gboolean fu_plugin_uefi_recovery_coldplug(FuPlugin *plugin, 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_with_context(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; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_uefi_recovery_init; vfuncs->coldplug = fu_plugin_uefi_recovery_coldplug; vfuncs->startup = fu_plugin_uefi_recovery_startup; } fwupd-1.7.5/plugins/uefi-recovery/meson.build000066400000000000000000000010471420024370600212600ustar00rootroot00000000000000if get_option('plugin_uefi_capsule') cargs = ['-DG_LOG_DOMAIN="FuPluginUefiRecovery"'] install_data(['uefi-recovery.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_uefi_recovery', fu_hash, sources : [ 'fu-plugin-uefi-recovery.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, ], dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/uefi-recovery/uefi-recovery.quirk000066400000000000000000000001741420024370600227570ustar00rootroot00000000000000# Silicom Minnowboard Turbot MNW2MAX1.X64.0100.R01.1811141729 [ea358e00-39f1-55b6-97be-a39225a585e1] Plugin = uefi_recovery fwupd-1.7.5/plugins/uf2/000077500000000000000000000000001420024370600150245ustar00rootroot00000000000000fwupd-1.7.5/plugins/uf2/README.md000066400000000000000000000034071420024370600163070ustar00rootroot00000000000000# UF2 Devices ## 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. fwupd-1.7.5/plugins/uf2/fu-plugin-uf2.c000066400000000000000000000010671420024370600175740ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uf2-device.h" #include "fu-uf2-firmware.h" static void fu_plugin_uf2_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_UF2_DEVICE); fu_plugin_add_firmware_gtype(plugin, "uf2", FU_TYPE_UF2_FIRMWARE); fu_plugin_add_udev_subsystem(plugin, "block"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_uf2_init; } fwupd-1.7.5/plugins/uf2/fu-self-test.c000066400000000000000000000032751420024370600175150ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { 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.7.5/plugins/uf2/fu-uf2-device.c000066400000000000000000000304321420024370600175330ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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) { g_autofree gchar *id0 = NULL; id0 = g_strdup_printf("UF2\\FAMILY_%08X", (guint32)fu_firmware_get_idx(firmware)); fu_device_add_instance_id_full(device, id0, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } /* 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_common_get_volume_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_common_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_common_get_volume_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_common_get_volume_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_common_strnsplit(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: ")) { g_autofree gchar *devid = NULL; devid = g_strdup_printf("UF2\\BOARD_%s", lines[i] + 10); fu_device_add_instance_id(device, devid); } } /* this might exist */ fn2 = fu_block_device_get_full_path(self, "CURRENT.UF2", error); fw = fu_common_get_contents_bytes(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; const gchar *uuid; guint64 vid = 0; guint64 pid = 0; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_uf2_device_parent_class)->probe(device, error)) return FALSE; /* 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); tmp = g_udev_device_get_property(udev_device, "ID_MODEL_ID"); if (tmp != NULL) pid = g_ascii_strtoull(tmp, NULL, 16); uuid = g_udev_device_get_property(udev_device, "ID_FS_UUID"); if (uuid != NULL && vid != 0x0 && pid != 0x0) { g_autofree gchar *devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&UUID_%s", (guint)vid, (guint)pid, uuid); fu_device_add_instance_id(device, devid); } if (vid != 0x0 && pid != 0x0) { g_autofree gchar *devid = g_strdup_printf("USB\\VID_%04X&PID_%04X", (guint)vid, (guint)pid); fu_device_add_instance_id(device, devid); } 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_common_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"); } 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.7.5/plugins/uf2/fu-uf2-device.h000066400000000000000000000004321420024370600175350ustar00rootroot00000000000000/* * 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.7.5/plugins/uf2/fu-uf2-firmware.c000066400000000000000000000242701420024370600201130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uf2-firmware.h" struct _FuUf2Firmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE) #define FU_UF2_FIRMWARE_MAGIC_START0 0x0A324655u #define FU_UF2_FIRMWARE_MAGIC_START1 0x9E5D5157u #define FU_UF2_FIRMWARE_MAGIC_END 0x0AB16F30u #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 magic = 0; guint32 flags = 0; guint32 addr = 0; guint32 datasz = 0; guint32 blockcnt = 0; guint32 blocktotal = 0; guint32 family_id = 0; /* sanity check */ if (bufsz != 512) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "chunk size invalid, expected 512 bytes and got %u", fu_chunk_get_data_sz(chk)); return FALSE; } /* check magic */ if (!fu_common_read_uint32_safe(buf, bufsz, 0x000, &magic, G_LITTLE_ENDIAN, error)) return FALSE; if (magic != FU_UF2_FIRMWARE_MAGIC_START0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "magic bytes #1 failed, expected 0x%08x bytes and got 0x%08x", FU_UF2_FIRMWARE_MAGIC_START0, magic); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, 0x004, &magic, G_LITTLE_ENDIAN, error)) return FALSE; if (magic != FU_UF2_FIRMWARE_MAGIC_START1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "magic bytes #2 failed, expected 0x%08x bytes and got 0x%08x", FU_UF2_FIRMWARE_MAGIC_START1, magic); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, 0x008, &flags, G_LITTLE_ENDIAN, error)) return FALSE; 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; } if (!fu_common_read_uint32_safe(buf, bufsz, 0x00C, &addr, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, bufsz, 0x010, &datasz, G_LITTLE_ENDIAN, error)) return FALSE; 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_common_read_uint32_safe(buf, bufsz, 0x014, &blockcnt, G_LITTLE_ENDIAN, error)) return FALSE; if (blockcnt != 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), blockcnt); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, 0x018, &blocktotal, G_LITTLE_ENDIAN, error)) return FALSE; if (blocktotal == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "block count invalid, expected > 0"); return FALSE; } if (!fu_common_read_uint32_safe(buf, bufsz, 0x01C, &family_id, G_LITTLE_ENDIAN, error)) return FALSE; if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) { if (family_id == 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), addr); fu_firmware_set_idx(FU_FIRMWARE(self), family_id); } /* just append raw data */ g_byte_array_append(tmp, buf + 0x020, 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 = 0x20 + datasz; while (offset < bufsz) { guint8 sz = 0; guint32 tag = 0; /* [SZ][TAG][TAG][TAG][TAG][DATA....] */ if (!fu_common_read_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_common_read_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 { g_warning("unknown tag 0x%06x", tag); } /* next! */ offset += sz; } } if (!fu_common_read_uint32_safe(buf, bufsz, 0x1FC, &magic, G_LITTLE_ENDIAN, error)) return FALSE; if (magic != FU_UF2_FIRMWARE_MAGIC_END) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "magic bytes #3 failed, expected 0x%08x bytes and got 0x%08x", FU_UF2_FIRMWARE_MAGIC_END, magic); return FALSE; } /* dump */ if (g_getenv("FWUPD_U2F_VERBOSE") != NULL) { g_debug("block: 0x%x/0x%x @0x%x", blockcnt, blocktotal - 1, addr); g_debug("family_id: 0x%x", family_id); g_debug("flags: 0x%x", flags); g_debug("datasz: 0x%x", datasz); } /* success */ return TRUE; } static gboolean fu_uf2_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware); g_autoptr(GByteArray) tmp = g_byte_array_new(); g_autoptr(GPtrArray) chunks = NULL; /* read in fixed sized chunks */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, 512); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error)) return FALSE; } /* success */ fu_firmware_set_bytes(firmware, g_byte_array_free_to_bytes(g_steal_pointer(&tmp))); 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 family_id = fu_firmware_get_idx(FU_FIRMWARE(self)); guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) datapad = g_byte_array_new(); /* sanity check */ if (fu_chunk_get_data_sz(chk) > 476) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "chunk size invalid, expected < 476 bytes and got %u", fu_chunk_get_data_sz(chk)); return NULL; } /* pad out data */ g_byte_array_append(datapad, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_set_size_full(datapad, 476, 0x0); /* optional */ if (family_id > 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_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_START0, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_START1, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, flags, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, addr, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, fu_chunk_get_idx(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, chk_len, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, family_id, G_LITTLE_ENDIAN); g_byte_array_append(buf, datapad->data, datapad->len); fu_byte_array_append_uint32(buf, FU_UF2_FIRMWARE_MAGIC_END, G_LITTLE_ENDIAN); /* success */ return g_steal_pointer(&buf); } static GBytes * 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(GPtrArray) 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), 0x0, 256); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) tmp = NULL; tmp = fu_uf2_firmware_write_chunk(self, chk, chunks->len, error); if (tmp == NULL) return NULL; g_byte_array_append(buf, tmp->data, tmp->len); } /* success */ return g_byte_array_free_to_bytes(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.7.5/plugins/uf2/fu-uf2-firmware.h000066400000000000000000000005121420024370600201110ustar00rootroot00000000000000/* * 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.7.5/plugins/uf2/meson.build000066400000000000000000000025301420024370600171660ustar00rootroot00000000000000if get_option('plugin_uf2') and host_machine.system() == 'linux' if not get_option('gusb') error('gusb is required for plugin_uf2') endif cargs = ['-DG_LOG_DOMAIN="FuPluginUf2"'] install_data(['uf2.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_uf2', fu_hash, sources : [ 'fu-plugin-uf2.c', 'fu-uf2-device.c', 'fu-uf2-firmware.c', # fuzzing ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-uf2-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : installed_test_bindir, ) test('uf2-self-test', e, env : env) endif endif fwupd-1.7.5/plugins/uf2/tests/000077500000000000000000000000001420024370600161665ustar00rootroot00000000000000fwupd-1.7.5/plugins/uf2/tests/uf2.bin000066400000000000000000000010001420024370600173430ustar00rootroot00000000000000UF2 WQ] hello world0o fwupd-1.7.5/plugins/uf2/tests/uf2.builder.xml000066400000000000000000000001631420024370600210310ustar00rootroot00000000000000 0x0100 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/uf2/uf2.quirk000066400000000000000000000044321420024370600166000ustar00rootroot00000000000000# match all devices with this udev subsystem [BLOCK] Plugin = uf2 # 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.7.5/plugins/upower/000077500000000000000000000000001420024370600156515ustar00rootroot00000000000000fwupd-1.7.5/plugins/upower/README.md000066400000000000000000000004701420024370600171310ustar00rootroot00000000000000# 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`. fwupd-1.7.5/plugins/upower/fu-plugin-upower.c000066400000000000000000000115301420024370600212420ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include struct FuPluginData { GDBusProxy *proxy; /* nullable */ GDBusProxy *proxy_manager; /* nullable */ }; static void fu_plugin_upower_init(FuPlugin *plugin) { fu_plugin_alloc_data(plugin, sizeof(FuPluginData)); } static void fu_plugin_upower_destroy(FuPlugin *plugin) { FuPluginData *data = fu_plugin_get_data(plugin); if (data->proxy != NULL) g_object_unref(data->proxy); if (data->proxy_manager != NULL) g_object_unref(data->proxy_manager); } static void fu_plugin_upower_rescan_devices(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_get_data(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(data->proxy, "Type"); if (type_val == NULL || g_variant_get_uint32(type_val) == 0) { g_warning("failed to query power type"); fu_context_set_battery_state(ctx, FU_BATTERY_STATE_UNKNOWN); fu_context_set_battery_level(ctx, FU_BATTERY_VALUE_INVALID); return; } state_val = g_dbus_proxy_get_cached_property(data->proxy, "State"); if (state_val == NULL || g_variant_get_uint32(state_val) == 0) { g_warning("failed to query power state"); fu_context_set_battery_state(ctx, FU_BATTERY_STATE_UNKNOWN); fu_context_set_battery_level(ctx, FU_BATTERY_VALUE_INVALID); return; } fu_context_set_battery_state(ctx, g_variant_get_uint32(state_val)); /* get percentage */ percentage_val = g_dbus_proxy_get_cached_property(data->proxy, "Percentage"); if (percentage_val == NULL) { g_warning("failed to query power percentage level"); fu_context_set_battery_level(ctx, FU_BATTERY_VALUE_INVALID); return; } fu_context_set_battery_level(ctx, g_variant_get_double(percentage_val)); } static void fu_plugin_upower_rescan_manager(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); FuPluginData *data = fu_plugin_get_data(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(data->proxy_manager, "LidIsPresent"); lid_is_closed = g_dbus_proxy_get_cached_property(data->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_plugin_upower_proxy_changed_cb(GDBusProxy *proxy, GVariant *changed_properties, const GStrv invalidated_properties, FuPlugin *plugin) { fu_plugin_upower_rescan_manager(plugin); fu_plugin_upower_rescan_devices(plugin); } static gboolean fu_plugin_upower_startup(FuPlugin *plugin, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); g_autofree gchar *name_owner = NULL; data->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 (data->proxy_manager == NULL) { g_prefix_error(error, "failed to connect to upower: "); return FALSE; } data->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 (data->proxy == NULL) { g_prefix_error(error, "failed to connect to upower: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(data->proxy); if (name_owner == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no owner for %s", g_dbus_proxy_get_name(data->proxy)); return FALSE; } g_signal_connect(G_DBUS_PROXY(data->proxy), "g-properties-changed", G_CALLBACK(fu_plugin_upower_proxy_changed_cb), plugin); g_signal_connect(G_DBUS_PROXY(data->proxy_manager), "g-properties-changed", G_CALLBACK(fu_plugin_upower_proxy_changed_cb), plugin); fu_plugin_upower_rescan_devices(plugin); fu_plugin_upower_rescan_manager(plugin); /* success */ return TRUE; } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_upower_init; vfuncs->startup = fu_plugin_upower_startup; vfuncs->destroy = fu_plugin_upower_destroy; } fwupd-1.7.5/plugins/upower/meson.build000066400000000000000000000007071420024370600200170ustar00rootroot00000000000000if get_option('plugin_upower') and host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginUpower"'] shared_module('fu_plugin_upower', fu_hash, sources : [ 'fu-plugin-upower.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/usi-dock/000077500000000000000000000000001420024370600160465ustar00rootroot00000000000000fwupd-1.7.5/plugins/usi-dock/README.md000066400000000000000000000015571420024370600173350ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_17EF&PID_7226` * `USB\VID_17EF` Additionally, some extra "component ID" instance IDs are added. * `USB\VID_17EF&PID_7226&CID_TBT4` * `USB\VID_17EF&PID_7226&CID_USB3` ## 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`. fwupd-1.7.5/plugins/usi-dock/data/000077500000000000000000000000001420024370600167575ustar00rootroot00000000000000fwupd-1.7.5/plugins/usi-dock/data/lsusb.txt000066400000000000000000000044551420024370600206600ustar00rootroot00000000000000Bus 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.7.5/plugins/usi-dock/fu-plugin-usi-dock.c000066400000000000000000000022361420024370600216370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Victor Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-usi-dock-dmc-device.h" #include "fu-usi-dock-mcu-device.h" #define USI_DOCK_TBT_INSTANCE_ID "THUNDERBOLT\\VEN_0108&DEV_2031" static void fu_plugin_usi_dock_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_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_MCU_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_DMC_DEVICE); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_usi_dock_init; vfuncs->device_registered = fu_plugin_usi_dock_dmc_registered; } fwupd-1.7.5/plugins/usi-dock/fu-usi-dock-child-device.c000066400000000000000000000046331420024370600226640ustar00rootroot00000000000000/* * 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_common_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.7.5/plugins/usi-dock/fu-usi-dock-child-device.h000066400000000000000000000007621420024370600226700ustar00rootroot00000000000000/* * 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.7.5/plugins/usi-dock/fu-usi-dock-common.c000066400000000000000000000017001420024370600216240ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Victor Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-usi-dock-common.h" const gchar * fu_usi_dock_spi_state_to_string(guint8 val) { if (val == SPI_STATE_NONE) return "none"; if (val == SPI_STATE_SWITCH_SUCCESS) return "switch-success"; if (val == SPI_STATE_SWITCH_FAIL) return "switch-fail"; if (val == SPI_STATE_CMD_SUCCESS) return "cmd-success"; if (val == SPI_STATE_CMD_FAIL) return "cmd-fail"; if (val == SPI_STATE_RW_SUCCESS) return "rw-success"; if (val == SPI_STATE_RW_FAIL) return "rw-fail"; if (val == SPI_STATE_READY) return "ready"; if (val == SPI_STATE_BUSY) return "busy"; if (val == SPI_STATE_TIMEOUT) return "timeout"; if (val == SPI_STATE_FLASH_FOUND) return "flash-found"; if (val == SPI_STATE_FLASH_NOT_FOUND) return "flash-not-found"; return NULL; } fwupd-1.7.5/plugins/usi-dock/fu-usi-dock-common.h000066400000000000000000000064131420024370600216370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Victor Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define USB_HID_REPORT_ID1 1u #define USB_HID_REPORT_ID2 2u #define USBUID_ISP_DEVICE_CMD_MCU_NONE 0x0 #define USBUID_ISP_DEVICE_CMD_MCU_STATUS 0x1 #define USBUID_ISP_DEVICE_CMD_MCU_JUMP2BOOT 0x2 #define USBUID_ISP_DEVICE_CMD_READ_MCU_VERSIONPAGE 0x3 #define USBUID_ISP_DEVICE_CMD_SET_I225_PWR 0x4 #define USBUID_ISP_DEVICE_CMD_DOCK_RESET 0x5 #define USBUID_ISP_DEVICE_CMD_VERSION_WRITEBACK 0x6 #define USBUID_ISP_DEVICE_CMD_FWBUFER_INITIAL 0x01 #define USBUID_ISP_DEVICE_CMD_FWBUFER_ERASE_FLASH 0x02 #define USBUID_ISP_DEVICE_CMD_FWBUFER_PROGRAM 0x03 #define USBUID_ISP_DEVICE_CMD_FWBUFER_WRITE_RESPONSE 0x04 #define USBUID_ISP_DEVICE_CMD_FWBUFER_READ_STATUS 0x05 #define USBUID_ISP_DEVICE_CMD_FWBUFER_CHECKSUM 0x06 #define USBUID_ISP_DEVICE_CMD_FWBUFER_END 0x07 #define USBUID_ISP_DEVICE_CMD_FWBUFER_TRANSFER_FINISH 0x08 #define USBUID_ISP_DEVICE_CMD_FWBUFER_ERROR_END 0x09 #define USBUID_ISP_INTERNAL_FW_CMD_INITAL 0x0A #define USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW 0x0B #define USBUID_ISP_INTERNAL_FW_CMD_TARGET_CHECKSUM 0x0C #define USBUID_ISP_INTERNAL_FW_CMD_ISP_END 0x0D #define USBUID_ISP_CMD_ALL 0xFF #define TAG_TAG2_ISP_BOOT 0 /* before Common CMD for bootload, with TAG0, TAG1, CMD */ #define TAG_TAG2_ISP 0x5a /* before Common, with TAG0, TAG1, CMD */ #define TAG_TAG2_CMD_MCU 0x6a /* USB->MCU(Common-cmd mode), with TAG0, TAG1, CMD */ #define TAG_TAG2_CMD_SPI 0x7a /* USB->MCU->SPI(Common-cmd mode), with TAG0, TAG1, CMD */ #define TAG_TAG2_CMD_I2C 0x8a /* USB->MCU->I2C(Mass data transmission) */ #define TAG_TAG2_MASS_DATA_MCU 0x6b /* MASS data transfer for MCU 0xA0 */ #define TAG_TAG2_MASS_DATA_SPI 0x7b /* MASS data transfer for External flash 0xA1 */ #define TAG_TAG2_MASS_DATA_I2C 0x8b /* MASS data transfer for TBT flash */ #define DP_VERSION_FROM_MCU 0x01 /* if in use */ #define NIC_VERSION_FROM_MCU 0x2 /* if in use */ #define External_Valid_Value 0x37 #define TX_ISP_LENGTH 61 #define W25Q16DV_PAGE_SIZE 256 #define FIRMWARE_IDX_NONE 0x00 #define FIRMWARE_IDX_DMC_PD 0x01 #define FIRMWARE_IDX_DP 0x02 #define FIRMWARE_IDX_TBT4 0x04 #define FIRMWARE_IDX_USB3 0x08 #define FIRMWARE_IDX_USB2 0x10 #define FIRMWARE_IDX_AUDIO 0x20 #define FIRMWARE_IDX_I225 0x40 #define FIRMWARE_IDX_MCU 0x80 typedef enum { SPI_STATE_NONE, SPI_STATE_SWITCH_SUCCESS, SPI_STATE_SWITCH_FAIL, SPI_STATE_CMD_SUCCESS, SPI_STATE_CMD_FAIL, SPI_STATE_RW_SUCCESS, SPI_STATE_RW_FAIL, SPI_STATE_READY, SPI_STATE_BUSY, SPI_STATE_TIMEOUT, SPI_STATE_FLASH_FOUND, SPI_STATE_FLASH_NOT_FOUND, } SPI_BUS_STATE; typedef struct { 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]; } IspVersionInMcu_t; typedef struct { guint8 id; guint8 length; guint8 mcutag1; guint8 mcutag2; guint8 inbuf[59]; guint8 mcutag3; } UsiDockSetReportBuf; const gchar * fu_usi_dock_idx_to_string(guint8 val); const gchar * fu_usi_dock_spi_state_to_string(guint8 val); fwupd-1.7.5/plugins/usi-dock/fu-usi-dock-dmc-device.c000066400000000000000000000031351420024370600223400ustar00rootroot00000000000000/* * 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_autofree gchar *instance_id = NULL; /* slightly odd: the MCU device uses the DMC version number */ g_debug("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 */ instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%s", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent)), fu_device_get_name(device)); fu_device_add_instance_id(parent, instance_id); /* don't allow firmware updates on this */ fu_device_set_name(device, "Dock Management Controller Information"); fu_device_inhibit(device, "dummy", "Use the MCU to update the DMC device"); } } 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.7.5/plugins/usi-dock/fu-usi-dock-dmc-device.h000066400000000000000000000005371420024370600223500ustar00rootroot00000000000000/* * 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.7.5/plugins/usi-dock/fu-usi-dock-mcu-device.c000066400000000000000000000537301420024370600223670ustar00rootroot00000000000000/* * 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-common.h" #include "fu-usi-dock-dmc-device.h" #include "fu-usi-dock-mcu-device.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 FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP (1 << 0) static gboolean fu_usi_dock_mcu_device_tx(FuUsiDockMcuDevice *self, guint8 tag2, const guint8 *inbuf, gsize inbufsz, GError **error) { UsiDockSetReportBuf tx_buffer = { .id = USB_HID_REPORT_ID2, .length = 0x3 + inbufsz, .mcutag1 = 0xFE, .mcutag2 = 0xFF, .mcutag3 = tag2, }; if (inbuf != NULL) { if (!fu_memcpy_safe(tx_buffer.inbuf, sizeof(tx_buffer.inbuf), 0x0, /* dst */ inbuf, inbufsz, 0x0, /* src */ inbufsz, error)) return FALSE; } /* special cases */ if (tx_buffer.inbuf[0] == USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW) { tx_buffer.inbuf[1] = 0xFF; } return fu_hid_device_set_report(FU_HID_DEVICE(self), USB_HID_REPORT_ID2, (guint8 *)&tx_buffer, sizeof(tx_buffer), FU_USI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_usi_dock_mcu_device_rx(FuUsiDockMcuDevice *self, guint8 cmd, guint8 *outbuf, gsize outbufsz, GError **error) { guint8 buf[64] = {0}; 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_NONE, error)) { return FALSE; } if (buf[0] != USB_HID_REPORT_ID2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid ID, expected 0x%02x, got 0x%02x", USB_HID_REPORT_ID2, buf[0]); return FALSE; } if (buf[2] != 0xFE || buf[3] != 0xFF) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid tags, expected 0x%02x:0x%02x, got 0x%02x:0x%02x", 0xFEu, 0xFFu, buf[2], buf[3]); 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_usi_dock_mcu_device_txrx(FuUsiDockMcuDevice *self, guint8 tag2, const guint8 *inbuf, gsize inbufsz, guint8 *outbuf, gsize outbufsz, GError **error) { if (!fu_usi_dock_mcu_device_tx(self, tag2, inbuf, inbufsz, error)) return FALSE; return fu_usi_dock_mcu_device_rx(self, USBUID_ISP_CMD_ALL, outbuf, outbufsz, error); } static gboolean fu_usi_dock_mcu_device_get_status(FuUsiDockMcuDevice *self, GError **error) { guint8 cmd = USBUID_ISP_DEVICE_CMD_MCU_STATUS; guint8 response = 0; if (!fu_usi_dock_mcu_device_txrx(self, TAG_TAG2_CMD_MCU, &cmd, sizeof(cmd), &response, sizeof(response), error)) 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[] = {USBUID_ISP_DEVICE_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", FIRMWARE_IDX_DMC_PD, G_STRUCT_OFFSET(IspVersionInMcu_t, DMC)}, {"PD", FIRMWARE_IDX_DP, G_STRUCT_OFFSET(IspVersionInMcu_t, PD)}, {"DP5x", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, DP5x)}, {"DP6x", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, DP6x)}, {"TBT4", FIRMWARE_IDX_TBT4, G_STRUCT_OFFSET(IspVersionInMcu_t, TBT4)}, {"USB3", FIRMWARE_IDX_USB3, G_STRUCT_OFFSET(IspVersionInMcu_t, USB3)}, {"USB2", FIRMWARE_IDX_USB2, G_STRUCT_OFFSET(IspVersionInMcu_t, USB2)}, {"AUDIO", FIRMWARE_IDX_AUDIO, G_STRUCT_OFFSET(IspVersionInMcu_t, AUDIO)}, {"I255", FIRMWARE_IDX_I225, G_STRUCT_OFFSET(IspVersionInMcu_t, I255)}, {"MCU", FIRMWARE_IDX_MCU, G_STRUCT_OFFSET(IspVersionInMcu_t, MCU)}, {"bcdVersion", FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(IspVersionInMcu_t, bcdVersion)}, {NULL, 0, 0}}; /* assume DP and NIC in-use */ if (!fu_usi_dock_mcu_device_txrx(self, TAG_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_autofree gchar *instance_id = 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; } else 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 */ instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&CID_%s", fu_usb_device_get_vid(FU_USB_DEVICE(self)), fu_usb_device_get_pid(FU_USB_DEVICE(self)), components[i].name); fu_device_add_instance_id(child, instance_id); 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)) return FALSE; if (!fu_usi_dock_mcu_device_enumerate_children(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_write_chunk(FuUsiDockMcuDevice *self, FuChunk *chk, GError **error) { guint8 buf[64] = {0x0}; guint32 length = 0; guint32 pagesize = fu_chunk_get_data_sz(chk); while (pagesize != 0) { memset(buf, 0x0, sizeof(buf)); buf[63] = TAG_TAG2_MASS_DATA_SPI; buf[0] = USB_HID_REPORT_ID2; /* set length and buffer */ if (pagesize >= TX_ISP_LENGTH) { length = TX_ISP_LENGTH; pagesize -= TX_ISP_LENGTH; } else { length = pagesize; pagesize = 0; } buf[1] = length; /* SetReport */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x2, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_chunk_get_data_sz(chk) - pagesize - length, /* src */ length, error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), USB_HID_REPORT_ID2, buf, sizeof(buf), FU_USI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* GetReport */ memset(buf, 0x0, sizeof(buf)); 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_NONE, error)) { return FALSE; } if (buf[0] != USB_HID_REPORT_ID2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid ID, expected 0x%02x, got 0x%02x", USB_HID_REPORT_ID2, buf[0]); return FALSE; } if (buf[63] != TAG_TAG2_CMD_SPI) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid tag2, expected 0x%02x, got 0x%02x", (guint)TAG_TAG2_CMD_SPI, buf[58]); return FALSE; } } return TRUE; } static gboolean fu_usi_dock_mcu_device_write_chunks(FuUsiDockMcuDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error) { 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_usi_dock_mcu_device_write_chunk(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[] = {USBUID_ISP_DEVICE_CMD_FWBUFER_READ_STATUS}; guint8 val = 0; if (!fu_usi_dock_mcu_device_txrx(self, TAG_TAG2_CMD_SPI, buf, sizeof(buf), &val, sizeof(val), error)) return FALSE; if (val != 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, USBUID_ISP_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; guint8 val = 0x0; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; guint8 checksum = 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_BUSY, 0); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 6); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 40); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 13); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 42); /* initial external flash */ cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_INITIAL; if (!fu_usi_dock_mcu_device_txrx(self, TAG_TAG2_CMD_SPI, &cmd, sizeof(cmd), &val, sizeof(val), error)) return FALSE; if (val != SPI_STATE_READY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid state for CMD_FWBUFER_INITIAL, got 0x%02x", val); return FALSE; } fu_progress_step_done(progress); /* erase external flash */ cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_ERASE_FLASH; if (!fu_usi_dock_mcu_device_txrx(self, TAG_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 = USBUID_ISP_DEVICE_CMD_FWBUFER_PROGRAM; if (!fu_usi_dock_mcu_device_txrx(self, TAG_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, 0x0, W25Q16DV_PAGE_SIZE); if (!fu_usi_dock_mcu_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* file transfer – finished */ cmd = USBUID_ISP_DEVICE_CMD_FWBUFER_TRANSFER_FINISH; if (!fu_usi_dock_mcu_device_txrx(self, TAG_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 = USBUID_ISP_INTERNAL_FW_CMD_UPDATE_FW; if (!fu_usi_dock_mcu_device_txrx(self, TAG_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_attach(FuDevice *device, FuProgress *progress, GError **error) { fu_device_set_remove_delay(device, 500000); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static void fu_usi_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_ERASE, 2); /* erase */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* write */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 6); /* attach */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2); /* 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_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_REPLUG_MATCH_GUID); fu_device_register_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP, "verfmt-hp"); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER); 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; } fwupd-1.7.5/plugins/usi-dock/fu-usi-dock-mcu-device.h000066400000000000000000000011241420024370600223620ustar00rootroot00000000000000/* * 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.7.5/plugins/usi-dock/meson.build000066400000000000000000000011671420024370600202150ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginUsiDock"'] install_data(['usi-dock.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_usi_dock', fu_hash, sources : [ 'fu-usi-dock-common.c', 'fu-usi-dock-child-device.c', 'fu-usi-dock-dmc-device.c', 'fu-usi-dock-mcu-device.c', 'fu-plugin-usi-dock.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) endif fwupd-1.7.5/plugins/usi-dock/usi-dock.quirk000066400000000000000000000005261420024370600206440ustar00rootroot00000000000000[USB\VID_17EF&PID_30B4] Plugin = usi_dock GType = FuUsiDockMcuDevice Name = ThinkPad Thunderbolt 4 Dock [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.7.5/plugins/vli/000077500000000000000000000000001420024370600151225ustar00rootroot00000000000000fwupd-1.7.5/plugins/vli/README.md000066400000000000000000000036751420024370600164140ustar00rootroot00000000000000# 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` * `USB\VID_17EF` 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 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. fwupd-1.7.5/plugins/vli/fu-plugin-vli.c000066400000000000000000000015701420024370600177670ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-firmware.h" static void fu_plugin_vli_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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); fu_context_add_quirk_key(ctx, "VliDeviceKind"); fu_context_add_quirk_key(ctx, "VliSpiAutoDetect"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_vli_init; } fwupd-1.7.5/plugins/vli/fu-self-test.c000066400000000000000000000012541420024370600176060ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-common.h" static void fu_test_common_device_kind_func(void) { for (guint i = 0; i < 0xffff; i++) { const gchar *tmp = fu_vli_common_device_kind_to_string(i); if (tmp == NULL) continue; g_assert_cmpint(fu_vli_common_device_kind_from_string(tmp), ==, i); } } 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/common{device-kind}", fu_test_common_device_kind_func); return g_test_run(); } fwupd-1.7.5/plugins/vli/fu-vli-common.c000066400000000000000000000167461420024370600177740ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-common.h" const gchar * fu_vli_common_device_kind_to_string(FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return "VL100"; if (device_kind == FU_VLI_DEVICE_KIND_VL101) return "VL101"; if (device_kind == FU_VLI_DEVICE_KIND_VL102) return "VL102"; if (device_kind == FU_VLI_DEVICE_KIND_VL103) return "VL103"; if (device_kind == FU_VLI_DEVICE_KIND_VL104) return "VL104"; if (device_kind == FU_VLI_DEVICE_KIND_VL105) return "VL105"; if (device_kind == FU_VLI_DEVICE_KIND_VL810) return "VL810"; if (device_kind == FU_VLI_DEVICE_KIND_VL811) return "VL811"; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB0) return "VL811PB0"; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB3) return "VL811PB3"; if (device_kind == FU_VLI_DEVICE_KIND_VL812B0) return "VL812B0"; if (device_kind == FU_VLI_DEVICE_KIND_VL812B3) return "VL812B3"; if (device_kind == FU_VLI_DEVICE_KIND_VL812Q4S) return "VL812Q4S"; if (device_kind == FU_VLI_DEVICE_KIND_VL813) return "VL813"; if (device_kind == FU_VLI_DEVICE_KIND_VL815) return "VL815"; if (device_kind == FU_VLI_DEVICE_KIND_VL817) return "VL817"; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q7) return "VL819Q7"; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q8) return "VL819Q8"; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q7) return "VL820Q7"; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return "VL820Q8"; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q7) return "VL821Q7"; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q8) return "VL821Q8"; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q5) return "VL822Q5"; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q7) return "VL822Q7"; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q8) return "VL822Q8"; if (device_kind == FU_VLI_DEVICE_KIND_VL120) return "VL120"; if (device_kind == FU_VLI_DEVICE_KIND_VL210) return "VL210"; if (device_kind == FU_VLI_DEVICE_KIND_VL211) return "VL211"; if (device_kind == FU_VLI_DEVICE_KIND_VL212) return "VL212"; if (device_kind == FU_VLI_DEVICE_KIND_MSP430) return "MSP430"; if (device_kind == FU_VLI_DEVICE_KIND_PS186) return "PS186"; if (device_kind == FU_VLI_DEVICE_KIND_RTD21XX) return "RTD21XX"; return NULL; } FuVliDeviceKind fu_vli_common_device_kind_from_string(const gchar *device_kind) { if (g_strcmp0(device_kind, "VL100") == 0) return FU_VLI_DEVICE_KIND_VL100; if (g_strcmp0(device_kind, "VL101") == 0) return FU_VLI_DEVICE_KIND_VL101; if (g_strcmp0(device_kind, "VL102") == 0) return FU_VLI_DEVICE_KIND_VL102; if (g_strcmp0(device_kind, "VL103") == 0) return FU_VLI_DEVICE_KIND_VL103; if (g_strcmp0(device_kind, "VL104") == 0) return FU_VLI_DEVICE_KIND_VL104; if (g_strcmp0(device_kind, "VL105") == 0) return FU_VLI_DEVICE_KIND_VL105; if (g_strcmp0(device_kind, "VL810") == 0) return FU_VLI_DEVICE_KIND_VL810; if (g_strcmp0(device_kind, "VL811") == 0) return FU_VLI_DEVICE_KIND_VL811; if (g_strcmp0(device_kind, "VL811PB0") == 0) return FU_VLI_DEVICE_KIND_VL811PB0; if (g_strcmp0(device_kind, "VL811PB3") == 0) return FU_VLI_DEVICE_KIND_VL811PB3; if (g_strcmp0(device_kind, "VL812B0") == 0) return FU_VLI_DEVICE_KIND_VL812B0; if (g_strcmp0(device_kind, "VL812B3") == 0) return FU_VLI_DEVICE_KIND_VL812B3; if (g_strcmp0(device_kind, "VL812Q4S") == 0) return FU_VLI_DEVICE_KIND_VL812Q4S; if (g_strcmp0(device_kind, "VL813") == 0) return FU_VLI_DEVICE_KIND_VL813; if (g_strcmp0(device_kind, "VL815") == 0) return FU_VLI_DEVICE_KIND_VL815; if (g_strcmp0(device_kind, "VL817") == 0) return FU_VLI_DEVICE_KIND_VL817; if (g_strcmp0(device_kind, "VL819Q7") == 0) return FU_VLI_DEVICE_KIND_VL819Q7; if (g_strcmp0(device_kind, "VL819Q8") == 0) return FU_VLI_DEVICE_KIND_VL819Q8; if (g_strcmp0(device_kind, "VL820Q7") == 0) return FU_VLI_DEVICE_KIND_VL820Q7; if (g_strcmp0(device_kind, "VL820Q8") == 0) return FU_VLI_DEVICE_KIND_VL820Q8; if (g_strcmp0(device_kind, "VL821Q7") == 0) return FU_VLI_DEVICE_KIND_VL821Q7; if (g_strcmp0(device_kind, "VL821Q8") == 0) return FU_VLI_DEVICE_KIND_VL821Q8; if (g_strcmp0(device_kind, "VL822Q5") == 0) return FU_VLI_DEVICE_KIND_VL822Q5; if (g_strcmp0(device_kind, "VL822Q7") == 0) return FU_VLI_DEVICE_KIND_VL822Q7; if (g_strcmp0(device_kind, "VL822Q8") == 0) return FU_VLI_DEVICE_KIND_VL822Q8; if (g_strcmp0(device_kind, "VL120") == 0) return FU_VLI_DEVICE_KIND_VL120; if (g_strcmp0(device_kind, "VL210") == 0) return FU_VLI_DEVICE_KIND_VL210; if (g_strcmp0(device_kind, "VL211") == 0) return FU_VLI_DEVICE_KIND_VL211; if (g_strcmp0(device_kind, "VL212") == 0) return FU_VLI_DEVICE_KIND_VL212; if (g_strcmp0(device_kind, "MSP430") == 0) return FU_VLI_DEVICE_KIND_MSP430; if (g_strcmp0(device_kind, "PS186") == 0) return FU_VLI_DEVICE_KIND_PS186; if (g_strcmp0(device_kind, "RTD21XX") == 0) return FU_VLI_DEVICE_KIND_RTD21XX; return FU_VLI_DEVICE_KIND_UNKNOWN; } 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_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 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL817) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q7) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q8) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q7) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q7) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q8) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q5) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q7) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q8) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_PS186) return 0x40000; 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; return 0x0; } fwupd-1.7.5/plugins/vli/fu-vli-common.h000066400000000000000000000035511420024370600177670ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_VLI_DEVICE_KIND_UNKNOWN = 0x0000, FU_VLI_DEVICE_KIND_VL100 = 0x0100, FU_VLI_DEVICE_KIND_VL101 = 0x0101, FU_VLI_DEVICE_KIND_VL102 = 0x0102, FU_VLI_DEVICE_KIND_VL103 = 0x0103, FU_VLI_DEVICE_KIND_VL104 = 0x0104, FU_VLI_DEVICE_KIND_VL105 = 0x0105, FU_VLI_DEVICE_KIND_VL120 = 0x0120, FU_VLI_DEVICE_KIND_VL210 = 0x0210, FU_VLI_DEVICE_KIND_VL211 = 0x0211, FU_VLI_DEVICE_KIND_VL212 = 0x0212, FU_VLI_DEVICE_KIND_VL810 = 0x0810, FU_VLI_DEVICE_KIND_VL811 = 0x0811, FU_VLI_DEVICE_KIND_VL811PB0 = 0x8110, FU_VLI_DEVICE_KIND_VL811PB3 = 0x8113, FU_VLI_DEVICE_KIND_VL812B0 = 0xa812, FU_VLI_DEVICE_KIND_VL812B3 = 0xb812, FU_VLI_DEVICE_KIND_VL812Q4S = 0xc812, FU_VLI_DEVICE_KIND_VL813 = 0x0813, FU_VLI_DEVICE_KIND_VL815 = 0x0815, FU_VLI_DEVICE_KIND_VL817 = 0x0817, FU_VLI_DEVICE_KIND_VL819Q7 = 0xa819, /* guessed */ FU_VLI_DEVICE_KIND_VL819Q8 = 0xb819, /* guessed */ FU_VLI_DEVICE_KIND_VL820Q7 = 0xa820, FU_VLI_DEVICE_KIND_VL820Q8 = 0xb820, FU_VLI_DEVICE_KIND_VL821Q7 = 0xa821, /* guessed */ FU_VLI_DEVICE_KIND_VL821Q8 = 0xb821, /* guessed */ FU_VLI_DEVICE_KIND_VL822Q5 = 0x0822, /* guessed */ FU_VLI_DEVICE_KIND_VL822Q7 = 0xa822, /* guessed */ FU_VLI_DEVICE_KIND_VL822Q8 = 0xb822, /* guessed */ FU_VLI_DEVICE_KIND_MSP430 = 0xf430, /* guessed */ FU_VLI_DEVICE_KIND_PS186 = 0xf186, /* guessed */ FU_VLI_DEVICE_KIND_RTD21XX = 0xff00, /* guessed */ } FuVliDeviceKind; const gchar * fu_vli_common_device_kind_to_string(FuVliDeviceKind device_kind); FuVliDeviceKind fu_vli_common_device_kind_from_string(const gchar *device_kind); guint32 fu_vli_common_device_kind_get_size(FuVliDeviceKind device_kind); guint32 fu_vli_common_device_kind_get_offset(FuVliDeviceKind device_kind); fwupd-1.7.5/plugins/vli/fu-vli-device.c000066400000000000000000000473571420024370600177450ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-device.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; } g_usleep(500 * 1000); } 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); 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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len); } 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 */ if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) 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; } g_usleep(800); /* 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_common_bytes_compare_raw(buf, bufsz, buf_tmp, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1); /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1); 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_progress_sleep(fu_progress_get_child(progress), 4000); 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); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) 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_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len); } 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"); } /* 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 */ if (priv->kind != FU_VLI_DEVICE_KIND_UNKNOWN) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autofree gchar *devid1 = NULL; devid1 = g_strdup_printf("USB\\VID_%04X&PID_%04X&DEV_%s", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device), fu_vli_common_device_kind_to_string(priv->kind)); fu_device_add_instance_id(FU_DEVICE(self), devid1); } } 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_common_string_append_kv(str, idt, "DeviceKind", fu_vli_common_device_kind_to_string(priv->kind)); } fu_common_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_common_string_append_kv(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; } if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "SpiCmdReadId", buf, sizeof(buf)); if (priv->spi_cmd_read_id_sz == 4) { if (!fu_common_read_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_common_read_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_common_read_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) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); 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 *devid1 = NULL; g_autofree gchar *devid2 = NULL; 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_probe(FU_DEVICE(priv->cfi_device), error)) return FALSE; /* add extra instance IDs to include the SPI variant */ devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X&SPI_%s&REV_%04X", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device), flash_id, g_usb_device_get_release(usb_device)); fu_device_add_instance_id(device, devid2); devid1 = g_strdup_printf("USB\\VID_%04X&PID_%04X&SPI_%s", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device), flash_id); fu_device_add_instance_id(device, devid1); } } /* 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_common_strtoull_full(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_common_strtoull_full(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_common_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); } 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.7.5/plugins/vli/fu-vli-device.h000066400000000000000000000045421420024370600177370ustar00rootroot00000000000000/* * 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.7.5/plugins/vli/fu-vli-pd-common.c000066400000000000000000000013631420024370600203620ustar00rootroot00000000000000/* * 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" FuVliDeviceKind fu_vli_pd_common_guess_device_kind(guint32 fwver) { guint32 tmp = (fwver & 0x0f000000) >> 24; if (tmp == 0x01 || tmp == 0x02 || tmp == 0x03) return FU_VLI_DEVICE_KIND_VL100; if (tmp == 0x04 || tmp == 0x05 || tmp == 0x06) return FU_VLI_DEVICE_KIND_VL101; if (tmp == 0x07 || tmp == 0x08) return FU_VLI_DEVICE_KIND_VL102; if (tmp == 0x09 || tmp == 0x0a) return FU_VLI_DEVICE_KIND_VL103; if (tmp == 0x0b) return FU_VLI_DEVICE_KIND_VL104; if (tmp == 0x0c) return FU_VLI_DEVICE_KIND_VL105; return FU_VLI_DEVICE_KIND_UNKNOWN; } fwupd-1.7.5/plugins/vli/fu-vli-pd-common.h000066400000000000000000000007751420024370600203750ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" typedef struct __attribute__((packed)) { guint32 fwver; /* BE */ guint16 vid; /* LE */ guint16 pid; /* LE */ } FuVliPdHdr; #define VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY 0x4000 #define VLI_USBHUB_PD_FLASHMAP_ADDR 0x1003 FuVliDeviceKind fu_vli_pd_common_guess_device_kind(guint32 fwver); fwupd-1.7.5/plugins/vli/fu-vli-pd-device.c000066400000000000000000000611301420024370600203270ustar00rootroot00000000000000/* * Copyright (C) 2015 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-pd-parade-device.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) 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) { 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; } if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) { g_autofree gchar *title = g_strdup_printf("ReadRegs@0x%x", addr); fu_common_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) { if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) { g_autofree gchar *title = g_strdup_printf("WriteReg@0x%x", addr); fu_common_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 */ g_usleep(100 * 1000); 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)) { g_prefix_error(error, "failed to write enable SPI: "); 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; 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; 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, (guint8 *)buf, 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 tmp = 0; g_autofree gchar *version_str = NULL; /* 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_common_read_uint32_safe(verbuf, sizeof(verbuf), 0x0, &version_raw, G_BIG_ENDIAN, error)) return FALSE; fu_device_set_version_raw(FU_DEVICE(self), version_raw); version_str = fu_common_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(FU_DEVICE(self), version_str); /* 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, 0x0018, &tmp, error)) return FALSE; switch (tmp & 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; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to map 0x0018=0x%02X to device kind", tmp); return FALSE; } } /* get bootloader mode */ if (!fu_vli_pd_device_read_reg(self, 0x00F7, &tmp, error)) return FALSE; if ((tmp & 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_common_device_kind_to_string(device_kind), fu_vli_common_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45); /* 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_common_read_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_common_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, 0x0003, &tmp, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, 0x0003, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 37); /* erase */ if (!fu_vli_device_spi_erase_all(FU_VLI_DEVICE(self), fu_progress_get_child(progress), error)) 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; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* write GPIOs */ if (!fu_vli_pd_device_write_gpios(self, 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_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, 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 tmp = 0; if (!fu_vli_pd_device_read_reg(self, 0x0018, &tmp, error)) return FALSE; if (tmp != 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_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_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_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_pd_device_init(FuVliPdDevice *self) { fu_device_add_icon(FU_DEVICE(self), "audio-card"); 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"); /* 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.7.5/plugins/vli/fu-vli-pd-device.h000066400000000000000000000006031420024370600203320ustar00rootroot00000000000000/* * 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) struct _FuVliPdDeviceClass { FuVliDeviceClass parent_class; }; fwupd-1.7.5/plugins/vli/fu-vli-pd-firmware.c000066400000000000000000000114551420024370600207110ustar00rootroot00000000000000/* * 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" struct _FuVliPdFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; FuVliPdHdr hdr; }; 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; } guint16 fu_vli_pd_firmware_get_vid(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return GUINT16_FROM_LE(self->hdr.vid); } guint16 fu_vli_pd_firmware_get_pid(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return GUINT16_FROM_LE(self->hdr.pid); } static gboolean fu_vli_pd_firmware_validate_header(FuVliPdFirmware *self) { if (GUINT16_FROM_LE(self->hdr.vid) == 0x2109) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x17EF) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x2D01) return TRUE; if (GUINT16_FROM_LE(self->hdr.vid) == 0x06C4) 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_common_device_kind_to_string(self->device_kind)); fu_xmlb_builder_insert_kx(bn, "vid", fu_vli_pd_firmware_get_vid(self)); fu_xmlb_builder_insert_kx(bn, "pid", fu_vli_pd_firmware_get_pid(self)); } static gboolean fu_vli_pd_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, 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; /* map header from new offset location */ if (!fu_memcpy_safe((guint8 *)&self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR, sizeof(self->hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } /* fall back to legacy location */ if (!fu_vli_pd_firmware_validate_header(self)) { if (!fu_memcpy_safe((guint8 *)&self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY, sizeof(self->hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } } /* 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 = GUINT32_FROM_BE(self->hdr.fwver); 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_common_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_common_read_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_common_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; } } /* whole image */ fu_firmware_set_bytes(firmware, fw); 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.7.5/plugins/vli/fu-vli-pd-firmware.h000066400000000000000000000011271420024370600207110ustar00rootroot00000000000000/* * 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); guint16 fu_vli_pd_firmware_get_vid(FuVliPdFirmware *self); guint16 fu_vli_pd_firmware_get_pid(FuVliPdFirmware *self); fwupd-1.7.5/plugins/vli/fu-vli-pd-parade-device.c000066400000000000000000000533741420024370600215740ustar00rootroot00000000000000/* * Copyright (C) 2015 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-device.h" #include "fu-vli-pd-parade-device.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_common_string_append_kv(str, idt, "DeviceKind", fu_vli_common_device_kind_to_string(self->device_kind)); fu_common_string_append_kx(str, idt, "Page2", self->page2); fu_common_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; g_usleep(1000 * 10); 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"); 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)); FuChunk *chk0; 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 36); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* 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, 0x0, 0x10000); for (guint i = 1; i < blocks->len; i++) { FuChunk *chk = g_ptr_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, blocks->len); } 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 */ g_usleep(1000 * 100); if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x82, 0x00, error)) return FALSE; /* write blocks */ for (guint i = 1; i < blocks->len; i++) { FuChunk *chk = g_ptr_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, blocks->len); } 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 = g_ptr_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 < blocks->len; i++) { FuChunk *chk = g_ptr_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, blocks->len); } fw_verify = g_byte_array_free_to_bytes(g_steal_pointer(&buf_verify)); if (!fu_common_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)); blocks = fu_chunk_array_mutable_new(fw->data, fw->len, 0x0, 0x0, 0x10000); 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_set_percentage_full(progress, i + 1, blocks->len); } return g_byte_array_free_to_bytes(g_steal_pointer(&fw)); } static gboolean fu_vli_pd_parade_device_probe(FuDevice *device, GError **error) { FuVliPdDevice *parent = FU_VLI_PD_DEVICE(fu_device_get_parent(device)); FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE(device); g_autofree gchar *instance_id1 = NULL; /* get version */ if (!fu_vli_pd_parade_device_read_fw_ver(self, error)) return FALSE; /* use header to populate device info */ instance_id1 = g_strdup_printf("USB\\VID_%04X&PID_%04X&I2C_%s", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent)), fu_vli_common_device_kind_to_string(self->device_kind)); fu_device_add_instance_id(device, instance_id1); /* success */ return TRUE; } 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_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.7.5/plugins/vli/fu-vli-pd-parade-device.h000066400000000000000000000010021420024370600215560ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) struct _FuVliPdParadeDeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_vli_pd_parade_device_new(FuVliDevice *parent); fwupd-1.7.5/plugins/vli/fu-vli-usbhub-common.c000066400000000000000000000035551420024370600212540ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-usbhub-common.h" guint8 fu_vli_usbhub_header_crc8(FuVliUsbhubHeader *hdr) { return ~fu_common_crc8((const guint8 *)hdr, sizeof(*hdr) - 1); } void fu_vli_usbhub_header_export(FuVliUsbhubHeader *hdr, XbBuilderNode *bn) { fu_xmlb_builder_insert_kx(bn, "dev_id", GUINT16_FROM_BE(hdr->dev_id)); fu_xmlb_builder_insert_kx(bn, "variant", hdr->variant); if (hdr->usb2_fw_sz > 0) { fu_xmlb_builder_insert_kx(bn, "usb2_fw_addr", GUINT16_FROM_BE(hdr->usb2_fw_addr)); fu_xmlb_builder_insert_kx(bn, "usb2_fw_sz", GUINT16_FROM_BE(hdr->usb2_fw_sz)); } fu_xmlb_builder_insert_kx(bn, "usb3_fw_addr", ((guint32)hdr->usb3_fw_addr_high) << 16 | GUINT16_FROM_BE(hdr->usb3_fw_addr)); fu_xmlb_builder_insert_kx(bn, "usb3_fw_sz", GUINT16_FROM_BE(hdr->usb3_fw_sz)); if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { fu_xmlb_builder_insert_kx(bn, "prev_ptr", VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->prev_ptr)); } if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) { fu_xmlb_builder_insert_kx(bn, "next_ptr", VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(hdr->next_ptr)); } fu_xmlb_builder_insert_kb(bn, "checksum_ok", hdr->checksum == fu_vli_usbhub_header_crc8(hdr)); } void fu_vli_usbhub_header_to_string(FuVliUsbhubHeader *hdr, guint idt, GString *str) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("header"); g_autofree gchar *xml = NULL; fu_vli_usbhub_header_export(hdr, bn); xml = xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | #if LIBXMLB_CHECK_VERSION(0, 2, 2) XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | #endif XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); fu_common_string_append_kv(str, idt, "xml", xml); } fwupd-1.7.5/plugins/vli/fu-vli-usbhub-common.h000066400000000000000000000044461420024370600212610ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" typedef struct __attribute__((packed)) { guint16 dev_id; /* 0x00, BE */ guint8 strapping1; /* 0x02 */ guint8 strapping2; /* 0x03 */ guint16 usb3_fw_addr; /* 0x04, BE */ guint16 usb3_fw_sz; /* 0x06, BE */ guint16 usb2_fw_addr; /* 0x08, BE */ guint16 usb2_fw_sz; /* 0x0a, BE */ guint8 usb3_fw_addr_high; /* 0x0c */ guint8 unknown_0d[3]; /* 0x0d */ guint8 usb2_fw_addr_high; /* 0x10 */ guint8 unknown_11[10]; /* 0x11 */ guint8 inverse_pe41; /* 0x1b */ guint8 prev_ptr; /* 0x1c, addr / 0x20 */ guint8 next_ptr; /* 0x1d, addr / 0x20 */ guint8 variant; /* 0x1e */ guint8 checksum; /* 0x1f */ } FuVliUsbhubHeader; G_STATIC_ASSERT(sizeof(FuVliUsbhubHeader) == 0x20); #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 guint8 fu_vli_usbhub_header_crc8(FuVliUsbhubHeader *hdr); void fu_vli_usbhub_header_to_string(FuVliUsbhubHeader *hdr, guint idt, GString *str); void fu_vli_usbhub_header_export(FuVliUsbhubHeader *hdr, XbBuilderNode *bn); fwupd-1.7.5/plugins/vli/fu-vli-usbhub-device.c000066400000000000000000001122621420024370600212170ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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; FuVliUsbhubHeader hd1_hdr; /* factory */ FuVliUsbhubHeader hd2_hdr; /* 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) 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_common_string_append_kb(str, idt, "DisablePowersave", self->disable_powersave); fu_common_string_append_kx(str, idt, "UpdateProtocol", self->update_protocol); if (self->update_protocol >= 0x2) { fu_common_string_append_kv(str, idt, "H1Hdr@0x0", NULL); fu_vli_usbhub_header_to_string(&self->hd1_hdr, idt + 1, str); if (self->hd2_hdr.dev_id != 0xffff) { fu_common_string_append_kv(str, idt, "H2Hdr@0x1000", NULL); fu_vli_usbhub_header_to_string(&self->hd2_hdr, idt + 1, str); } } } 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 */ g_usleep(100 * 1000); 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; 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); 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, (guint8 *)buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } 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; /* 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_debug("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_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; } if (!fu_vli_usbhub_device_read_reg(self, 0xf63f, &chipver2, error)) { g_prefix_error(error, "Read_ChipVer2 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf800, &b811P812, error)) { g_prefix_error(error, "Read_811P812 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf88e, &chipid1, error)) { g_prefix_error(error, "Read_ChipID1 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf88f, &chipid2, error)) { g_prefix_error(error, "Read_ChipID2 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf64e, &chipid12, error)) { g_prefix_error(error, "Read_ChipID12 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf64f, &chipid22, error)) { g_prefix_error(error, "Read_ChipID22 failed: "); return FALSE; } if (!fu_vli_usbhub_device_read_reg(self, 0xf651, &pkgtype, error)) { g_prefix_error(error, "Read_820Q7Q8 failed: "); return FALSE; } if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) { g_debug("chipver = 0x%02x", chipver); g_debug("chipver2 = 0x%02x", chipver2); g_debug("b811P812 = 0x%02x", b811P812); g_debug("chipid1 = 0x%02x", chipid1); g_debug("chipid2 = 0x%02x", chipid2); g_debug("chipid12 = 0x%02x", chipid12); g_debug("chipid22 = 0x%02x", chipid22); 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 == 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 (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) { guint16 usbver = fu_usb_device_get_spec(FU_USB_DEVICE(device)); /* FuUsbDevice->probe */ if (!FU_DEVICE_CLASS(fu_vli_usbhub_device_parent_class)->probe(device, error)) return FALSE; /* 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"); } 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; /* 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, (guint8 *)&self->hd1_hdr, sizeof(self->hd1_hdr), &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, (guint8 *)&self->hd1_hdr, sizeof(self->hd1_hdr), 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, (guint8 *)&self->hd1_hdr, sizeof(self->hd1_hdr), error)) { g_prefix_error(error, "failed to read HD1 header: "); return FALSE; } /* detect update protocol from the device ID */ switch (GUINT16_FROM_BE(self->hd1_hdr.dev_id) >> 8) { /* VL810~VL813 */ case 0x0d: 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 0x05: 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; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "hardware is not supported, dev_id=0x%x", (guint)GUINT16_FROM_BE(self->hd1_hdr.dev_id)); 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, (guint8 *)&self->hd2_hdr, sizeof(self->hd2_hdr), 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; guint16 device_id; 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_common_device_kind_to_string(device_kind), fu_vli_common_device_kind_to_string( fu_vli_device_get_kind(FU_VLI_DEVICE(self)))); return NULL; } device_id = fu_vli_usbhub_firmware_get_device_id(FU_VLI_USBHUB_FIRMWARE(firmware)); if (GUINT16_FROM_BE(self->hd1_hdr.dev_id) != device_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", device_id, (guint)GUINT16_FROM_BE(self->hd1_hdr.dev_id)); return NULL; } /* we could check this against flags */ g_debug("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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); /* 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(FuVliUsbhubHeader *hdr) { if (hdr->prev_ptr != VLI_USBHUB_FLASHMAP_IDX_INVALID) return FALSE; if (hdr->checksum != fu_vli_usbhub_header_crc8(hdr)) return FALSE; return TRUE; } static gboolean fu_vli_usbhub_device_hd1_recover(FuVliUsbhubDevice *self, FuVliUsbhubHeader *hdr, FuProgress *progress, GError **error) { /* point to HD2, i.e. updated firmware */ if (hdr->next_ptr != VLI_USBHUB_FLASHMAP_IDX_HD2) { hdr->next_ptr = VLI_USBHUB_FLASHMAP_IDX_HD2; hdr->checksum = 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, (const guint8 *)hdr, sizeof(FuVliUsbhubHeader), 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 */ memcpy(&self->hd1_hdr, hdr, sizeof(self->hd1_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; FuVliUsbhubHeader hdr = {0x0}; 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->hd1_hdr)) { /* no update has ever been done */ if (self->hd1_hdr.next_ptr != 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, (const guint8 *)&self->hd1_hdr, sizeof(hdr), progress, error)) { g_prefix_error(error, "failed to write block at header 1: "); return FALSE; } if (!fu_vli_usbhub_device_hd1_recover(self, &self->hd1_hdr, progress, error)) { g_prefix_error(error, "failed to write header: "); return FALSE; } } /* copy the header from the backup zone */ } else { g_debug("HD1 was invalid, reading backup"); if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, (guint8 *)&self->hd1_hdr, sizeof(hdr), 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->hd1_hdr)) { g_debug("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->hd1_hdr, 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 = GUINT16_FROM_BE(self->hd1_hdr.usb3_fw_sz); 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); memcpy(&hdr, buf_fw, sizeof(hdr)); hd2_fw_sz = GUINT16_FROM_BE(hdr.usb3_fw_sz); hd2_fw_offset = GUINT16_FROM_BE(hdr.usb3_fw_addr); 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20); 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); /* map into header */ if (!fu_memcpy_safe((guint8 *)&self->hd2_hdr, sizeof(hdr), 0x0, buf_fw, buf_fwsz, 0x0, sizeof(hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } /* write new HD2 */ self->hd2_hdr.usb3_fw_addr = GUINT16_TO_BE(hd2_fw_addr & 0xffff); self->hd2_hdr.usb3_fw_addr_high = (guint8)(hd2_fw_addr >> 16); self->hd2_hdr.prev_ptr = VLI_USBHUB_FLASHMAP_IDX_HD1; self->hd2_hdr.next_ptr = VLI_USBHUB_FLASHMAP_IDX_INVALID; self->hd2_hdr.checksum = fu_vli_usbhub_header_crc8(&self->hd2_hdr); 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, (const guint8 *)&self->hd2_hdr, sizeof(self->hd2_hdr), fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write HD2: "); return FALSE; } fu_progress_step_done(progress); /* success */ 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); /* 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) { fu_device_add_icon(FU_DEVICE(self), "audio-card"); 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_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"); } static void fu_vli_usbhub_device_class_init(FuVliUsbhubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS(klass); 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.7.5/plugins/vli/fu-vli-usbhub-device.h000066400000000000000000000006331420024370600212220ustar00rootroot00000000000000/* * 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) struct _FuVliUsbhubDeviceClass { FuVliDeviceClass parent_class; }; fwupd-1.7.5/plugins/vli/fu-vli-usbhub-firmware.c000066400000000000000000000173151420024370600215770ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-usbhub-firmware.h" struct _FuVliUsbhubFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; FuVliUsbhubHeader hdr; }; 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 GUINT16_FROM_BE(self->hdr.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_common_device_kind_to_string(self->device_kind)); fu_vli_usbhub_header_export(&self->hdr, bn); } static gboolean fu_vli_usbhub_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE(firmware); gsize bufsz = 0; guint16 adr_ofs = 0; guint16 version = 0x0; guint8 tmp = 0x0; guint8 fwtype = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* map into header */ if (!fu_memcpy_safe((guint8 *)&self->hdr, sizeof(self->hdr), 0x0, buf, bufsz, 0x0, sizeof(self->hdr), error)) { g_prefix_error(error, "failed to read header: "); return FALSE; } /* get firmware versions */ switch (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: /* VL81x */ if (!fu_common_read_uint16_safe(buf, bufsz, 0x1f4c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) { if (!fu_common_read_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_common_read_uint16_safe(buf, bufsz, 0x8f0c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } version |= (self->hdr.strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) version += 1; break; default: /* U3ID_Address_In_FW_Zone */ if (!fu_common_read_uint16_safe(buf, bufsz, 0x8000, &adr_ofs, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to get offset addr: "); return FALSE; } if (!fu_common_read_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 |= (self->hdr.strapping1 >> 4) & 0x07; } /* version is set */ if (version != 0x0) { g_autofree gchar *version_str = NULL; version_str = fu_common_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 (GUINT16_FROM_BE(self->hdr.dev_id)) { case 0x0d12: { guint16 binver1 = 0x0; guint16 binver2 = 0x0; guint16 usb2_fw_addr = GUINT16_FROM_BE(self->hdr.usb2_fw_addr) + 0x1ff1; guint16 usb3_fw_addr = GUINT16_FROM_BE(self->hdr.usb3_fw_addr) + 0x1ffa; if (!fu_common_read_uint16_safe(buf, bufsz, usb2_fw_addr, &binver1, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get binver1: "); return FALSE; } if (!fu_common_read_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 (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S) { self->device_kind = FU_VLI_DEVICE_KIND_VL812Q4S; /* VL812 == VT3470 (812/813) */ } else if (self->hdr.strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN) { /* is B3 */ if (self->hdr.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 (self->hdr.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_common_read_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_common_read_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_common_read_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 (!fu_common_read_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 0x0538: /* VL817 == VT3538 */ self->device_kind = FU_VLI_DEVICE_KIND_VL817; break; case 0x0553: /* VL120 == VT3553 */ self->device_kind = FU_VLI_DEVICE_KIND_VL120; break; default: break; } /* whole image */ fu_firmware_set_bytes(firmware, fw); 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.7.5/plugins/vli/fu-vli-usbhub-firmware.h000066400000000000000000000011641420024370600215770ustar00rootroot00000000000000/* * 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.7.5/plugins/vli/fu-vli-usbhub-i2c-common.c000066400000000000000000000026041420024370600217210ustar00rootroot00000000000000/* * 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.7.5/plugins/vli/fu-vli-usbhub-i2c-common.h000066400000000000000000000010621420024370600217230ustar00rootroot00000000000000/* * 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.7.5/plugins/vli/fu-vli-usbhub-msp430-device.c000066400000000000000000000250661420024370600222500ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-device.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; } if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_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; if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "I2cWriteData", 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, I2C_W_VDR, value, 0x0, (guint8 *)buf, 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_progress_sleep(progress, 1000); /* 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; g_usleep(5 * 1000); 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 */ g_usleep(5 * 1000); 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_status(progress, FWUPD_STATUS_DEVICE_WRITE); 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_set_percentage_full(progress, (gsize)j + 1, (gsize)records->len); } /* 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)); g_autofree gchar *instance_id = NULL; fu_device_set_name(device, fu_vli_common_device_kind_to_string(device_kind)); fu_device_set_physical_id(device, fu_device_get_physical_id(FU_DEVICE(parent))); /* add instance ID */ instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&I2C_%s", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent)), fu_vli_common_device_kind_to_string(device_kind)); fu_device_add_instance_id(device, instance_id); return TRUE; } 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), "audio-card"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.i2c"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); 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.7.5/plugins/vli/fu-vli-usbhub-msp430-device.h000066400000000000000000000010051420024370600222400ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) struct _FuVliUsbhubMsp430DeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_vli_usbhub_msp430_device_new(FuVliUsbhubDevice *parent); fwupd-1.7.5/plugins/vli/fu-vli-usbhub-pd-device.c000066400000000000000000000240241420024370600216160ustar00rootroot00000000000000/* * 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-usbhub-common.h" #include "fu-vli-usbhub-device.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_common_string_append_kv(str, idt, "DeviceKind", fu_vli_common_device_kind_to_string(self->device_kind)); fu_common_string_append_kx(str, idt, "FwOffset", fu_vli_common_device_kind_get_offset(self->device_kind)); fu_common_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) { FuVliPdHdr hdr = {0x0}; FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); guint32 fwver; g_autofree gchar *fwver_str = NULL; g_autofree gchar *instance_id0 = NULL; g_autofree gchar *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; g_autofree gchar *instance_id3 = 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, (guint8 *)&hdr, sizeof(hdr), error)) { g_prefix_error(error, "failed to read legacy PD header: "); return FALSE; } /* new location */ if (GUINT16_FROM_LE(hdr.vid) != 0x2109) { g_debug("PD VID was 0x%04x trying new location", GUINT16_FROM_LE(hdr.vid)); if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(parent), VLI_USBHUB_FLASHMAP_ADDR_PD + VLI_USBHUB_PD_FLASHMAP_ADDR, (guint8 *)&hdr, sizeof(hdr), error)) { g_prefix_error(error, "failed to read PD header: "); return FALSE; } } /* just empty space */ if (hdr.fwver == G_MAXUINT32) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no PD device header found"); return FALSE; } /* get version */ fwver = GUINT32_FROM_BE(hdr.fwver); 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; } fu_device_set_name(device, fu_vli_common_device_kind_to_string(self->device_kind)); /* use header to populate device info */ fu_device_set_version_raw(device, fwver); fwver_str = fu_common_version_from_uint32(fwver, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, fwver_str); instance_id0 = g_strdup_printf("USB\\VID_%04X&PID_%04X&APP_%02X", GUINT16_FROM_LE(hdr.vid), GUINT16_FROM_LE(hdr.pid), fwver & 0xff); fu_device_add_instance_id(device, instance_id0); instance_id1 = g_strdup_printf("USB\\VID_%04X&PID_%04X&DEV_%s", GUINT16_FROM_LE(hdr.vid), GUINT16_FROM_LE(hdr.pid), fu_vli_common_device_kind_to_string(self->device_kind)); fu_device_add_instance_id(device, instance_id1); /* add standard GUIDs in order of priority */ instance_id2 = g_strdup_printf("USB\\VID_%04X&PID_%04X", GUINT16_FROM_LE(hdr.vid), GUINT16_FROM_LE(hdr.pid)); fu_device_add_instance_id(device, instance_id2); instance_id3 = g_strdup_printf("USB\\VID_%04X", GUINT16_FROM_LE(hdr.vid)); fu_device_add_instance_id_full(device, instance_id3, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* 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_common_device_kind_to_string(device_kind), fu_vli_common_device_kind_to_string(self->device_kind)); 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_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 22); /* 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), "audio-card"); 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.7.5/plugins/vli/fu-vli-usbhub-pd-device.h000066400000000000000000000007511420024370600216240ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) struct _FuVliUsbhubPdDeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_vli_usbhub_pd_device_new(FuVliUsbhubDevice *parent); fwupd-1.7.5/plugins/vli/fu-vli-usbhub-rtd21xx-device.c000066400000000000000000000407311420024370600225320ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-usbhub-device.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 5000 /* us */ #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; if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_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; } g_usleep(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; } if (g_getenv("FWUPD_VLI_USBHUB_VERBOSE") != NULL) fu_common_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 */ g_usleep(300000); 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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10); /* restart */ /* 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 */ g_usleep(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_common_read_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_common_write_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, /* start addr */ 0x00, /* page_sz */ ISP_DATA_BLOCKSIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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_progress_sleep(progress, 20000); /* 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)); g_autofree gchar *instance_id = NULL; fu_device_set_name(device, fu_vli_common_device_kind_to_string(device_kind)); fu_device_set_physical_id(device, fu_device_get_physical_id(FU_DEVICE(parent))); /* add instance ID */ instance_id = g_strdup_printf("USB\\VID_%04X&PID_%04X&I2C_%s", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent)), fu_vli_common_device_kind_to_string(device_kind)); fu_device_add_instance_id(device, instance_id); return TRUE; } 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.7.5/plugins/vli/fu-vli-usbhub-rtd21xx-device.h000066400000000000000000000010141420024370600225260ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #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) struct _FuVliUsbhubRtd21xxDeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_vli_usbhub_rtd21xx_device_new(FuVliUsbhubDevice *parent); fwupd-1.7.5/plugins/vli/meson.build000066400000000000000000000027751420024370600172770ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginVliUsbhub"'] install_data([ 'vli-pd.quirk', 'vli-usbhub.quirk', 'vli-usbhub-lenovo.quirk', 'vli-usbhub-hyper.quirk', 'vli-usbhub-bizlink.quirk', 'vli-usbhub-minibons.quirk', ], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_vli', fu_hash, sources : [ 'fu-plugin-vli.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-common.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 : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, link_with : [ fwupd, fwupdplugin, ], c_args : cargs, dependencies : [ plugin_deps, ], ) if get_option('tests') e = executable( 'vli-self-test', fu_hash, sources : [ 'fu-self-test.c', 'fu-vli-common.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, install : true, install_dir : installed_test_bindir, ) test('vli-self-test', e) # added to installed-tests endif endif fwupd-1.7.5/plugins/vli/vli-pd.quirk000066400000000000000000000044611420024370600173770ustar00rootroot00000000000000# Generic [USB\VID_0800&PID_0800] Plugin = vli GType = FuVliPdDevice 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 [USB\VID_2109&PID_D101] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_D102] Plugin = vli GType = FuVliPdDevice # 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 # Samsung [USB\VID_04E8&PID_A025] Plugin = vli GType = FuVliPdDevice [USB\VID_04E8&PID_A048] 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 [USB\VID_2109&PID_888C] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_888D] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_888E] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_888F] Plugin = vli GType = FuVliPdDevice fwupd-1.7.5/plugins/vli/vli-usbhub-bizlink.quirk000066400000000000000000000005741420024370600217250ustar00rootroot00000000000000# 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.7.5/plugins/vli/vli-usbhub-hyper.quirk000066400000000000000000000004071420024370600214050ustar00rootroot00000000000000# 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.7.5/plugins/vli/vli-usbhub-lenovo.quirk000066400000000000000000000141251420024370600215620ustar00rootroot00000000000000# 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 [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 Modularized dock (with VIA USB PID?!) [USB\VID_2109&PID_1822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_2109&PID_2822 [USB\VID_2109&PID_2822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [USB\VID_2109&PID_3822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_2109&PID_1822 CounterpartGuid = USB\VID_2109&PID_4822 [USB\VID_2109&PID_4822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_2109&PID_2822 fwupd-1.7.5/plugins/vli/vli-usbhub-minibons.quirk000066400000000000000000000004171420024370600220750ustar00rootroot00000000000000#Goodway [USB\VID_065F&PID_F817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_065F&PID_F816 ParentGuid = USB\VID_1F29&PID_7518 [USB\VID_065F&PID_F816] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_065F&PID_F817 fwupd-1.7.5/plugins/vli/vli-usbhub.quirk000066400000000000000000000027111420024370600202600ustar00rootroot00000000000000# 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 [USB\VID_2109&PID_0212] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_2212] 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,has-shared-spi-pd [USB\VID_2109&PID_2820] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd # VL822 [USB\VID_2109&PID_0822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd [USB\VID_2109&PID_2822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,has-shared-spi-pd fwupd-1.7.5/plugins/wacom-raw/000077500000000000000000000000001420024370600162255ustar00rootroot00000000000000fwupd-1.7.5/plugins/wacom-raw/README.md000066400000000000000000000023431420024370600175060ustar00rootroot00000000000000# 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`. ## 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 ## 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. fwupd-1.7.5/plugins/wacom-raw/data/000077500000000000000000000000001420024370600171365ustar00rootroot00000000000000fwupd-1.7.5/plugins/wacom-raw/data/hid-recorder.txt000066400000000000000000001054561420024370600222610ustar00rootroot00000000000000# 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.7.5/plugins/wacom-raw/fu-plugin-wacom-raw.c000066400000000000000000000015051420024370600221730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wacom-aes-device.h" #include "fu-wacom-common.h" #include "fu-wacom-emr-device.h" static void fu_plugin_wacom_raw_init(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); 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"); fu_context_add_quirk_key(ctx, "WacomI2cFlashBlockSize"); fu_context_add_quirk_key(ctx, "WacomI2cFlashBaseAddr"); fu_context_add_quirk_key(ctx, "WacomI2cFlashSize"); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_wacom_raw_init; } fwupd-1.7.5/plugins/wacom-raw/fu-wacom-aes-device.c000066400000000000000000000164031420024370600221160ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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}}; g_autofree gchar *devid1 = NULL; g_autofree gchar *devid2 = NULL; 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; } devid1 = g_strdup_printf("HIDRAW\\VEN_2D1F&DEV_%04X", pid); devid2 = g_strdup_printf("HIDRAW\\VEN_056A&DEV_%04X", pid); fu_device_add_instance_id(device, devid1); fu_device_add_instance_id(device, devid2); return TRUE; } 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_common_read_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_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 * 1000, /* 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_progress_sleep(progress, 2000); 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, 1000, FU_WACOM_DEVICE_CMD_FLAG_NONE, 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, GPtrArray *chunks, FuProgress *progress, GError **error) { FuWacomAesDevice *self = FU_WACOM_AES_DEVICE(device); /* 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80); /* erase */ if (!fu_wacom_aes_device_erase_all(self, progress, error)) return FALSE; fu_progress_step_done(progress); /* write */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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_wac_device->write_firmware = fu_wacom_aes_device_write_firmware; } FuWacomAesDevice * fu_wacom_aes_device_new(FuUdevDevice *device) { FuWacomAesDevice *self = g_object_new(FU_TYPE_WACOM_AES_DEVICE, NULL); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return self; } fwupd-1.7.5/plugins/wacom-raw/fu-wacom-aes-device.h000066400000000000000000000005761420024370600221270ustar00rootroot00000000000000/* * 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) FuWacomAesDevice * fu_wacom_aes_device_new(FuUdevDevice *device); fwupd-1.7.5/plugins/wacom-raw/fu-wacom-common.c000066400000000000000000000043571420024370600214060ustar00rootroot00000000000000/* * 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.7.5/plugins/wacom-raw/fu-wacom-common.h000066400000000000000000000037711420024370600214120ustar00rootroot00000000000000/* * 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_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.7.5/plugins/wacom-raw/fu-wacom-device.c000066400000000000000000000255141420024370600213530ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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)) 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_common_string_append_kx(str, idt, "FlashBlockSize", priv->flash_block_size); fu_common_string_append_kx(str, idt, "FlashBaseAddr", priv->flash_base_addr); fu_common_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; } guint fu_wacom_device_get_base_addr(FuWacomDevice *self) { FuWacomDevicePrivate *priv = GET_PRIVATE(self); return priv->flash_base_addr; } 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_ONLY_QUIRKS); return TRUE; } /* W9021 */ if (rsp.resp == 0x45) { fu_device_add_instance_id_full(FU_DEVICE(self), "WacomEMR_W9021", FU_DEVICE_INSTANCE_FLAG_ONLY_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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_wacom_device_parent_class)->probe(device, error)) return FALSE; /* 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); 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)) { g_prefix_error(error, "failed to switch to bootloader mode: "); return FALSE; } g_usleep(300 * 1000); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_wacom_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; } /* only required on AES, but harmless for EMR */ g_usleep(300 * 1000); fu_device_remove_flag(FU_DEVICE(self), 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(GPtrArray) 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, 0x00, /* page_sz */ 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_common_dump_raw(G_LOG_DOMAIN, "SetFeature", data, datasz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(datasz), (guint8 *)data, NULL, 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, error)) return FALSE; fu_common_dump_raw(G_LOG_DOMAIN, "GetFeature", data, datasz); return TRUE; } gboolean fu_wacom_device_cmd(FuWacomDevice *self, FuWacomRawRequest *req, FuWacomRawResponse *rsp, gulong delay_us, 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; } if (delay_us > 0) g_usleep(delay_us); 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++) { if (delay_us > 0) g_usleep(delay_us); 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_common_strtoull_full(value, &tmp, 0, G_MAXSIZE, error)) return FALSE; priv->flash_block_size = tmp; return TRUE; } if (g_strcmp0(key, "WacomI2cFlashBaseAddr") == 0) { if (!fu_common_strtoull_full(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->flash_base_addr = tmp; return TRUE; } if (g_strcmp0(key, "WacomI2cFlashSize") == 0) { if (!fu_common_strtoull_full(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_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_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_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); } 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->attach = fu_wacom_device_attach; 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; } fwupd-1.7.5/plugins/wacom-raw/fu-wacom-device.h000066400000000000000000000025201420024370600213500ustar00rootroot00000000000000/* * 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, GPtrArray *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; 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, gulong delay_us, 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); guint fu_wacom_device_get_base_addr(FuWacomDevice *self); fwupd-1.7.5/plugins/wacom-raw/fu-wacom-emr-device.c000066400000000000000000000157501420024370600221350ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #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 */ g_autofree gchar *version = NULL; if (!fu_wacom_device_get_feature(FU_WACOM_DEVICE(self), data, sizeof(data), error)) return FALSE; if (!fu_common_read_uint16_safe(data, sizeof(data), 11, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); version = fu_common_version_from_uint16(fw_ver, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(device, version); fu_device_set_version_raw(device, fw_ver); } /* success */ return TRUE; } static guint8 fu_wacom_emr_device_calc_checksum(guint8 init1, const guint8 *buf, gsize bufsz) { return init1 + ~(fu_common_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, 50, 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, 50, 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 * 1000, /* 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_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[blocksz + 0] = fu_wacom_emr_device_calc_checksum(0x05 + 0x00 + 0x4c + 0x00, (const guint8 *)&req, 8); req.data[blocksz + 1] = fu_wacom_emr_device_calc_checksum(0x00, data, datasz); if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 50, 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, GPtrArray *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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); /* 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 < chunks->len; i++) { FuChunk *chk = g_ptr_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)chunks->len); } 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"); } 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_wac_device->write_firmware = fu_wacom_emr_device_write_firmware; } FuWacomEmrDevice * fu_wacom_emr_device_new(FuUdevDevice *device) { FuWacomEmrDevice *self = g_object_new(FU_TYPE_WACOM_EMR_DEVICE, NULL); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return self; } fwupd-1.7.5/plugins/wacom-raw/fu-wacom-emr-device.h000066400000000000000000000005761420024370600221420ustar00rootroot00000000000000/* * 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) FuWacomEmrDevice * fu_wacom_emr_device_new(FuUdevDevice *device); fwupd-1.7.5/plugins/wacom-raw/meson.build000066400000000000000000000011521420024370600203660ustar00rootroot00000000000000if get_option('gudev') cargs = ['-DG_LOG_DOMAIN="FuPluginWacomRaw"'] install_data(['wacom-raw.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_wacom_raw', fu_hash, sources : [ 'fu-plugin-wacom-raw.c', 'fu-wacom-common.c', 'fu-wacom-device.c', 'fu-wacom-aes-device.c', 'fu-wacom-emr-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) endif fwupd-1.7.5/plugins/wacom-raw/wacom-raw.quirk000066400000000000000000000037671420024370600212140ustar00rootroot00000000000000# 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. # Dell Chromebook Enterprise 5300 [HIDRAW\VEN_2D1F&DEV_4946] Plugin = wacom_raw Guid = WacomAES # Moffet 14-LGD-TPK [HIDRAW\VEN_2D1F&DEV_4970] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Moffet 14-Sharp-HH [HIDRAW\VEN_2D1F&DEV_4971] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Moffet 14-Sharp-VIA [HIDRAW\VEN_2D1F&DEV_4972] Plugin = wacom_raw Guid = WacomAES Flags = self-recovery # Dell Latitude 5175 [HIDRAW\VEN_056A&DEV_4807] Plugin = wacom_raw Guid = WacomAES # Dell XPS 12 9250 [HIDRAW\VEN_056A&DEV_4822] Plugin = wacom_raw Guid = WacomAES # Dell Venue 8 Pro 5855 [HIDRAW\VEN_056A&DEV_4824] Plugin = wacom_raw Guid = WacomAES # Dell XPS 13 9365 [HIDRAW\VEN_056A&DEV_4831] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 5285 [HIDRAW\VEN_056A&DEV_484C] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 7390 2-in-1 [HIDRAW\VEN_056A&DEV_4841] Plugin = wacom_raw Guid = WacomAES # Dell XPS-15 9575 [HIDRAW\VEN_056A&DEV_4875] Plugin = wacom_raw Guid = WacomAES # Dell Latitude 7400 2-in-1 [HIDRAW\VEN_056A&DEV_48C9] Plugin = wacom_raw Guid = WacomAES # Dell XPS-15 9570 [HIDRAW\VEN_056A&DEV_488F] Plugin = wacom_raw Guid = WacomAES # Dell XPS 13 7390 2-in-1 [HIDRAW\VEN_056A&DEV_48ED] Plugin = wacom_raw Guid = WacomAES # AES bootloader mode [HIDRAW\VEN_056A&DEV_0094] Plugin = wacom_raw Guid = WacomAES Flags = is-bootloader # EMR bootloader mode [HIDRAW\VEN_056A&DEV_012B] Plugin = wacom_raw Guid = 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 = 0x24000 fwupd-1.7.5/plugins/wacom-usb/000077500000000000000000000000001420024370600162255ustar00rootroot00000000000000fwupd-1.7.5/plugins/wacom-usb/README.md000066400000000000000000000030651420024370600175100ustar00rootroot00000000000000# 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&REV_0001` * `USB\VID_056A&PID_0378` * `USB\VID_056A` ## 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`. fwupd-1.7.5/plugins/wacom-usb/data/000077500000000000000000000000001420024370600171365ustar00rootroot00000000000000fwupd-1.7.5/plugins/wacom-usb/data/lsusb.txt000066400000000000000000000037021420024370600210310ustar00rootroot00000000000000Bus 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.7.5/plugins/wacom-usb/fu-plugin-wacom-usb.c000066400000000000000000000022331420024370600221720ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-android-device.h" #include "fu-wac-device.h" #include "fu-wac-firmware.h" static void fu_plugin_wacom_usb_init(FuPlugin *plugin) { fu_plugin_add_device_gtype(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 gboolean fu_plugin_wacom_usb_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); } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->build_hash = FU_BUILD_HASH; vfuncs->init = fu_plugin_wacom_usb_init; vfuncs->write_firmware = fu_plugin_wacom_usb_write_firmware; } fwupd-1.7.5/plugins/wacom-usb/fu-self-test.c000066400000000000000000000060331420024370600207110ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-wac-common.h" #include "fu-wac-firmware.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_common_get_contents_bytes(fn, &error); g_assert_no_error(error); g_assert_nonnull(bytes); ret = fu_firmware_parse(firmware, bytes, FWUPD_INSTALL_FLAG_NONE, &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) { 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 */ 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.7.5/plugins/wacom-usb/fu-wac-android-device.c000066400000000000000000000014471420024370600224340ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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.7.5/plugins/wacom-usb/fu-wac-android-device.h000066400000000000000000000004771420024370600224430ustar00rootroot00000000000000/* * 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.7.5/plugins/wacom-usb/fu-wac-common.c000066400000000000000000000040251420024370600210420ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-wac-common.h" const gchar * fu_wac_report_id_to_string(guint8 report_id) { if (report_id == FU_WAC_REPORT_ID_FW_DESCRIPTOR) return "FwDescriptor"; if (report_id == FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER) return "SwitchToFlashLoader"; if (report_id == FU_WAC_REPORT_ID_QUIT_AND_RESET) return "QuitAndReset"; if (report_id == FU_WAC_REPORT_ID_READ_BLOCK_DATA) return "ReadBlockData"; if (report_id == FU_WAC_REPORT_ID_WRITE_BLOCK) return "WriteBlock"; if (report_id == FU_WAC_REPORT_ID_ERASE_BLOCK) return "EraseBlock"; if (report_id == FU_WAC_REPORT_ID_SET_READ_ADDRESS) return "SetReadAddress"; if (report_id == FU_WAC_REPORT_ID_GET_STATUS) return "GetStatus"; if (report_id == FU_WAC_REPORT_ID_UPDATE_RESET) return "UpdateReset"; if (report_id == FU_WAC_REPORT_ID_WRITE_WORD) return "WriteWord"; if (report_id == FU_WAC_REPORT_ID_GET_PARAMETERS) return "GetParameters"; if (report_id == FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR) return "GetFlashDescriptor"; if (report_id == FU_WAC_REPORT_ID_GET_CHECKSUMS) return "GetChecksums"; if (report_id == FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK) return "SetChecksumForBlock"; if (report_id == FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK) return "CalculateChecksumForBlock"; if (report_id == FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE) return "WriteChecksumTable"; if (report_id == FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX) return "GetCurrentFirmwareIdx"; if (report_id == FU_WAC_REPORT_ID_MODULE) return "Module"; return NULL; } void fu_wac_buffer_dump(const gchar *title, guint8 cmd, const guint8 *buf, gsize sz) { g_autofree gchar *tmp = NULL; if (g_getenv("FWUPD_WACOM_USB_VERBOSE") == NULL) return; tmp = g_strdup_printf("%s %s (%" G_GSIZE_FORMAT ")", title, fu_wac_report_id_to_string(cmd), sz); fu_common_dump_raw(G_LOG_DOMAIN, tmp, buf, sz); } fwupd-1.7.5/plugins/wacom-usb/fu-wac-common.h000066400000000000000000000035201420024370600210460ustar00rootroot00000000000000/* * 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 #define FU_WAC_REPORT_ID_FW_DESCRIPTOR 0xcb /* GET_FEATURE */ #define FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER 0xcc /* SET_FEATURE */ #define FU_WAC_REPORT_ID_QUIT_AND_RESET 0xcd /* SET_FEATURE */ #define FU_WAC_REPORT_ID_READ_BLOCK_DATA 0xd1 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_BLOCK 0xd2 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_ERASE_BLOCK 0xd3 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_SET_READ_ADDRESS 0xd4 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_STATUS 0xd5 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_UPDATE_RESET 0xd6 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_WORD 0xd7 /* SET_FEATURE */ #define FU_WAC_REPORT_ID_GET_PARAMETERS 0xd8 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR 0xd9 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_GET_CHECKSUMS 0xda /* GET_FEATURE */ #define FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK 0xdb /* SET_FEATURE */ #define FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK 0xdc /* SET_FEATURE */ #define FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE 0xde /* SET_FEATURE */ #define FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX 0xe2 /* GET_FEATURE */ #define FU_WAC_REPORT_ID_MODULE 0xe4 const gchar * fu_wac_report_id_to_string(guint8 report_id); void fu_wac_buffer_dump(const gchar *title, guint8 cmd, const guint8 *buf, gsize sz); fwupd-1.7.5/plugins/wacom-usb/fu-wac-device.c000066400000000000000000000671701420024370600210230ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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.h" #include "fu-wac-module-touch.h" typedef struct { guint32 start_addr; guint32 block_sz; guint16 write_sz; /* bit 15 is write protection flag */ } FuWacFlashDescriptor; typedef enum { FU_WAC_STATUS_UNKNOWN = 0, FU_WAC_STATUS_WRITING = 1 << 0, FU_WAC_STATUS_ERASING = 1 << 1, FU_WAC_STATUS_ERROR_WRITE = 1 << 2, FU_WAC_STATUS_ERROR_ERASE = 1 << 3, FU_WAC_STATUS_WRITE_PROTECTED = 1 << 4, FU_WAC_STATUS_LAST } FuWacStatus; #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 GString * fu_wac_device_status_to_string(guint32 status_word) { GString *str = g_string_new(NULL); if (status_word & FU_WAC_STATUS_WRITING) g_string_append(str, "writing,"); if (status_word & FU_WAC_STATUS_ERASING) g_string_append(str, "erasing,"); if (status_word & FU_WAC_STATUS_ERROR_WRITE) g_string_append(str, "error-write,"); if (status_word & FU_WAC_STATUS_ERROR_ERASE) g_string_append(str, "error-erase,"); if (status_word & FU_WAC_STATUS_WRITE_PROTECTED) g_string_append(str, "write-protected,"); if (str->len == 0) { g_string_append(str, "none"); return str; } g_string_truncate(str, str->len - 1); return str; } 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_common_string_append_kx(str, idt, "StartAddr", fd->start_addr); fu_common_string_append_kx(str, idt, "BlockSize", fd->block_sz); fu_common_string_append_kx(str, idt, "WriteSize", fd->write_sz & ~0x8000); fu_common_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_autoptr(GString) status_str = NULL; if (self->firmware_index != 0xffff) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", self->firmware_index); fu_common_string_append_kv(str, idt, "FwIndex", tmp); } if (self->loader_ver > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->loader_ver); fu_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv(str, idt, "NrFlashBlocks", tmp); } if (self->configuration != 0xffff) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->configuration); fu_common_string_append_kv(str, idt, "Configuration", tmp); } if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL) { 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_common_string_append_kv(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_common_string_append_kv(str, idt, "Status", status_str->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) { /* hit hardware */ if (g_getenv("FWUPD_WAC_EMULATE") != NULL) return TRUE; 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, 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++) { FuWacFlashDescriptor *fd = g_new0(FuWacFlashDescriptor, 1); const guint blksz = 0x0A; if (!fu_common_read_uint32_safe(buf, sz, (i * blksz) + 1, &fd->start_addr, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint32_safe(buf, sz, (i * blksz) + 5, &fd->block_sz, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe(buf, sz, (i * blksz) + 9, &fd->write_sz, G_LITTLE_ENDIAN, error)) return FALSE; g_ptr_array_add(self->flash_descriptors, fd); } g_debug("added %u flash descriptors", self->flash_descriptors->len); return TRUE; } static gboolean fu_wac_device_ensure_status(FuWacDevice *self, GError **error) { g_autoptr(GString) 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_common_read_uint32(buf + 1, G_LITTLE_ENDIAN); str = fu_wac_device_status_to_string(self->status_word); g_debug("status now: %s", str->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_common_read_uint32(buf + 1, G_LITTLE_ENDIAN); g_debug("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_common_read_uint32(buf + 5 + (i * 4), G_LITTLE_ENDIAN); if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL) 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_common_read_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_common_read_uint16(buf + 1, G_LITTLE_ENDIAN); self->read_data_sz = fu_common_read_uint16(buf + 3, G_LITTLE_ENDIAN); self->write_word_sz = fu_common_read_uint16(buf + 5, G_LITTLE_ENDIAN); self->write_block_sz = fu_common_read_uint16(buf + 7, G_LITTLE_ENDIAN); self->nr_flash_blocks = fu_common_read_uint16(buf + 9, G_LITTLE_ENDIAN); self->configuration = fu_common_read_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_common_write_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_common_write_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); } static 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_common_write_uint16(buf + 1, block_nr, G_LITTLE_ENDIAN); fu_common_write_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_common_write_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); } static 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* 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)); /* enter flash mode */ if (!fu_wac_device_switch_to_flash_loader(self, error)) return FALSE; /* 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 (guint16 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 (guint16 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_common_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 (guint16 i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); GBytes *blob_block; g_autoptr(GPtrArray) 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_common_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, 0, /* page_sz */ self->write_block_sz); for (guint j = 0; j < chunks->len; j++) { FuChunk *chk = g_ptr_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_common_sum32w_bytes(blob_block, G_LITTLE_ENDIAN)); if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL) 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 (guint16 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 (guint16 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_common_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; } if (g_getenv("FWUPD_WACOM_USB_VERBOSE") != NULL) 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) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autofree gchar *name = NULL; g_autofree gchar *name_id6 = NULL; g_autofree gchar *version = 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_common_read_uint16_safe(buf, sizeof(buf), 1, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; if (fw_ver != 0) break; g_usleep(G_USEC_PER_SEC); } version = fu_common_version_from_uint16(fw_ver, FWUPD_VERSION_FORMAT_BCD); /* 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_get_context(FU_DEVICE(self)), usb_device); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_version(FU_DEVICE(module), version); fu_device_set_version_raw(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_get_context(FU_DEVICE(self)), usb_device); 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(FU_DEVICE(module_id6), version); fu_device_set_version_raw(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) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); 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_common_read_uint16_safe(buf, sizeof(buf), 1, &boot_ver, G_BIG_ENDIAN, error)) return FALSE; version_bootloader = fu_common_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_autofree gchar *version = NULL; g_autoptr(FuWacModule) module = NULL; guint16 ver; if (!fu_common_read_uint16_safe(buf, sizeof(buf), (i * 4) + 5, &ver, G_BIG_ENDIAN, error)) return FALSE; version = fu_common_version_from_uint16(ver, FWUPD_VERSION_FORMAT_BCD); switch (fw_type) { case FU_WAC_MODULE_FW_TYPE_TOUCH: module = fu_wac_module_touch_new(fu_device_get_context(FU_DEVICE(self)), usb_device); 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(FU_DEVICE(module), version); fu_device_set_version_raw(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH: module = fu_wac_module_bluetooth_new(fu_device_get_context(FU_DEVICE(self)), usb_device); 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(FU_DEVICE(module), version); fu_device_set_version_raw(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6: module = fu_wac_module_bluetooth_id6_new(fu_device_get_context(FU_DEVICE(self)), usb_device); name = g_strdup_printf("%s [Bluetooth Module (ID6)]", 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(FU_DEVICE(module), version); fu_device_set_version_raw(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_MAIN: fu_device_set_version(FU_DEVICE(self), version); fu_device_set_version_raw(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. */ g_usleep(20 * 1000); /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_wac_device_parent_class)->close(device, error); } static gboolean fu_wac_device_cleanup(FuDevice *device, FwupdInstallFlags flags, GError **error) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_wac_device_update_reset(FU_WAC_DEVICE(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_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); } 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->cleanup = fu_wac_device_cleanup; klass_device->close = fu_wac_device_close; klass_device->set_progress = fu_wac_device_set_progress; } fwupd-1.7.5/plugins/wacom-usb/fu-wac-device.h000066400000000000000000000011011420024370600210060ustar00rootroot00000000000000/* * 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); fwupd-1.7.5/plugins/wacom-usb/fu-wac-firmware.c000066400000000000000000000235321420024370600213720ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #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 */ 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; } 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); if (!fu_firmware_parse_full(firmware_srec, blob, hdr->addr, 0x0, helper->flags, 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); fu_firmware_add_image(helper->firmware, img); helper->images_cnt++; /* clear the image buffer */ g_string_set_size(helper->image_buffer, 0); } /* success */ return TRUE; } static gboolean fu_wac_firmware_parse(FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { gsize sz = 0; const gchar *data = g_bytes_get_data(fw, &sz); 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}; /* check the prefix (BE) */ if (sz < 5 || memcmp(data, "WACOM", 5) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid .wac prefix"); return FALSE; } /* tokenize */ if (!fu_common_strnsplit_full(data, sz, "\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_common_sum8(buf->data, buf->len) ^ 0xFF; } static GBytes * 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_hdr = g_byte_array_new(); /* fw header */ 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 */ return g_string_free_to_bytes(g_steal_pointer(&str)); } static void fu_wac_firmware_init(FuWacFirmware *self) { } static void fu_wac_firmware_class_init(FuWacFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); 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.7.5/plugins/wacom-usb/fu-wac-firmware.h000066400000000000000000000005121420024370600213700ustar00rootroot00000000000000/* * 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.7.5/plugins/wacom-usb/fu-wac-module-bluetooth-id6.c000066400000000000000000000131251420024370600235230ustar00rootroot00000000000000/* * 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" 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 typedef struct { guint8 preamble[2]; guint8 crc; guint8 addr[4]; guint8 cdata[FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ]; } FuWacModuleBluetoothId6BlockData; 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_common_crc8_full(data, sz, 0x00, FU_WAC_MODULE_BLUETOOTH_ID6_CRC8_POLYNOMIAL); return fu_wac_module_bluetooth_id6_reverse_bits(crc); } static GPtrArray * fu_wac_module_bluetooth_id6_parse_blocks(const guint8 *data, gsize sz, GError **error) { const guint8 preamble[] = {0x00, 0x01}; GPtrArray *blocks = g_ptr_array_new_with_free_func(g_free); for (guint addr = 0x0; addr < sz; addr += FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ) { g_autofree FuWacModuleBluetoothId6BlockData *bd = NULL; gsize cdata_sz = FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ; bd = g_new0(FuWacModuleBluetoothId6BlockData, 1); memcpy(bd->preamble, preamble, sizeof(preamble)); bd->addr[0] = 0; bd->addr[1] = 0; bd->addr[2] = 0; bd->addr[3] = 0; memset(bd->cdata, 0xff, FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ); /* if file is not in multiples of payload size */ if (addr + FU_WAC_MODULE_BLUETOOTH_ID6_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_id6_calculate_crc( bd->cdata, FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ); g_ptr_array_add(blocks, g_steal_pointer(&bd)); } return blocks; } 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 *data; gsize len = 0; const guint8 buf_start[] = {FU_WAC_MODULE_BLUETOOTH_ID6_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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 59); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 33); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* build each data packet */ data = g_bytes_get_data(fw, &len); blocks = fu_wac_module_bluetooth_id6_parse_blocks(data, len, error); if (blocks == NULL) 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_ERASE_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* data */ for (guint i = 0; i < blocks->len; i++) { FuWacModuleBluetoothId6BlockData *bd = g_ptr_array_index(blocks, i); guint8 buf[FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ + 7]; g_autoptr(GBytes) blob_chunk = NULL; /* build data packet */ memset(buf, 0xff, sizeof(buf)); memcpy(&buf[0], bd->preamble, 2); buf[2] = bd->crc; memcpy(&buf[3], bd->addr, 4); memcpy(&buf[7], 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_WRITE_TIMEOUT, error)) 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_COMMIT_TIMEOUT, error)) 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); } 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(FuContext *context, GUsbDevice *usb_device) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_BLUETOOTH_ID6, "context", context, "usb-device", usb_device, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6, NULL); return module; } fwupd-1.7.5/plugins/wacom-usb/fu-wac-module-bluetooth-id6.h000066400000000000000000000010211420024370600235200ustar00rootroot00000000000000/* * 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(FuContext *context, GUsbDevice *usb_device); fwupd-1.7.5/plugins/wacom-usb/fu-wac-module-bluetooth.c000066400000000000000000000143171420024370600230470ustar00rootroot00000000000000/* * 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" 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]; guint8 addr[3]; 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) { FuWacModuleBluetoothBlockData *bd; 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); memcpy(bd->preamble, preamble, sizeof(preamble)); bd->addr[0] = (addr >> 16) & 0xff; bd->addr[1] = (addr >> 8) & 0xff; bd->addr[2] = addr & 0xff; 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, 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 79); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) 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) 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_ERASE_TIMEOUT, error)) 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); memcpy(&buf[7], bd->addr, 3); 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_WRITE_TIMEOUT, error)) 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_FINISH_TIMEOUT, error)) 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(FuContext *context, GUsbDevice *usb_device) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_BLUETOOTH, "context", context, "usb-device", usb_device, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH, NULL); return module; } fwupd-1.7.5/plugins/wacom-usb/fu-wac-module-bluetooth.h000066400000000000000000000006771420024370600230600ustar00rootroot00000000000000/* * 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(FuContext *context, GUsbDevice *usb_device); fwupd-1.7.5/plugins/wacom-usb/fu-wac-module-touch.c000066400000000000000000000074331420024370600221650ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-wac-device.h" #include "fu-wac-module-touch.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(GPtrArray) 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); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10); 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) return FALSE; chunks = fu_chunk_array_new_from_bytes(fw, fu_firmware_get_addr(firmware), 0x0, /* page_sz */ 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_ERASE_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* data */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_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_common_write_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)) 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_WRITE_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, chunks->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_FINISH_TIMEOUT, error)) 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(FuContext *context, GUsbDevice *usb_device) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_TOUCH, "context", context, "usb-device", usb_device, "fw-type", FU_WAC_MODULE_FW_TYPE_TOUCH, NULL); return module; } fwupd-1.7.5/plugins/wacom-usb/fu-wac-module-touch.h000066400000000000000000000006131420024370600221630ustar00rootroot00000000000000/* * 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(FuContext *context, GUsbDevice *usb_device); fwupd-1.7.5/plugins/wacom-usb/fu-wac-module.c000066400000000000000000000270111420024370600210370ustar00rootroot00000000000000/* * 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" #define FU_WAC_MODULE_STATUS_OK 0 #define FU_WAC_MODULE_STATUS_BUSY 1 #define FU_WAC_MODULE_STATUS_ERR_CRC 2 #define FU_WAC_MODULE_STATUS_ERR_CMD 3 #define FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL 4 #define FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT 5 #define FU_WAC_MODULE_STATUS_ERR_MODE_WRONG 6 #define FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT 7 #define FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT 8 #define FU_WAC_MODULE_STATUS_ERR_ERASE 9 #define FU_WAC_MODULE_STATUS_ERR_WRITE 10 #define FU_WAC_MODULE_STATUS_ERR_EXIT 11 #define FU_WAC_MODULE_STATUS_ERR 12 #define FU_WAC_MODULE_STATUS_ERR_INVALID_OP 13 #define FU_WAC_MODULE_STATUS_ERR_WRONG_IMAGE 14 typedef struct { GUsbDevice *usb_device; 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_USB_DEVICE, PROP_LAST }; static const gchar * fu_wac_module_fw_type_to_string(guint8 fw_type) { if (fw_type == FU_WAC_MODULE_FW_TYPE_TOUCH) return "touch"; if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH) return "bluetooth"; if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6) return "bluetooth-id6"; if (fw_type == FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION) return "emr-correction"; if (fw_type == FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID) return "bluetooth-hid"; return NULL; } static const gchar * fu_wac_module_command_to_string(guint8 command) { if (command == FU_WAC_MODULE_COMMAND_START) return "start"; if (command == FU_WAC_MODULE_COMMAND_DATA) return "data"; if (command == FU_WAC_MODULE_COMMAND_END) return "end"; return NULL; } static const gchar * fu_wac_module_status_to_string(guint8 status) { if (status == FU_WAC_MODULE_STATUS_OK) return "ok"; if (status == FU_WAC_MODULE_STATUS_BUSY) return "busy"; if (status == FU_WAC_MODULE_STATUS_ERR_CRC) return "err-crc"; if (status == FU_WAC_MODULE_STATUS_ERR_CMD) return "err-cmd"; if (status == FU_WAC_MODULE_STATUS_ERR_HW_ACCESS_FAIL) return "err-hw-access-fail"; if (status == FU_WAC_MODULE_STATUS_ERR_FLASH_NO_SUPPORT) return "err-flash-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_MODE_WRONG) return "err-mode-wrong"; if (status == FU_WAC_MODULE_STATUS_ERR_MPU_NO_SUPPORT) return "err-mpu-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_VERSION_NO_SUPPORT) return "erro-version-no-support"; if (status == FU_WAC_MODULE_STATUS_ERR_ERASE) return "err-erase"; if (status == FU_WAC_MODULE_STATUS_ERR_WRITE) return "err-write"; if (status == FU_WAC_MODULE_STATUS_ERR_EXIT) return "err-exit"; if (status == FU_WAC_MODULE_STATUS_ERR) return "err-err"; if (status == FU_WAC_MODULE_STATUS_ERR_INVALID_OP) return "err-invalid-op"; if (status == FU_WAC_MODULE_STATUS_ERR_WRONG_IMAGE) return "err-wrong-image"; return NULL; } 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_common_string_append_kv(str, idt, "FwType", fu_wac_module_fw_type_to_string(priv->fw_type)); fu_common_string_append_kv(str, idt, "Status", fu_wac_module_status_to_string(priv->status)); fu_common_string_append_kv(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]; if (g_getenv("FWUPD_WACOM_VERBOSE") != NULL) { 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; } gboolean fu_wac_module_set_feature(FuWacModule *self, guint8 command, GBytes *blob, /* optional */ FuProgress *progress, guint busy_timeout, 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 busy_poll_loops = busy_timeout * 100; 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, len + 3, FU_HID_DEVICE_FLAG_ALLOW_TRUNC, error)) { g_prefix_error(error, "failed to set module feature: "); return FALSE; } /* wait for hardware */ for (guint i = 0; i < busy_poll_loops; i++) { if (!fu_wac_module_refresh(self, error)) return FALSE; if (priv->status == FU_WAC_MODULE_STATUS_BUSY) { g_usleep(10000); /* 10ms */ continue; } if (priv->status == FU_WAC_MODULE_STATUS_OK) break; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to SetFeature: %s", fu_wac_module_status_to_string(priv->status)); return FALSE; } /* too many retries */ if (priv->status != FU_WAC_MODULE_STATUS_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Timed out after %u loops with status %s", busy_poll_loops, fu_wac_module_status_to_string(priv->status)); return FALSE; } /* success */ return TRUE; } static gboolean fu_wac_module_cleanup(FuDevice *device, 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, flags, error); } 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; 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_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; case PROP_USB_DEVICE: g_set_object(&priv->usb_device, g_value_get_object(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_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); g_autofree gchar *devid = NULL; g_autofree gchar *vendor_id = NULL; /* set vendor ID */ vendor_id = g_strdup_printf("USB:0x%04X", g_usb_device_get_vid(priv->usb_device)); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); /* set USB physical and logical IDs */ fu_device_set_physical_id(FU_DEVICE(self), g_usb_device_get_platform_id(priv->usb_device)); 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", g_usb_device_get_vid(priv->usb_device), g_usb_device_get_pid(priv->usb_device), 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_finalize(GObject *object) { FuWacModule *self = FU_WAC_MODULE(object); FuWacModulePrivate *priv = GET_PRIVATE(self); if (priv->usb_device != NULL) g_object_unref(priv->usb_device); G_OBJECT_CLASS(fu_wac_module_parent_class)->finalize(object); } 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:usb-device: * * The parent USB device to use. */ pspec = g_param_spec_object("usb-device", NULL, NULL, G_USB_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_USB_DEVICE, pspec); /** * 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; object_class->finalize = fu_wac_module_finalize; 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; } fwupd-1.7.5/plugins/wacom-usb/fu-wac-module.h000066400000000000000000000020751420024370600210470ustar00rootroot00000000000000/* * 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_FW_TYPE_TOUCH 0x00 #define FU_WAC_MODULE_FW_TYPE_BLUETOOTH 0x01 #define FU_WAC_MODULE_FW_TYPE_EMR_CORRECTION 0x02 #define FU_WAC_MODULE_FW_TYPE_BLUETOOTH_HID 0x03 #define FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6 0x06 #define FU_WAC_MODULE_FW_TYPE_MAIN 0x3f #define FU_WAC_MODULE_COMMAND_START 0x01 #define FU_WAC_MODULE_COMMAND_DATA 0x02 #define FU_WAC_MODULE_COMMAND_END 0x03 #define FU_WAC_MODULE_WRITE_TIMEOUT 1 #define FU_WAC_MODULE_ERASE_TIMEOUT 15 #define FU_WAC_MODULE_FINISH_TIMEOUT 1 #define FU_WAC_MODULE_COMMIT_TIMEOUT 80 gboolean fu_wac_module_set_feature(FuWacModule *self, guint8 command, GBytes *blob, FuProgress *progress, guint busy_timeout, GError **error); fwupd-1.7.5/plugins/wacom-usb/meson.build000066400000000000000000000030071420024370600203670ustar00rootroot00000000000000if get_option('gusb') cargs = ['-DG_LOG_DOMAIN="FuPluginWacomUsb"'] install_data(['wacom-usb.quirk'], install_dir: join_paths(datadir, 'fwupd', 'quirks.d') ) shared_module('fu_plugin_wacom_usb', fu_hash, 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-touch.c', 'fu-plugin-wacom-usb.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], install : true, install_dir: plugin_dir, c_args : cargs, dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], ) 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', fu_hash, sources : [ 'fu-self-test.c', 'fu-wac-common.c', 'fu-wac-firmware.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ plugin_deps, ], link_with : [ fwupd, fwupdplugin, ], c_args : cargs, install : true, install_dir : installed_test_bindir, ) test('wacom-usb-self-test', e, env : env) # added to installed-tests endif endif fwupd-1.7.5/plugins/wacom-usb/tests/000077500000000000000000000000001420024370600173675ustar00rootroot00000000000000fwupd-1.7.5/plugins/wacom-usb/tests/wacom-usb.bin000066400000000000000000000003311420024370600217530ustar00rootroot00000000000000WACOM2080080000000000B080400000000000B55 WA10800800077 S0030000FC S3100800800068656C6C6F20776F726C640B S5030001FB S70500000000FA WA208040000F3 S0030000FC S3100804000068656C6C6F20776F726C6487 S5030001FB S70500000000FA fwupd-1.7.5/plugins/wacom-usb/tests/wacom-usb.builder.xml000066400000000000000000000004611420024370600234340ustar00rootroot00000000000000 0x0 0x8008000 aGVsbG8gd29ybGQ= 0x1 0x8040000 aGVsbG8gd29ybGQ= fwupd-1.7.5/plugins/wacom-usb/wacom-usb.quirk000066400000000000000000000040001420024370600211710ustar00rootroot00000000000000 # Intuos Pro medium (2nd-gen USB) [PTH-660] [USB\VID_056A&PID_0357] Plugin = wacom_usb GType = FuWacDevice Flags = use-runtime-version # Intuos Pro large (2nd-gen USB) [PTH-860] [USB\VID_056A&PID_0358] Plugin = wacom_usb GType = FuWacDevice Flags = use-runtime-version # Intuos S 3rd-gen (USB) [CTL-4100] [USB\VID_056A&PID_0374] Plugin = wacom_usb GType = FuWacDevice 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 GType = FuWacDevice 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 GType = FuWacDevice 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 GType = FuWacDevice 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] GType = FuWacDevice Plugin = wacom_usb # Intuos BT S 3rd-gen, Rev 2 (USB) [CTL-4100WL] [USB\VID_056A&PID_03C5] Plugin = wacom_usb GType = FuWacDevice 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 GType = FuWacDevice 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 fwupd-1.7.5/po/000077500000000000000000000000001420024370600132655ustar00rootroot00000000000000fwupd-1.7.5/po/.gitignore000066400000000000000000000000061420024370600152510ustar00rootroot00000000000000*.pot fwupd-1.7.5/po/LINGUAS000066400000000000000000000002031420024370600143050ustar00rootroot00000000000000af ast ca cs da de en_GB eo eu fi fr fur gl he hi hr hu id it ja kk ko ky lt nl oc pa pl pt pt_BR ru si sk sr sv tr uk zh_CN zh_TW fwupd-1.7.5/po/POTFILES.in000066400000000000000000000007201420024370600150410ustar00rootroot00000000000000data/remotes.d/lvfs.metainfo.xml data/remotes.d/lvfs-testing.metainfo.xml policy/org.freedesktop.fwupd.policy.in plugins/dfu/fu-dfu-tool.c plugins/tpm/fu-tpm-eventlog.c plugins/uefi-capsule/fu-plugin-uefi-capsule.c plugins/uefi-capsule/fu-uefi-tool.c plugins/uefi-dbx/fu-dbxtool.c src/fu-debug.c src/fu-engine-helper.c src/fu-main.c src/fu-offline.c src/fu-progressbar.c src/fu-remote-list.c src/fu-security-attr.c src/fu-tool.c src/fu-util.c src/fu-util-common.c fwupd-1.7.5/po/POTFILES.skip000066400000000000000000000000501420024370600153750ustar00rootroot00000000000000data/org.freedesktop.fwupd.metainfo.xml fwupd-1.7.5/po/af.po000066400000000000000000000164561420024370600142270ustar00rootroot00000000000000# 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://www.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 msgid "Cancelled" msgstr "Gekanselleer" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Verander" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolesom" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Kies 'n toestel:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Kies 'n vrystelling:" #. TRANSLATORS: error message msgid "Command not found" msgstr "Opdrag nie gevind nie" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Pak tans uit…" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: 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 from the Internet 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 msgid "Unknown" msgstr "Onbekend" #. TRANSLATORS: ask the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Laai verslag nou op?" #. 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.7.5/po/ast.po000066400000000000000000000036711420024370600144230ustar00rootroot00000000000000# 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.7.5/po/ca.po000066400000000000000000002431061420024370600142160ustar00rootroot00000000000000# 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-2022 # Robert Antoni Buj Gelonch , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Catalan (http://www.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: 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 microprogramari 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: 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 incorporat %s" #. 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: 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: 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: 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: 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: 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: the age of the metadata msgid "Age" msgstr "Antiguitat" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accepteu i habiliteu 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: 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: 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 we want to shutdown 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 propera vegada?" #. 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 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 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 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 "El pujo automàticament cada vegada?" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "CONSTRUCTOR_XML NOM_FITXER_DEST" msgid "BYTES" msgstr "BYTES" #. 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: 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 firmware file" msgstr "Construeix un fitxer de microprogramari." #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Construeix el microprogramari usant un entorn de proves." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel·la" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: remote checksum msgid "Checksum" msgstr "Suma de comprovació" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose a branch:" msgstr "Trieu una branca:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Trieu un dispositiu:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Trieu un tipus de microprogramari:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Trieu un alliberament:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Trieu 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "El dispositiu pot recuperar fallades de flaix" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "S'ha canviat el dispositiu:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "S'ha eliminat el 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 msgid "Devices with no available firmware updates: " msgstr "Dispositius sense actualitzacions de microprogramari disponibles:" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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 "Desactualitzo %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 "Bolca 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: 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 "Habilito el remot nou?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Habilito aquest remot?" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Habilitat" msgid "Enabled fwupdate debugging" msgstr "La depuració del «fwupdate» està habilitada" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Habilita un remot indicat." 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" #. 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 "Sur 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FITXER_ENTRADA FITXER_SORTIDA [SCRIPT] [SORTIDA]" #. 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|NOM_ALT_IMTAGE]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Utilitat per al microprogramari" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Atestat 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Últim refresc de les metadades de microprogramari: fa %s. Empreu «--force» per a refrescar de nou." #. 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: 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 una encriptació 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: 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 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 "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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de seguretat de l'amfitrió:" #. 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 "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 comprovació 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: length of time the update takes to apply msgid "Install Duration" msgstr "Durada de la instal·lació" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instal·la un blob de microprogramari en un dispositiu." #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instal·la un fitxer de microprogramari en aquest maquinari." 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: 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: 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, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fusible OTP del 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: 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, #. * 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Depurador DCI d'Intel" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "SMAP d'Intel" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositiu intern" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "No vàlid" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Està en el mode carregador d'arrencada" #. 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 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" 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 line option msgid "List entries in dbx" msgstr "Llista les entrades a 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 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: 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 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: 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: smallest version number installable on device msgid "Minimum Version" msgstr "Versió mínima" #. 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 daemon.conf 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 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" 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: 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: 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 IPFS when downloading files" msgstr "Empra només IPFS quan es baixin 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: 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 "Carrega ú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 "Realitzo l'operació?" #. 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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Manquen les dependències del connector" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protecció DMA durant la prearrencada" #. 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" msgid "Proceed with upload?" msgstr "Continuo amb l'enviament?" #. 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: 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 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: 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 "Reinstal·lo %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: 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 "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: 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 from the Internet msgid "Requires internet connection" msgstr "Requereix connexió a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reinicio ara?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Reinicio 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 "Return all the hardware IDs for the machine" msgstr "Retorna tots els ID del maquinari de la màquina." #. 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-bkc` to complete this action." msgstr "Executeu `fwupdmgr sync-bkc` 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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escriu 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 line option msgid "Save device state into a JSON file between executions" msgstr "Desa l'estat del dispositiu en un fitxer JSON entre les execucions" #. 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" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Estableix l'indicador de depuració durant l'actualització" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Estableix la llista de microprogramari aprovat" #. 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 la 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 la informació de depuració addicional." #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra l'historial de les actualitzacions de microprogramari." #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra la informació detallada del connector" #. 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 "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: 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 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: one line summary of device msgid "Summary" msgstr "Resum" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "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 "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 "Canvio 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 host best known configuration" msgstr "Sincronitza les versions de microprogramari a la millor configuració coneguda de l'amfitrió" #. TRANSLATORS: Must be plugged in to 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: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrucció PCR0 del 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: 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: 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: 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: 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: 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-bkc` #, 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: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Aquest paquet no ha estat validat, pel que podria no funcionar adecuadament." #. 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: 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 de seguretat HSI baix." #. 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 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: remote type, e.g. remote or local msgid "Type" msgstr "Tipus" #. 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: 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: 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: Suffix: the HSI result msgid "Unencrypted" msgstr "Desencriptada" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: command line option msgid "Unset the debugging flag during update" msgstr "No estableixis l'indicador de depuració durant l'actualització" #. 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 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 the user if we can update the metadata msgid "Update now?" msgstr "Actualitzo 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." 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 "Actualitzo %s des de %s a %s?" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Envio l'informe ara?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Empreu «fwupdmgr --help» per a l'ajuda" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "AVÍS:" #. 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: 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: 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 "[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 "[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 "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[FITXER-HWIDS-SMBIOS]" #. 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.7.5/po/cs.po000066400000000000000000002516451420024370600142470ustar00rootroot00000000000000# 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 # Ascii Wolf , 2017 # Marek Černocký , 2016,2018 # Pavel Borecki , 2020-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Czech (http://www.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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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 budu 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: 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 we want to shutdown 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 "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 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "XML-PRO-SESTAVENÍ CÍLOVÝ-SOUBOR" msgid "BYTES" msgstr "BAJTŮ" #. 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 firmware file" msgstr "Sestavit soubor s firmware" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Sestavit firmware za použití izolovaného prostředí" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Storno" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Zvolte větev firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vyberte zařízení:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Zvolte typ firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vyberte verzi:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Zvolte 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: 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: 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: 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: 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: this is when a device has been updated msgid "Device changed:" msgstr "Změněno zařízení:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Odebráno zařízení:" #. 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 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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" 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "VSTUPNÍ-SOUBOR VÝSTUPNÍ-SOUBOR [SCRIPT] [VÝSTUP]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Nástroj pro práci s firmwary" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Metadata ohledně firmwarů byla naposledy aktualizována před pouhými: %s. Pokud přesto chcete znovu načíst, použijte --force." #. 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: 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: 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 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 "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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Identifikátor zabezpečení hostitele:" #. 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 "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: length of time the update takes to apply msgid "Install Duration" msgstr "Délka instalace" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Nainstalovat na zařízení binární soubor s firmwarem" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Nainstalovat soubor s firmwarem na tento hardware" 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: 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: 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, #. * 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, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Co Intel BootGuard udělá v případě chyb" #. 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, #. * 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Ladicí nástroj pro Intel DCI" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Vestavěné zařízení" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Neplatné" #. 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" 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 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 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: 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 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: 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: smallest version number installable on device msgid "Minimum Version" msgstr "Nejstarší použitelná verze" #. 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 daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "POZN.: Tento program může správně fungovat jen pod uživatelem root" #. 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 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" 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: command line option msgid "Only show single PCR value" msgstr "Zobrazit pouze jedinou PCR hodnotu" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "IFPS použít pouze při stahování souborů" #. 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: 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Chybí komponenty, na kterých zásuvný modul závisí" #. 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: 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" msgid "Proceed with upload?" msgstr "Pokračovat v nahrávání?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietární" #. 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: command description msgid "Read a firmware blob from a device" msgstr "Vyčíst blob firmwaru 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: 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 "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: 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 from the Internet msgid "Requires internet connection" msgstr "Vyžaduje připojení k Internetu" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restartovat nyní?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Vypsat všechny identifikátory hardwaru počítače" #. 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-bkc` to complete this action." msgstr "Pokud chcete vyřešit tuto část, spusťte `fwupdmgr sync-bkc`" #. 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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI zápis" #. 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 line option msgid "Save device state into a JSON file between executions" msgstr "Mezi vykonáními uložit stav zařízení do JSON souboru" #. 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" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Během aktualizace nastavit příznak ladění" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Nastavuje seznam schválených firmwarů" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Zobrazit podrobné informace o zásuvném modulu" #. 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: 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 firmware, 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 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: one line summary of device msgid "Summary" msgstr "Souhrn" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Podporováno" #. 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: 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 host best known configuration" msgstr "Synchronizovat verze firmwarů do stavu osvědčené kombinace" #. TRANSLATORS: Must be plugged in to 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: 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: 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: 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: 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: 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: 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: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Výrobce neposkytl žádné poznámky k vydání." #. 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-bkc` #, 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: 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 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: We verified the meatdata 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: 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: 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: 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: Suffix: the HSI result msgid "Unencrypted" msgstr "Nešifrováno" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: command line option msgid "Unset the debugging flag during update" msgstr "Během aktualizace zrušit příznak ladění" #. 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 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 the user if we can update the 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 upload msgid "Upload report now?" msgstr "Odeslat hlášení nyní?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Nápovědu si zobrazíte použitím příkazu fwupdmgr --help" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "VAROVÁNÍ:" #. 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: 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: 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 "[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 "[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 "[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.7.5/po/da.po000066400000000000000000002166611420024370600142250ustar00rootroot00000000000000# 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://www.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 we want to shutdown 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: command description msgid "Build firmware using a sandbox" msgstr "Byg firmware med en sandkasse" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annuller" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Vælg en gren:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Vælg en enhed:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Vælg en firmwaretype:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Vælg en udgivelse:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Vælg et diskområde:" #. 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: 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 msgid "Devices with no available firmware updates: " msgstr "Enheder uden nogen tilgængelige firmwareopdateringer: " #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 the user 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FIL-IND FIL-UD [SCRIPT] [OUTPUT]" #. 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: Suffix: the fallback HSI result #. 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 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Sidste opdatering af firmwaremetadata: %s siden. Brug --force til at opdatere igen." #. 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 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" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Installer en firmwareblob på en enhed" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installer en firmwarefil på hardwaren" 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, #. * 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI-fejlsøgning" #. 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 daemon.conf 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: command line option msgid "Only use IPFS when downloading files" msgstr "Brug kun IPFS når filer downloades" #. 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 from the Internet msgid "Requires internet connection" msgstr "Kræver internetforbindelse" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Genstart nu?" #. TRANSLATORS: configuration 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 "Save device state into a JSON file between executions" msgstr "Gem enhedens tilstand i en JSON-fil mellem udførsler" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Vis uddybende information om plugin" #. 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 in to 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Upload rapport nu?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Brug fwupdmgr --help for hjælp" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "ADVARSEL:" #. 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.7.5/po/de.po000066400000000000000000002063041420024370600142220ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ettore Atalan , 2018,2021 # Marco Tedaldi , 2015 # Wolfgang Stöggl , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: German (http://www.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: 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: 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: 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: 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: 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: 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: 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: 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: 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: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(veraltet)" #. 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: the age of the metadata msgid "Age" msgstr "Alter" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Verweis auf %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 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: 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 we want to shutdown 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: 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 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 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 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?" msgid "BYTES" msgstr "BYTES" #. 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: 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 firmware file" msgstr "Eine Firmware-Datei bauen" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Firmware mit Hilfe einer Sandbox erstellen" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Abbrechen" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Wählen Sie einen Zweig aus:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Wählen Sie ein Gerät aus:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Wählen Sie einen Firmware-Typ aus:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Wählen Sie eine Version aus:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Wählen Sie einen Datenträger:" #. 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: 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: 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: multiline description of device msgid "Description" msgstr "Beschreibung" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Details" #. 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: 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: this is when a device has been updated msgid "Device changed:" msgstr "Gerät geändert:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Eine Versionsprüfung der Geräte-Firmware ist erforderlich" #. 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Gerät entfernt:" #. 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 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used msgid "Disabled" msgstr "Deaktiviert" msgid "Disabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung deaktivieren" #. TRANSLATORS: command line option msgid "Display version" msgstr "Version anzeigen" #. 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 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 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: 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: 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: command line option msgid "Enable firmware update support on supported systems" msgstr "Firmware-Aktualisierungsunterstützung auf unterstützten Systemen aktivieren" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Aktiviert" msgid "Enabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung aktivieren" 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: 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "DATEIEINGANG DATEIAUSGANG [SKRIPT] [AUSGABE]" #. 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 [DEVICE-ID|GUID]" msgstr "DATEINAME [GERÄTEKENNUNG|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "DATEINAME [FIRMWARE-TYP]" #. TRANSLATORS: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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: 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Firmware-Dienstprogramm" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Firmware-Beglaubigung" #. 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: 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: 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 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 "Gets details about a firmware file" msgstr "Ermittelt Details über eine Firmware-Datei" #. 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: 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-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 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: length of time the update takes to apply msgid "Install Duration" msgstr "Installationsdauer" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Firmware-Blob auf einem Gerät installieren" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Eine Firmware-Datei auf dieser Hardware 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: 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: 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-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" #. 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI Debugger" #. 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: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Ist im Bootloader-Modus" #. 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 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" 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 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 types" msgstr "Die verfügbaren Firmware-Typen auflisten" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Listet die Dateien auf dem ESP auf" #. 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 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: 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: smallest version number installable on device msgid "Minimum Version" msgstr "Minimale Version" 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 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" 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: 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: command line option msgid "Only show single PCR value" msgstr "Nur einzelnen PCR-Wert anzeigen" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "IPFS nur beim Herunterladen von Dateien 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: 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" 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Fehlende Plugin-Abhängigkeiten" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "DMA-Schutz vor dem Booten" #. 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" msgid "Proceed with upload?" msgstr "Mit dem Hochladen fortfahren?" #. 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 description msgid "Read a firmware blob from a device" msgstr "Einen Firmware-Blob 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: 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: 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: 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 from the Internet msgid "Requires internet connection" msgstr "Erfordert Internetverbindung" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Jetzt neu starten?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Gerät wird neu gestartet …" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Alle Hardware-Kennungen für das System zurückgeben" #. 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI schreiben" #. 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 line option msgid "Save device state into a JSON file between executions" msgstr "Gerätestatus zwischen den Ausführungen in einer JSON-Datei speichern" #. 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" #. 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: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Legt die Liste der zugelassenen Firmware fest" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Ausführliche Informationen zum Plugin 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: 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" #. 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 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 -- 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 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 -- 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: one line summary of device msgid "Summary" msgstr "Zusammenfassung" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Unterstützt" #. 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 "In Ruhezustand versetzen" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "In Bereitschaft versetzen" #. 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: Must be plugged in to 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: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. 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: 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. Möglicherweise hat Ihr Lieferant eine der Firmware-Aktualisierungen nicht auf Kompatibilität mit Ihrem System oder angeschlossenen Geräten überprüft. Die gesamte Firmware wird nur vom Originalhersteller zur Verfügung gestellt." #. 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: 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: 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" #. 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: 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: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. 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: 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: 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: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: 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 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 the user if we can update the 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" 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 upload msgid "Upload report now?" msgstr "Bericht jetzt hochladen?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Verwenden Sie fwupdmgr --help für Hilfe" #. TRANSLATORS: error message explaining command on how to get help msgid "Use fwupdtool --help for help" msgstr "Verwenden Sie fwupdtool --help für Hilfe" #. 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 "Überprüfung …" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: this is a prefix on the console msgid "WARNING:" msgstr "WARNUNG:" #. 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: 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: 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 "Möglicherweise hat Ihr Lieferant eine der Firmware-Aktualisierungen nicht auf Kompatibilität mit Ihrem System oder angeschlossenen Geräten überprüft." #. 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: 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 "[FILENAME1] [FILENAME2]" msgstr "[DATEINAME1] [DATEINAME2]" #. 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.7.5/po/en_GB.po000066400000000000000000002330401420024370600146010ustar00rootroot00000000000000# 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 # Richard Hughes , 2015,2017-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: English (United Kingdom) (http://www.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: 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: 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 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: 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: 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: 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: 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: 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: 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: 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: 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 we want to shutdown 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 "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 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML FILENAME-DST" msgid "BYTES" msgstr "BYTES" #. 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 firmware file" msgstr "Build a firmware file" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Build firmware using a sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Choose a branch:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Choose a device:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Choose a firmware type:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Choose a release:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Choose a 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Device can recover flash failures" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Device changed:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Device removed:" #. 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 msgid "Devices with no available firmware updates: " msgstr "Devices with no available firmware updates: " #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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" 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Firmware Utility" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Firmware metadata last refresh: %s ago. Use --force to refresh again." #. 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: 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: 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 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 plugins registered with the system" #. 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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Host Security 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 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: length of time the update takes to apply msgid "Install Duration" msgstr "Install Duration" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Install a firmware blob on a device" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Install a firmware file on this hardware" 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: 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: 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, #. * 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 error policy" #. 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, #. * 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI debugger" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Internal device" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Invalid" #. 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" 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 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 types" msgstr "List the available firmware types" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Lists files on the ESP" #. 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 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 plugins" #. 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: 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 "Mismatched daemon and client, use %s instead" #. TRANSLATORS: sets something in daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "NOTE: This program may only work correctly as root" #. 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 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 plugins 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" 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: command line option msgid "Only show single PCR value" msgstr "Only show single PCR value" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Only use IPFS 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: 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Plugin dependencies missing" #. 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: 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" msgid "Proceed with upload?" msgstr "Proceed with upload?" #. 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: command description msgid "Read a firmware blob from a device" msgstr "Read a firmware blob 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: 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 "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: 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 from the Internet msgid "Requires internet connection" msgstr "Requires internet connection" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restart now?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Return all the hardware IDs for the machine" #. 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-bkc` to complete this action." msgstr "Run `fwupdmgr sync-bkc` to complete this action." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Run the plugin 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 plugin composite prepare routine when using install-blob" #. 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI write" #. 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 line option msgid "Save device state into a JSON file between executions" msgstr "Save device state into a JSON file between executions" #. 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" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Set the debugging flag during update" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Sets the list of approved firmware" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Show plugin verbose information" #. 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: 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 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: one line summary of device msgid "Summary" msgstr "Summary" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Supported" #. 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: 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 host best known configuration" msgstr "Sync firmware versions to the host best known configuration" #. TRANSLATORS: Must be plugged in to 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: 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: 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: 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: 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: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "The TPM PCR0 differs from reconstruction." #. 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: naughty vendor msgid "The vendor did not supply any release notes." msgstr "The vendor did not supply any release notes." #. 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-bkc` #, 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: 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 plugins without being installed on the host system." #. 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: We verified the meatdata 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: 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: 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: 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: Suffix: the HSI result msgid "Unencrypted" msgstr "Unencrypted" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: command line option msgid "Unset the debugging flag during update" msgstr "Unset the debugging flag during update" #. 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 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 the user if we can update the 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 upload msgid "Upload report now?" msgstr "Upload report now?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Use fwupdmgr --help for help" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "WARNING:" #. 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: 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: 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 "[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 "[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 "[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 plugins" fwupd-1.7.5/po/eo.po000066400000000000000000000036461420024370600142410ustar00rootroot00000000000000# 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.7.5/po/eu.po000066400000000000000000000030701420024370600142360ustar00rootroot00000000000000# 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 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Basque (http://www.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: this is when a device is hotplugged msgid "Added" msgstr "Gehitua" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Aldatua" #. TRANSLATORS: error message msgid "Command not found" msgstr "Ez da komandoa aurkitu" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Aurkitua" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "IDa" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Izena" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokoloa" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Eskualdea" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Kendua" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Egoera" fwupd-1.7.5/po/fi.po000066400000000000000000002326611420024370600142350ustar00rootroot00000000000000# 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-2021 # Kimmo Kujansuu , 2019-2021 # Timo Jyrinki , 2021 # Ville-Pekka Vainio , 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Finnish (http://www.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: 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: 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: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Laitteen %s näppäimistö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 "Laitteen%s hiiripä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: 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: 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: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s-tuotantotila (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: 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] "%utuettua 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: 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: 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: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Päivitys vaatii uudelleenkäynnistyksen." #. TRANSLATORS: explain why we want to shutdown 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 "Ota päivitystiedostot käyttöön" #. 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 "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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "LUONTI-XML TIEDOSTONIMI-KOHDE" msgid "BYTES" msgstr "TAVUA" #. 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: 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 firmware file" msgstr "Luo laiteohjelmistotiedosto" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Rakenna laiteohjelmisto hiekkalaatikon avulla" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Peru" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Valitse haara:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Valitse laite:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Valitse laiteohjelmiston tyyppi:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Valitse julkaisu:" #. TRANSLATORS: get interactive prompt msgid "Choose a 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: 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Laite voi palautua asennuksen virheistä" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Laite muutettu:" #. 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: 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: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Laitetta voidaan käyttää päivityksen aikana" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Laite poistettu:" #. 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 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Käytössä" msgid "Enabled fwupdate debugging" msgstr "fwupdaten vianjäljitys on käytössä" #. 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "SYÖTETIEDOSTO TULOSTIEDOSTO [SKRIPTI] [TULOSTE]" #. 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 [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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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 "Firmware-päivityksen taustaprosessi" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Laiteohjelmistotyökalu" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Firmwaren metatiedot päivitetty: %s. Käytä --force päivittääksesi ne uudelleen." #. 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 vapauttamalla 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: 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 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 "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 suojaustapahtumia" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Koneen tietoturvatunniste:" #. 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-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: length of time the update takes to apply msgid "Install Duration" msgstr "Asennuksen kesto" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Asenna laiteohjelmisto (\"blob\") laitteeseen" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Asenna laiteohjelmistotiedosto tähän laitteistoon" msgid "Install old version of signed system firmware" msgstr "Asenna allekirjoitetun järjestelmän firmware:n vanha versio" msgid "Install old version of unsigned system firmware" msgstr "Asenna allekirjoittamattoman järjestelmän firmware:n 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: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Asennetaan laiteohjelmstopäivitystä…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Asentaa laitteelle %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 -suojattu" #. 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, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard -virhekäytäntö" #. 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, #. * 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI -virheenjäljitys" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Sisäinen laite" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Virheellinen" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "On käynnistyslataintilassa" #. 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 lockdown disabled" msgstr "Kernel lukitus poistettu käytöstä" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Kernel lukitus 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" 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 kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux kernel lukitus" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linuxin näennäismuisti (swap)" #. 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 types" msgstr "Luettelo käytettävissä olevista laiteohjelmistotyypeistä" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Luetteloi ESP:n tiedostot" #. 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 msgid "MEI manufacturing mode" msgstr "MEI-tuotantotekniikka (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: 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: 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 daemon.conf 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 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" 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: 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: command line option msgid "Only show single PCR value" msgstr "Näytä vain yksi PCR-arvo" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Käytä vain IPFS:ää kun lataat tiedostoja" #. 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" 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: '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: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Käynnistystä edeltävä DMA-suojaus" #. 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" msgid "Proceed with upload?" msgstr "Jatketaanko lähettämistä?" #. 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: command description msgid "Read a firmware blob from a device" msgstr "Lue laiteohjelmiston \"blob\" laitteesta" #. 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: the exact component on the server msgid "Release ID" msgstr "Julkaisu ID-tunnus" #. 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: 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 from the Internet msgid "Requires internet connection" msgstr "Vaatii internetyhteyden" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Käynnistetäänkö uudelleen nyt?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Palauta kaikki koneen laitetunnukset" #. 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: 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: 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: 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 line option msgid "Save device state into a JSON file between executions" msgstr "Tallenna laitteen tila JSON-tiedostoon suoritusten välillä" #. 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 "Secure Boot pois käytöstä" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot käytössä" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Aseta virheenkorjauksen lippu päivityksen aikana" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Tallentaa hyväksyttyjen laiteohjelmistojen luettelon" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Näytä liitännäisten täydelliset tiedot" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Näytä dbx 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 "Kirjoita lähetetyt tiedot asiakkaan sertifikaatilla" msgid "Signature" msgstr "Allekirjoitus" #. 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 salaukset voidaan mitätöidä tätä firmware-ohjelmistoa 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 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: one line summary of device msgid "Summary" msgstr "Yhteenveto" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Tuettu" #. 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: 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: Must be plugged in to 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: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 -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: 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: 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: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 eroaa uudelleenrakennetusta." #. 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: 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: 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: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Järjestelmässä on HSI-suoritusajan 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 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: remote type, e.g. remote or local msgid "Type" msgstr "Tyyppi" #. 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: 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-alustan 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: 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: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: 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 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 the user if we can update the 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ä" 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 upload msgid "Upload report now?" msgstr "Lähetetäänkö raportti nyt?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Suorita fwupdmgr --help nähdäksesi ohjeet" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "VAROITUS:" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Odotetaan…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Seuraa laitteiston muutoksia" #. 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: 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 "[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 "[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.7.5/po/fr.po000066400000000000000000001015341420024370600142400ustar00rootroot00000000000000# 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://www.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=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 restante" msgstr[1] "%.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" #. 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." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u heure" msgstr[1] "%u heures" #. 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 seconde" msgstr[1] "%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 we want to shutdown 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 :" #. 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 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: remote checksum msgid "Checksum" msgstr "Somme de contrôle" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose a branch:" msgstr "Choisir une branche :" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Choisissez un appareil :" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Choisissez un type de micrologiciel :" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Choisir un volume :" #. 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: Suffix: the fallback HSI result #. 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" #. 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 "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" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installer un fichier de micrologiciel sur ce matériel" 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Débogueur Intel DCI" #. 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 line option msgid "Only use IPFS when downloading files" msgstr "N'utiliser IPFS que lors du téléchargement de fichiers" #. 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: configuration 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: this is shown in the MOTD msgid "Run `fwupdmgr sync-bkc` to complete this action." msgstr "Exécutez `fwupdmgr sync-bkc` pour terminer cette action." #. 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" #. 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 in to 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 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 the user if we can update the 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Utilisez fwupdmgr --help pour obtenir de l'aide" #. 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: this is a prefix on the console msgid "WARNING:" msgstr "ATTENTION :" #. 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.7.5/po/fur.po000066400000000000000000000726051420024370600144330ustar00rootroot00000000000000# 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 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Friulian (http://www.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: 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: 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" 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: 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â il rimot?" #. 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: 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 we want to shutdown 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: 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: waiting for user to authenticate msgid "Authenticating…" msgstr "Daûr a autenticâ…" #. 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 a configured remote used for firmware updates" msgstr "La autenticazion e je necessarie par modificâ un rimot configurât, doprât 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 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: command description msgid "Build firmware using a sandbox" msgstr "Costruìs il firmware doprant une ambient isolât - sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Anule" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Anulât" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modificât" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Sielç un dispositîf:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Sielç un gjenar di firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Sielç une publicazion:" #. 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: 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: multiline description of device msgid "Description" msgstr "Descrizion" #. 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositîf gjavât:" #. 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:" msgid "Disabled fwupdate debugging" msgstr "Disabilite il debug di fwupdate" #. 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: success #. success msgid "Done!" msgstr "Fat!" #. 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: 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: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "L'ESP specificât nol jere valit" #. 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: Turn on the remote msgid "Enable this remote?" msgstr "Abilitâ chest rimot?" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Abilitât" msgid "Enabled fwupdate debugging" msgstr "Abilite il debug di 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 "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 chest rimot e ven fate a to pericul." #. 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: we could not talk to the fwupd daemon 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: 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: 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: filename of the local file msgid "Filename" msgstr "Non file" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firme non file" #. 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: program name msgid "Firmware Utility" msgstr "Utilitât 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." 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." 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: 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 "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 i rimots configurâ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: daemon is inactive msgid "Idle…" msgstr "In polse…" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instale un file firmware su chest hardware" 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" 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: 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" 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: command line option msgid "List supported firmware updates" msgstr "Liste dai inzornaments di firmware supuartâts" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Daûr a cjariâ…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadata" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Al modifiche un rimot furnît" msgid "Modify a configured remote" msgstr "Modifiche un rimot configurât" 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: 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: no repositories to download from msgid "No remotes available" msgstr "Nissun rimot 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 "OK" msgstr "Va ben" #. 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: remote filename base msgid "Password" msgstr "Password" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentuâl di completament" #. 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: " 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: command line option msgid "Query for firmware update support" msgstr "Interogazion pal supuart dai inzornaments 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 rimot" #. 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 server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID rimot" #. 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: metadata is downloaded from the Internet 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: configuration 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: 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: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositîf selezionât" #. 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: 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostre lis informazions prolissis dai plugin" #. 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" msgid "Signature" msgstr "Firme" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specifiche Vendidôr/ID prodot dal dispositîf DFU" msgid "Specify the number of bytes per USB transfer" msgstr "Specifiche il numar di byte par trasferiment USB" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Rimot disabilitât 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 remote" msgstr "Rimot abilitât 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 "Rimot modificât 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 -- 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: one line summary of device msgid "Summary" msgstr "Sintesi" 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: 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 "Chest rimot al conten firmware che no son sot di embargo, ma a son ancjemò in prove dal vendidôr dal hardware. Si à di sigurâsi di vê une maniere par puartâ indaûr a man il firmware ae version precedente, se l'inzornament al falìs." #. 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: remote type, e.g. remote or local msgid "Type" msgstr "Gjenar" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitât Firmware UEFI" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency msgid "Unknown" msgstr "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: 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 the user if we can update the metadata msgid "Update now?" msgstr "Inzornâ cumò?" msgid "Update the stored device verification information" msgstr "Inzorne lis informazions di verifiche dal dispositîf archiviadis" #. 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: ask the user to upload msgid "Upload report now?" msgstr "Inviâ il rapuart cumò?" #. 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: remote filename base msgid "Username" msgstr "Non utent" #. 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: 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: 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." fwupd-1.7.5/po/gl.po000066400000000000000000000754501420024370600142420ustar00rootroot00000000000000# 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://www.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 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: get interactive prompt msgid "Choose a device:" msgstr "Seleccione un dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Escolla o tipo de firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Escolla a publicación:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Escolla un volume:" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: Suffix: the fallback HSI result #. 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 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…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instalar un blob de firmware nun dispositivo" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instalar un ficheiro de firmware neste hardware" 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Depurador de Intel DCI" #. 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 from the Internet msgid "Requires internet connection" msgstr "Require conexión a internet" #. TRANSLATORS: configuration 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostrar información de depuración do engadido" #. 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Desexa subir o informe agora?" #. TRANSLATORS: error message explaining command on 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.7.5/po/he.po000066400000000000000000001620631420024370600142310ustar00rootroot00000000000000# 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 # GenghisKhan , 2015 # Omer I.S. , 2021 # Yaron Shahrabani , 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hebrew (http://www.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 we want to shutdown 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 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 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: command description msgid "Build firmware using a sandbox" msgstr "בניית קושחה בסביבה מבודדת" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "ביטול" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "בוטל" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "השתנה" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "בודק שהגיבוב הקריפטוגרפי תואם לקושחה" #. 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 a branch:" msgstr "נא לבחור ענף:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "נא לבחור התקן:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "נא לבחור סוג קושחה" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "נא לבחור מהדורה:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" 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: 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: 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 msgid "Devices with no available firmware updates: " msgstr "התקנים ללא עדכוני קושחה זמינים:" #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "התקנים עם גרסת הקושחה העדכנית ביותר שזמינה:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used msgid "Disabled" msgstr "מושבת" #. 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: 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 the user 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" 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 "זיכרון מוצפן" #. 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: Suffix: the fallback HSI result #. 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 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: 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 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 "משך התקנה" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "התקנת מקטע בינרי של קושחה על התקן" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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 use IPFS when downloading files" msgstr "להשתמש ב־IPFS רק בעת הורדת קבצים" #. 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 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: 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 "הצגת מספר הגרסה" #. 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: 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 from the Internet msgid "Requires internet connection" msgstr "דורש חיבור לאינטרנט" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "להפעיל כעת מחדש?" #. TRANSLATORS: configuration 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: command line option msgid "Save device state into a JSON file between executions" msgstr "לשמור את מצב ההתקנים לקובץ JSON בין ההפעלות" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" 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: 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: 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 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 "מקור" #. 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 in to 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 "החבילה לא עברה אימות, יכול להיות שלא תעבוד כראוי." 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 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: 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "להעלות דוח עכשיו?" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "דחיפות" #. TRANSLATORS: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "יש להשתמש ב־fwupdmgr --help לקבלת עזרה" #. TRANSLATORS: error message explaining command on 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 "שם משתמש" #. 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: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "תוספים של fwupd" fwupd-1.7.5/po/hi.po000066400000000000000000000075051420024370600142340ustar00rootroot00000000000000# 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.7.5/po/hr.po000066400000000000000000002422371420024370600142500ustar00rootroot00000000000000# 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 # Ping , 2020-2021 # Ping , 2021 # gogo , 2016-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Croatian (http://www.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: 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: 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: 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: 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 upravljača" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s touchpad nadopuna" #. 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: 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: 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: 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: 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 we want to shutdown 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "IZGRADITELJ-XML NAZIV DATOTEKE-DST" msgid "BYTES" msgstr "BAJTA" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Spoji novi pogon jezgre" #. 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: command description msgid "Build firmware using a sandbox" msgstr "Izgradi firmver u osiguranom okruženju" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Odustani" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Odaberi granu:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Odaberite uređaj:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Odaberi vrstu firmvera:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Odaberi izdanje:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Odaberite uređaj:" #. 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: 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: 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: 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: 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: this is when a device has been updated msgid "Device changed:" msgstr "Uređaj promijenjen:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Uređaj uklonjen:" #. 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 msgid "Devices with no available firmware updates: " msgstr "Uređaji bez dostupnih nadopuna firmvera: " #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "ULAZNA-DATOTEKA IZLAZNA-DATOTEKA [SKRIPTA] [IZLAZ]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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: 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: 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: program name msgid "Firmware Utility" msgstr "Firmver pomagalo" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Posljednja provjera metapodatka frimvera: prije %s. Koristite --force za ponovnu provjeru." #. 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: 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 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 "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: 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: 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 firmware blob on a device" msgstr "Instaliraj blob firmvera na uređaj" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instaliraj datoteku firmvera na ovaj uređaj" 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: 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, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP osigurač" #. 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: 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, #. * 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: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI otklanatelj grešaka" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Unutrašnji uređaj" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Nevaljano" #. 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" 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 "Nabraja datoteke na ESP-u" #. 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 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: 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: 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 daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "NAPOMENA: Ovaj program može samo raditi samo s korijenskim ovlastima" #. 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" 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: command line option msgid "Only show single PCR value" msgstr "Prikaži samo jednu PRC vrijednost" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Koristi samo IPFS pri preuzimanju datoteka" #. 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: '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: 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: 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" msgid "Proceed with upload?" msgstr "Nastavi sa slanjem?" #. 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 "UDALJENA_LOKACIJA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "VRIJEDNOST KLJUČA ID-UDALJENE_LOKACIJE" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Očitaj blob firmvera sa 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: 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 from the Internet msgid "Requires internet connection" msgstr "Potreban je pristup internetu" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Ponovno pokreni odmah?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Vrati sve ID-ove hardvera za uređaj" #. 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: this is shown in the MOTD msgid "Run `fwupdmgr sync-bkc` to complete this action." msgstr "Za završavanje ove radnje pokreni `fwupdmgr sync-bkc`." #. 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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI zapisivanje" #. 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 line option msgid "Save device state into a JSON file between executions" msgstr "Spremi stanje uređaja u JSON datoteku između izvršavanja" #. 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: %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: command line option msgid "Set the debugging flag during update" msgstr "Postavi oznaku otklanjanja grešaka tijekom nadopune" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Postavlja popis odobrenih firmvera" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Prikaži dodatne informacije priključka" #. 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: 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 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: 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 "Suspendiraju-u-mirovanje" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspendiraju-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: command description msgid "Sync firmware versions to the host best known configuration" msgstr "Uskladi inačicu firmvera s najbolje poznatim podešavanjem na poslužitelju" #. TRANSLATORS: Must be plugged in to 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: 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: 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: 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: 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: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 razlikuje se od rekonstrukcije." #. 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 "Firmver od %s se ne isporučuje od %s, dobavljač 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: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Proizvođač nije objavio nikakve bilješke izdanja." #. 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-bkc` #, 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: 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 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: We verified the meatdata against the server msgid "Trusted metadata" msgstr "Provjereni metapodaci" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Provjereni 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: 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: command description msgid "Unbind current driver" msgstr "Odspoji trenutačni pogon" #. 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 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: 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Pošalji izvještaj odmah?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Koristite fwupdmgr --help za prikaz pomoći" #. TRANSLATORS: error message explaining command on 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: 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: 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: 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 "Tvoj je sustav 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 "[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 "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-DATOTEKA|HWIDS-DATOTEKA]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "standardno" #. 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.7.5/po/hu.po000066400000000000000000001256211420024370600142500ustar00rootroot00000000000000# 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 Úr, 2015-2018 # Gabor Kelemen , 2016 # kelemeng , 2016 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hungarian (http://www.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: 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 ME 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 ME 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: 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: 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 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: 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: 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 "Lehet, hogy a(z) %s és az összes kapcsolódó eszköz használhatatlan lesz a frissítés alatt." #. 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 károsodás elkerülése miatt szükséges, hogy a(z) %s kapcsolódva maradjon a frissítés során." #. 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 károsodás elkerülése miatt szükséges, hogy a(z) %s csatlakoztatva maradjon egy áramforráshoz a frissítés során." #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u nap" msgstr[1] "%u nap" #. 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: 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 firmware aktiválása az eszközön" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Firmware frissítés aktiválása" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Firmware frissítés aktiválása ennél:" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Kor" #. 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: command line option msgid "Allow downgrading firmware versions" msgstr "Firmware verziók visszafejlesztésének engedélyezése" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "A meglévő firmware verziók újratelepítésének engedélyezése" #. 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 we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Egy frissítés a rendszer újraindítását igényel a befejezéshez." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Igen az összes kérdésre" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Firmware-frissítések alkalmazása" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Jóváhagyott firmware:" msgstr[1] "Jóváhagyott firmwarek:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Csatlakoztatás a firmware módhoz" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Hitelesítés…" #. 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 firmware visszafejlesztéséhez egy cserélhető eszközön" #. 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 firmware visszafejlesztéséhez ezen a gépen" #. 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 firmware frissítéshez 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 set the list of approved firmware" msgstr "Hitelesítés szükséges a jóváhagyott firmwarek 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ánnyal 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 az új firmware verzió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 firmware frissítéséhez egy cserélhető eszközön" #. 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 firmware frissítéséhez ezen a gépen" #. 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: firmware version of bootloader msgid "Bootloader Version" msgstr "Rendszerbetöltő verziója" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Firmware összeállítása egy homokozóban" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Mégse" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Megszakítva" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Módosítva" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Ellenőrzi, hogy a kriptográfiai hash egyezik-e a firmware-rel" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Ellenőrzőösszeg" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Válasszon eszközt:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Válasszon firmware típust:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Válasszon kiadást:" #. 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: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kriptográfiai hash ellenőrzés érhető el" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Jelenlegi verzió" #. 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 beállítások" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Kibontás…" #. 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Az eszköz képes helyreállni a felülírási hibák után" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Eszköz módosítva:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Az eszköz zárolt" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Az eszköz használható marad a frissítés ideje alatt" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Eszköz eltávolítva:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Az eszköz szakaszosan frissít" #. 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: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Az eszköz a frissítés végeztével újra meg fog jelenni" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Eszközök, melyek sikeresen frissítve lettek:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Eszközök, melyek nem lettek helyesen frissítve:" msgid "Disabled fwupdate debugging" msgstr "Fwupdate hibakeresés 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: 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: 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öz biztonsági ellenőrzéseket" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne írjon az előzmények adatbázisába" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Kész!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "A firmware visszafejlesztése az 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 visszafejlesztése: %s -> %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s visszaállítása…" #. 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 "Hossz" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "A megadott ESP érvénytelen" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Firmware frissítési támogatás engedélyezése a támogatott rendszereken" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Engedélyezi ezt a távoli tárolót?" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Engedélyezve" msgid "Enabled fwupdate debugging" msgstr "Fwupdate hibakeresés engedélyezve" #. 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, amely 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 "A saját felelősségére engedélyezze ezt a távoli tárolót." #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Firmware frissítési előzmények 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: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "A démonhoz kapcsolódás sikertelen" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "A függőben lévő eszközök lekérése sikertelen" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "A firmware frissítés telepítése sikertelen" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "A trükkök betöltése sikertelen" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Nem sikerült feldolgozni az argumentumokat" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "A --filter jelzőinek feldolgozása sikertelen" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Újraindítás sikertelen" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Az indítóképernyő módjának beállítása sikertelen" #. 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: 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, ~ előtag a kihagyáshoz, például „internal,~needs-reboot”" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware kiindulópont URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmware frissítés D-Bus szolgáltatás" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware frissítő démon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware segédprogram" #. 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 firmware metaadatok %u napja nem lettek frissítve, és lehet hogy elavultak." msgstr[1] "A firmware metaadatok %u napja nem lettek frissítve, és lehet hogy elavultak." msgid "Firmware updates are not supported on this machine." msgstr "A firmware frissítések nem támogatottak ezen a gépen." msgid "Firmware updates are supported on this machine." msgstr "A firmware frissítések támogatottak ezen a gépen." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Jelzők" msgid "Force the action ignoring all warnings" msgstr "A művelet erőltetése, az összes figyelmeztetés mellőzése" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Megtalálva" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID-ok" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. 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 "Minden eszköz lekérése, amelyek támogatják a firmware frissítéseket" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Az összes rendszeren regisztrált és engedélyezett bővítmény lekérdezése" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Részleteket kér le egy firmware fájlró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 list of updates for connected hardware" msgstr "A frissítések listáját kéri le 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 "A legutóbbi frissítésből származó eredményeket kéri le" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "A hardver újracsatlakoztatásra vár" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Üresjárat…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "A szogorú SSL ellenőrzések mellőzése a fájlok letöltésekor" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Biztonsági ellenőrzések mellőzése" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Telepítés hossza" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Firmware blob telepítése egy eszközre" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Egy firmware fájl telepítése ezen a hardveren" msgid "Install signed device firmware" msgstr "Aláírt eszköz firmware telepítése" msgid "Install signed system firmware" msgstr "Aláírt rendszer firmware telepítése" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Elsőként telepítés a szülő eszközre" msgid "Install unsigned device firmware" msgstr "Nem aláírt eszköz firmware telepítése" msgid "Install unsigned system firmware" msgstr "Nem aláírt rendszer firmware telepítése" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Firmware telepítése…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmware frissítés telepítése…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "%s telepítése…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Belső eszköz" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Rendszerbetöltő módban van" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Probléma" msgstr[1] "Problémák" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Kulcstartó" #. 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" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux gyártói firmware szolgáltatás (stabil firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux gyártói firmware szolgáltatás (teszt firmware)" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "A támogatott firmware-frissítések listázása" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Az elérhető firmware típusok listázása" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Betöltés…" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metaadatok aláírása" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaadat URI" #. 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 firmware szolgáltatásból." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Legkisebb verzió" #. 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: command description msgid "Modifies a given remote" msgstr "A megadott távoli tároló módosítása" 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: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "A telepítés után újraindítást igényel" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "A telepítés után kikapcsolást igényel" #. 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: nothing found msgid "No firmware IDs found" msgstr "Nem találhatók firmware azonosítók" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nem észlelhető firmware frissítési képességgel rendelkező hardver" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nem található bővítmény" #. 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: nothing was updated offline msgid "No updates were applied" msgstr "Nem lett frissítés alkalmazva" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Az alapértelmezett ESP útvonal felülbírálása" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "A firmware fájl részleteinek értelmezése és megjelenítése" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Jelszó" msgid "Payload" msgstr "Tartalom" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Százalék kész" #. 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: 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" msgid "Proceed with upload?" msgstr "Folytatja a feltöltést?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Tulajdonosi" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Firmware frissítési támogatás lekérdezése" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Egy firmware blob beolvasása egy eszközről" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Firmware beolvasása eszközről egy fájlba" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Firmware beolvasása egy partícióról 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: command description msgid "Refresh metadata from remote server" msgstr "Metaadatok frissítése a távoli kiszolgálóról" #. 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 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 "Replace data in an existing firmware file" msgstr "Adatok cseréje egy meglévő firmware 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: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Rendszerbetöltő szükséges" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Internetkapcsolat szükséges" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Újraindítja most?" #. TRANSLATORS: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Újraindítja a démont a változá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 "Return all the hardware IDs for the machine" msgstr "A géphez tartozó összes hardverazonosító visszaadása" #. 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: command line option msgid "Save device state into a JSON file between executions" msgstr "Eszköz állapotának mentése JSON fájlba a végrehajtások között" #. 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: Device has been chosen by the daemon for the user msgid "Selected device" msgstr "Kiválasztott eszköz" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sorozatszám" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "A hibakeresési jelző beállítása frissítéskor" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Beállítja a jóváhagyott firmwarek listáját" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Firmware frissítése előzmények megosztása a fejlesztőkkel" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Ügyfél és démon verziók 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 beállítások megjelenítése" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Eszközök, melyek nem frissíthetőek" #. 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 "Firmware frissítési előzmények megtekintése" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Bővítmény bőbeszédű információinak megjelenítése" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "A legutóbb megpróbált frissítés hibakeresési naplójának megjelenítése" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "A firmware frissítési állapot információinak megjelenítése" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Leállítja most?" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvánnyal" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvánnyal" #. 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: file size of the download msgid "Size" msgstr "Méret" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Forrás" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Adja meg a DFU eszköz gyártó-/termékazonosítóját" msgid "Specify the number of bytes per USB transfer" msgstr "Adja meg az USB átvitelek bájtjainak számát" #. 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 "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 "Új metaadatok sikeresen letöltve:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Távoli tároló sikeresen engedélyezve" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware sikeresen telepítve" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Konfigurációs érték sikeresen módosítva" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "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 "Metaadatok kézi frissítése sikeres" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "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 "Eszköz ellenőrzőösszegek sikeresen ellenőrizve" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Összegzés" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Távoli kiszolgálón nem támogatott " msgid "Target" msgstr "Cél" #. 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 $OS_RELEASE:NAME$ operációs rendszerrel. A disztribúció szállítója nem biztos, hogy ellenőrízte kompatibilitási szempontból a firmware frissítést. Mindent firmware-t csak az eredeti termék gyártója biztosít." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nincs jóváhagyott firmware." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ez a program jelenleg lehet, hogy csak rendszergazdaként működik" 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 firmware-t tartalmaz, amelyre nem vonatkozik embargó, de még teszteli a harvergyártó. Érdemes biztosítani a firmware kézi visszaállításáról, ha a firmware frissítése meghiúsul." #. 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: remote type, e.g. remote or local msgid "Type" msgstr "Típus" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmware segédprogram" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency msgid "Unknown" msgstr "Ismeretlen" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Ismeretlen eszköz" msgid "Unlock the device to allow access" msgstr "Eszköz feloldása hozzáférés engedélyezéséhez" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Eszköz feloldása a firmware eléréséhez" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "A hibakeresési jelző kikapcsolása frissítéskor" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nem támogatott démonverzió: %s, a kliensverzió %s" #. 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 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 the user if we can update the 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 hash 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: 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 -> %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s frissítése…" #. TRANSLATORS: ask the user to upload msgid "Upload report now?" msgstr "Feltölti most a jelentést?" #. 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 firmware jelentések segítenek a hardvergyártóknak, hogy gyorsan azonosítsák a hibás és sikeres frissítéseket valós eszközökön." #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Kerülőmegoldás jelzők használata a firmware 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" #. 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: 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: command description msgid "Write firmware from file into device" msgstr "Firmware írása fájlból egy eszközre" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Firmware írása fájlból egy partícióra" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Írás…" #. 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őrízte a firmware frissítés kompatibilitását a rendszerével és a kapcsolódó eszközeivel." fwupd-1.7.5/po/id.po000066400000000000000000002245171420024370600142340ustar00rootroot00000000000000# 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 # Andika Triwidada , 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Indonesian (http://www.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: 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: 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: 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: 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: 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: 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: 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: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(usang)" #. 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: 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: 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: 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 we want to shutdown 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 "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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NAMABERKAS-DST" msgid "BYTES" msgstr "BYTE" #. 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: 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: command description msgid "Build firmware using a sandbox" msgstr "Bangun firmware memakai suatu sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Batal" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Pilih cabang:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Pilih suatu peranti:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Pilih tipe firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Pilih sebuah rilis:" #. TRANSLATORS: get interactive prompt msgid "Choose a 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: 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Perangkat dapat memulihkan kegagalan flash" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Perangkat diubah:" #. 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: 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: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Perangkat dapat digunakan selama durasi pembaruan" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Perangkat dilepas:" #. 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 msgid "Devices with no available firmware updates: " msgstr "Perangkat tanpa pembaruan firmware yang tersedia: " #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Difungsikan" msgid "Enabled fwupdate debugging" msgstr "Diaktifkan debugging fwupdate" #. 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "BERKAS-MASUK BERKAS-KELUAR [SKRIP] [KELUARAN]" #. 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 [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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Utilitas Firmware" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Penyegaran terakhir metadata firmware: %s yang lalu. Gunakan --force untuk menyegarkan lagi." #. 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: 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 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 "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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID Keamanan Host:" #. 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 "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" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instal blob firmware pada perangkat" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Pasang suatu berkas firmware pada perangkat keras ini" 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: 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, #. * 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 "Kebijakan kesalahan 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 "Boot terverifikasi Intel BootGuard" #. 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Debugger Intel DCI" #. 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: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Sedang dalam mode bootloader" #. 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 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" 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: 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 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: 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: 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 daemon.conf 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" 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: 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: command line option msgid "Only show single PCR value" msgstr "Hanya tampilkan nilai PCR tunggal" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Hanya menggunakan IPFS saat mengunduh berkas" #. 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" 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: '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: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Perlindungan DMA pra-boot" #. 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" msgid "Proceed with upload?" msgstr "Lanjutkan mengunggah?" #. 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: command description msgid "Read a firmware blob from a device" msgstr "Baca blob 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: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID Remote" #. 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: 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 from the Internet msgid "Requires internet connection" msgstr "Memerlukan koneksi internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Mulai ulang sekarang?" #. TRANSLATORS: configuration 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 "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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI tulis" #. 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 line option msgid "Save device state into a JSON file between executions" msgstr "Simpan status perangkat ke berkas JSON di antara eksekusi" #. 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: %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: command line option msgid "Set the debugging flag during update" msgstr "Atur flag debugging selama pembaruan" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Setel daftar firmware yang disetujui" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Tampilkan informasi rinci pengaya" #. 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: 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 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: 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 "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: Must be plugged in to 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: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. 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: 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: 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: 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: 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: 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: 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: 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 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: 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 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 the user if we can update the 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 upload msgid "Upload report now?" msgstr "Unggah laporan sekarang?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Gunakan fwupdmgr --help untuk bantuan" #. TRANSLATORS: error message explaining command on 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: 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: 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: 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 "[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 "[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.7.5/po/it.po000066400000000000000000002350331420024370600142470ustar00rootroot00000000000000# 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-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Italian (http://www.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=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 minuto" msgstr[1] "Mancano %.0f minuti" #. 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: 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: 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: 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: 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: 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" #. 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." #. 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." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u ora" msgstr[1] "%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" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minuti" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u secondo" msgstr[1] "%u secondi" #. 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: 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: 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 we want to shutdown 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:" #. 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NOMEFILE-DST" msgid "BYTES" msgstr "BYTE" #. 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: 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: command description msgid "Build firmware using a sandbox" msgstr "Compila il firmware utilizzando una sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annulla" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Scegliere un ramo:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Scegliere un dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Scegliere un tipo di firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Scegliere un rilascio:" #. TRANSLATORS: get interactive prompt msgid "Choose a 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Il dispositivo è in grado di correggere problemi di flash" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificato:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo rimosso:" #. 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 msgid "Devices with no available firmware updates: " msgstr "Dispositivi senza aggiornamenti firmware:" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Abilitato" msgid "Enabled fwupdate debugging" msgstr "Debug fwupdate abilitato" #. 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: 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" #. 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: Suffix: the fallback HSI result #. 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 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Strumento gestione 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." #. TRANSLATORS: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Ultimo aggiornamento metadati firmware: %s fa. Usare --force per aggiornare nuovamente." #. 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: 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 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: 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: length of time the update takes to apply msgid "Install Duration" msgstr "Tempo di installazione" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Installa blob firmware su un dispositivo" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installa un file di firmware su questo hardware" 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: 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 da 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, #. * 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, #. * 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Debugger Intel DCI" #. 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: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "È in modalità bootloader" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CHIAVE,VALORE" #. 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" 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: 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 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: 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: 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 daemon.conf 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 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" 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: 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: command line option msgid "Only show single PCR value" msgstr "Mostra solo il valore PCR" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Usa IPFS solo quando si scaricano 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: 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dipendenze plugin mancanti" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protezione DMA pre-boot" #. 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à" msgid "Proceed with upload?" msgstr "Procedere con il caricamento?" #. 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: command description msgid "Read a firmware blob from a device" msgstr "Legge un blob 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: 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: 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: 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 from the Internet msgid "Requires internet connection" msgstr "Richiede una connessione a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Riavviare ora?" #. TRANSLATORS: configuration 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: this is shown in the MOTD msgid "Run `fwupdmgr sync-bkc` to complete this action." msgstr "Eseguire «fwupdmgr sync-bkc» per completare questa azione." #. 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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "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: command line option msgid "Save device state into a JSON file between executions" msgstr "Salva lo stato del dispositivo tra le esecuzioni su un file JSON" #. 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: %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: command line option msgid "Set the debugging flag during update" msgstr "Imposta il flag di debug durante l'aggiornamento" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Imposta l'elenco dei firmware approvati" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra informazioni dettagliate del plugin" #. 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: 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 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" #. 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: 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 "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: command description msgid "Sync firmware versions to the host best known configuration" msgstr "Sincronizza le versioni del firmware con quelle migliori note dell'host" #. TRANSLATORS: Must be plugged in to 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: 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" #. 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: 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: 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-bkc` #, 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: 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: 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: 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 "Partizione ESP UEFI non rilavata o non configurata" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Strumento firmware 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: 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 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: 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 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 the user if we can update the 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" 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: ask the user to upload msgid "Upload report now?" msgstr "Caricare il rapporto ora?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Usare «fwupdmgr --help» per maggiori informazioni" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "Attenzione:" #. 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 "[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 "[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.7.5/po/its/000077500000000000000000000000001420024370600140645ustar00rootroot00000000000000fwupd-1.7.5/po/its/appdata.its000066400000000000000000000013501420024370600162160ustar00rootroot00000000000000 fwupd-1.7.5/po/its/appdata.loc000066400000000000000000000005121420024370600161730ustar00rootroot00000000000000 fwupd-1.7.5/po/ja.po000066400000000000000000000221021420024370600142140ustar00rootroot00000000000000# 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://www.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.7.5/po/kk.po000066400000000000000000000024751420024370600142420ustar00rootroot00000000000000# 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.7.5/po/ko.po000066400000000000000000002225301420024370600142420ustar00rootroot00000000000000# 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 # Shinjo Park , 2018-2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Korean (http://www.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: 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 we want to shutdown 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 "BUILDER-XML FILENAME-DST" msgid "BYTES" msgstr "BYTES" #. 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: command description msgid "Build firmware using a sandbox" msgstr "샌드박스에서 펌웨어 빌드" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "취소" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "브랜치 선택:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "장치를 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "펌웨어 종류를 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "출시 버전을 선택하십시오:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" 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 "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: 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 msgid "Devices with no available firmware updates: " msgstr "펌웨어 업데이트를 사용할 수 없는 장치:" #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "최신 펌웨어가 설치된 장치:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 "timestamp 접두사를 포함하지 않기" #. 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 the user 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" #. 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 [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: Suffix: the fallback HSI result #. 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 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "펌웨어 메타데이터 최근 새로 고침: %s 전. 다시 새로 고치려면 --force 옵션을 사용하십시오." #. 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: 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 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: 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: 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 "설치 예상 시간" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "장치에 펌웨어 파일 설치" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI 디버거" #. 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 "KEY,VALUE" #. 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 "라이선스" 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 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 "리눅스 제조사 펌웨어 서비스에서 메타데이터를 가져올 수 있습니다." #. 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 daemon.conf 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: command line option msgid "Only use IPFS when downloading files" msgstr "파일을 다운로드할 때 IPFS만 사용" #. 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 "암호" 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 "REMOTE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "REMOTE-ID KEY VALUE" #. 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 "원격 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 from the Internet msgid "Requires internet connection" msgstr "인터넷 연결 필요" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "지금 다시 시작하시겠습니까?" #. TRANSLATORS: configuration 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 "install-blob 사용 시 플러그인 정리 루틴 실행" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "install-blob 사용 시 플러그인 준비 루틴 실행" #. 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 "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "하드웨어 ID를 생성할 수 있는 파일 저장" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "실행하는 동안의 장치 상태를 JSON 파일로 저장" #. 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: %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: this is for plugin development msgid "Show plugin verbose information" 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 "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: Must be plugged in to an outlet msgid "System requires external power source" msgstr "시스템에 외부 전원을 연결해야 함" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. 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: 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$와(과) 별개로 운영되는 독립된 법적 단체에서 운영하는 무료 서비스입니다. 배포판 개발사에서 펌웨어 업데이트와 시스템 및 연결한 장치의 호환성을 검증한다는 보장이 없습니다. 모든 펌웨어는 장치 제조사(OEM)에서 직접 제공합니다." #. 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: %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 "이 프로그램은 루트 권한으로만 올바르게 작동할 수도 있습니다" 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 "이 도구는 루트로만 사용할 수 있습니다" #. 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 "레거시 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: 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "지금 보고서를 업로드하시겠습니까?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "도움말을 보려면 fwupdmgr --help 명령을 사용하십시오" #. TRANSLATORS: error message explaining command on 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: 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: 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 "[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 "[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 "[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.7.5/po/ky.po000066400000000000000000000032341420024370600142520ustar00rootroot00000000000000# 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.7.5/po/lt.po000066400000000000000000001333251420024370600142530ustar00rootroot00000000000000# 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 # Moo, 2020-2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Lithuanian (http://www.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ė" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s akumuliatoriaus atnaujinimas" #. 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 atnaujinimas" #. 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 atnaujinimas" #. 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 atnaujinimas" #. 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 atnaujinimas" #. 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 atnaujinimas" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s klaviatūros atnaujinimas" #. 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 atnaujinimas" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s pelės atnaujinimas" #. 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 sisteminis atnaujinimas" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s jutiklinio kilimėlio atnaujinimas" #. 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 atnaujinimas" #. 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 "Aktyvuoti naują programinę aparatinę įrangą įrenginyje" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktyvuojamas programinės aparatinės įrangos atnaujinimas" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktyvuojama programinė aparatinė į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 "Alternatyvusis %s pavadinimas" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Leisti sendinti programinės aparatinės įrangos versijas" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Leisti iš naujo įdiegti esamas programinės aparatinės įrangos versijas" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Atnaujinimo užbaigimui, reikia paleisti sistemą iš naujo." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "Atnaujinimo 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 programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Taikyti atnaujinimą, netgi kai nepatariama" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Taikyti atnaujinimo failus" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Taikomas atnaujinimas…" #. 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 į programinės aparatinė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 programinę aparatinę į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 kompiuteryje sendinti programinę aparatinę į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 programinės aparatinės įrangos atnaujinimams 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 programinės aparatinė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ą programinės aparatinė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 programinę aparatinę į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 kompiuteryje atnaujinti programinę aparatinę į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 įrenginiui saugomas kontrolines sumas, reikalingas tapatybės nustatymas" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Susieti naują branduolio tvarkyklę" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Sukurti programinę aparatinę įrangą, naudojant smėliadėžę" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Atsisakyti" #. TRANSLATORS: this is when a device ctrl+c's a watch 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 atnaujinimas jau yra pritaikytas." #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Pakeistas" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolinė suma" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Pasirinkite įrenginį:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Pasirinkite laidą:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Išvalo rezultatus iš paskutinio atnaujinimo" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komanda nerasta" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Konvertuoti programinės aparatinė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 "ĮPAĮ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: 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 vėliavėlės" #. 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, kurie nebuvo teisingai atnaujinti:" #. 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ų programinės aparatinės įrangos atnaujinimų: " #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 programinę aparatinę į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" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Įjungti programinės aparatinės įrangos atnaujinimo palaikymą palaikomose sistemose" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Įjungti šią nuotolinę saugyklą?" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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ą" #. 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 "Ištrinti visą programinės aparatinės įrangos atnaujinimų 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 programinės aparatinė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-PAVADINIMAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FAILO-PAVADINIMAS LIUDIJIMAS PRIVATUSIS-RAKTAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FAILO-PAVADINIMAS ĮRENGINIO-ALT-PAVADINIMAS|ĮRENGINIO-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FAILO-PAVADINIMAS ĮRENGINIO-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FAILO-PAVADINIMAS [ĮRENGINIO-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FAILO-PAVADINIMAS [PROGRAMINĖS-APARATINĖS-ĮRANGOS-TIPAS]" #. TRANSLATORS: Suffix: the fallback HSI result #. 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 atnaujinimo" #. TRANSLATORS: we could not talk to the fwupd daemon 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 programinės aparatinės įrangos atnaujinimo" #. 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 pavadinimas" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Failo pavadinimo parašas" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Reikalingas failo pavadinimas" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Pagrindinis programinės aparatinės įrangos URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Programinės aparatinės įrangos atnaujinimo D-Bus tarnyba" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Programinės aparatinės įrangos atnaujinimo tarnyba" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Programinės aparatinės įrangos paslaugų programa" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Programinė aparatinė įranga jau užblokuota" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Programinės aparatinės įrangos atnaujinimai" msgid "Firmware updates are not supported on this machine." msgstr "Šiame kompiuteryje programinės aparatinės įrangos atnaujinimai yra neprieinami." msgid "Firmware updates are supported on this machine." msgstr "Šiame kompiuteryje yra prieinami programinės aparatinės įrangos atnaujinimai." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Vėliavėlės" 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 description msgid "Get all device flags supported by fwupd" msgstr "Gauti visas fwupd palaikomas įrenginio vėliavėles" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Gauti visus įrenginius, kurie palaiko programinės aparatinės įrangos atnaujinimus" #. 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 programinės aparatinė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 atnaujinimų 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 atnaujinimo" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Neveiklus…" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Nepaisyti programinės aparatinės įrangos kontrolinės sumos nesėkmių" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Įdiegimo trukmė" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Įdiegti įrenginyje programinės aparatinės įrangos dvejetainį išplėstinį objektą" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Įdiegti šioje aparatinėje įrangoje programinės aparatinės įrangos failą" msgid "Install signed device firmware" msgstr "Įdiegti pasirašytą įrenginio programinę aparatinę įrangą" msgid "Install signed system firmware" msgstr "Įdiegti pasirašytą sistemos programinę aparatinę į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 programinę aparatinę įrangą" msgid "Install unsigned system firmware" msgstr "Įdiegti nepasirašytą sistemos programinę aparatinę įrangą" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Įdiegiama programinė aparatinė įranga…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Įdiegiamas programinės aparatinės įrangos atnaujinimas…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Įdiegiama ties %s…" #. TRANSLATORS: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI derintuvė" #. 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 viena minutė" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licencija" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux tiekėjų programinės aparatinės įrangos paslauga (stabili programinė aparatinė įranga)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux tiekėjų programinės aparatinės įrangos paslauga (testuojama programinė aparatinė į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 programinės aparatinės įrangos atnaujinimus" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Išvardyti prieinamus programinės aparatinė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 tiekėjų programinės aparatinės įrangos 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ų programinės aparatinė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 programinės aparatinės įrangos atnaujinimo 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 atnaujinimai" #. 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 "Only use IPFS when downloading files" msgstr "Naudoti IPFS tik atsisiunčiant failus" #. 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 teiginius" #. 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 programinės aparatinės įrangos atnaujinimų palaikymo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Skaityti programinę aparatinę įrangą iš įrenginio į failą" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Skaityti programinę aparatinę į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 programinę aparatinę į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 programinės aparatinės įrangos faile" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Ataskaitų URI" #. TRANSLATORS: metadata is downloaded from the Internet msgid "Requires internet connection" msgstr "Reikalauja interneto ryšio" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Paleisti iš naujo dabar?" #. TRANSLATORS: configuration 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 "Save device state into a JSON file between executions" msgstr "Įrašyti įrenginio būseną tarp paleidimų į JSON failą" #. 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šsamesnei informacijai, žiūrėkite %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 "Atnaujinimo metu nustatyti derinimo vėliavėlę" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Nustato patvirtintą programinės aparatinės įrangos sąrašą" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Bendrinti programinės aparatinė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 atnaujinti į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 programinės aparatinės įrangos atnaujinimų istoriją" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Rodyti išsamią įskiepio informaciją" #. 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 atnaujinimo" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Rodyti programinės aparatinės įrangos atnaujinimo 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 programinę aparatinę į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 ĮPAĮA įrenginio tiekėją/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 "Programinė aparatinė į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 in to 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: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nėra jokios patvirtintos programinės aparatinė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" #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Šis įrankis leidžia administratoriui taikyti UEFI dbx atnaujinimus." #. 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 programinės aparatinė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 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į programinės aparatinė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 "Atnaujinimo metu panaikinti derinimo vėliavėlės nustatymą" #. 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 atnaujinti" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Atnaujinimo klaida" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Atnaujinimo pranešimas" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Atnaujinimo 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 "Atnaujinimo nesėkmė yra žinoma problema, išsamesnei informacijai apsilankykite šiame URL:" #. TRANSLATORS: ask the user if we can update the metadata msgid "Update now?" msgstr "Atnaujinti dabar?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Atnaujinimas reikalauja paleidimo iš naujo" msgid "Update the stored device verification information" msgstr "Atnaujinti saugomo įrenginio patikrinimo informaciją" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Atnaujinti saugomus metaduomenis esamu turiniu" msgid "Updating" msgstr "Atnaujinama" #. 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 "Atnaujinama %s iš %s į %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Atnaujinama %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: ask the user to upload msgid "Upload report now?" msgstr "Išsiųsti ataskaitą dabar?" #. 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 "Programinės aparatinės įrangos ataskaitų išsiuntimas padeda aparatinės įrangos tiekėjams greitai atpažinti nesėkmingus bei sėkmingus atnaujinimus 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 programinę aparatinę įrangą iš failo į įrenginį" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Rašyti programinę aparatinę į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, suderinamumui su jūsų sistema ar prijungtais įrenginiais, nėra patvirtinęs jokių programinės aparatinės įrangos atnaujinimų." #. 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.7.5/po/meson.build000066400000000000000000000004341420024370600154300ustar00rootroot00000000000000i18n.gettext(meson.project_name(), preset : 'glib', args: [ '--default-domain=' + meson.project_name(), ] ) run_target('fix-translations', command: [ join_paths(meson.source_root(), 'contrib/fix_translations.py'), join_paths(meson.source_root(), 'po') ]) fwupd-1.7.5/po/nl.po000066400000000000000000000211311420024370600142340ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Richard E. van der Luit , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Dutch (http://www.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: 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: 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 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: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Geannuleerd" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Gewijzigd" #. 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: 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 "Bezig met uitpakken..." #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Apparaat toegevoegd:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Apparaat gewijzigd:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Apparaat verwijderd:" #. 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: 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: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Het doorvoeren van argumenten is mislukt" #. 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: program name msgid "Firmware Utility" msgstr "Firmware-hulpmiddel" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Gevonden" #. 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 "Alle apparaten verkrijgen die firmware-updates ondersteunen" #. 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: daemon is inactive msgid "Idle…" msgstr "Slaapt..." #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Een firmware-bestand op deze hardware 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: parsing the firmware information msgid "Loading…" msgstr "Bezig met laden..." #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "De achtergrondservice controleren op gebeurtenissen" #. 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: Suffix: the HSI result msgid "OK" msgstr "Oké" #. 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: command description msgid "Refresh metadata from remote server" msgstr "Metadata verversen vanuit externe server" #. 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: command description msgid "Replace data in an existing firmware file" msgstr "Gegevens vervangen in een bestaand firmwarebestand" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Bezig met herstarten van apparaat..." #. 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 "Bezig met inplannen..." #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Foutopsporingsopties weergeven" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Extra foutopsporingsinformatie weergeven" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency msgid "Unknown" msgstr "Onbekend" msgid "Unlock the device to allow access" msgstr "Ontgrendel het apparaat om toegang te verlenen" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Ontgrendelt het apparaat voor firmware-toegang" msgid "Update the stored device verification information" msgstr "Opgeslagen apparaatverificatie-informatie 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: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Bezig met valideren..." #. 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: writing to the flash chips msgid "Writing…" msgstr "Bezig met schrijven..." fwupd-1.7.5/po/oc.po000066400000000000000000000077441420024370600142420ustar00rootroot00000000000000# 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.7.5/po/pa.po000066400000000000000000000260731420024370600142350ustar00rootroot00000000000000# 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://www.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: 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 in to 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.7.5/po/pl.po000066400000000000000000002545121420024370600142510ustar00rootroot00000000000000# 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-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Polish (http://www.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: 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 urządzenia %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: 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: 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: 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: 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: 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: 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: 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: 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: 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: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Ukończenie aktualizacji wymaga ponownego uruchomienia." #. TRANSLATORS: explain why we want to shutdown 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 "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 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NAZWA-PLIKU-DOCELOWEGO" msgid "BYTES" msgstr "BAJTY" #. 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 firmware file" msgstr "Buduje plik oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Buduje oprogramowanie sprzętowe za pomocą piaskownicy" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Anuluj" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Wybór gałęzi:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Wybór urządzenia:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Wybór typu oprogramowania sprzętowego:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Wybór wydania:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Proszę wybrać wolumin:" #. 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: 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: 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: 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: 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: this is when a device has been updated msgid "Device changed:" msgstr "Zmieniono urządzenie:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Usunięto urządzenie:" #. 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 przez 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 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "PLIK-WEJŚCIOWY PLIK-WYJŚCIOWY [SKRYPT] [WYJŚCIE]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Narzędzie 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Ostatnie odświeżenie metadanych oprogramowania sprzętowego: %s temu. Parametr --force odświeży ponownie." #. 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: 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: 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 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 "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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Identyfikator zabezpieczeń komputera:" #. 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 "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: length of time the update takes to apply msgid "Install Duration" msgstr "Czas trwania instalacji" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instaluje zamknięte oprogramowanie sprzętowe na urządzeniu" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instaluje plik oprogramowania sprzętowego na tym sprzęcie" 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: 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: 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, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Bezpiecznik OTP Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Błąd zasad 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, #. * 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Debuger Intel DCI" #. 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: 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" 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 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 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: 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 msgid "MEI manufacturing mode" msgstr "Tryb produkcyjny 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: 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: smallest version number installable on device msgid "Minimum Version" msgstr "Minimalna wersja" #. 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 daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "UWAGA: ten program może działać poprawnie tylko jako root" #. 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 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" 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: command line option msgid "Only show single PCR value" msgstr "Wyświetla tylko jedną wartość PCR" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Używa IPFS tylko 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: 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Brak zależności wtyczki" #. 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: 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" msgid "Proceed with upload?" msgstr "Kontynuować wysyłanie?" #. 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: 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 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: 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 "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: 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 from the Internet msgid "Requires internet connection" msgstr "Wymaga połączenia z Internetem" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Uruchomić ponownie?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Zwraca wszystkie identyfikatory sprzętu dla komputera" #. 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-bkc` to complete this action." msgstr "Polecenie „fwupdmgr sync-bkc” 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: 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: 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: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Zapis 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 line option msgid "Save device state into a JSON file between executions" msgstr "Zapisuje stan urządzenia do pliku JSON między wykonaniami" #. 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" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Ustawia flagę debugowania podczas aktualizacji" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Ustawienie listy zatwierdzonego oprogramowania sprzętowego" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Wyświetla więcej informacji o wtyczkach" #. 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: 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 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: one line summary of device msgid "Summary" msgstr "Podsumowanie" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Obsługiwane" #. 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: 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 host best known configuration" msgstr "Synchronizuje wersje oprogramowania sprzętowego do najlepszej znanej konfiguracji komputera" #. TRANSLATORS: Must be plugged in to 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: 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: 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: 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: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Dostawca nie dostarczył żadnych informacji o wydaniu." #. 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-bkc` #, 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: 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 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: We verified the meatdata 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: 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: 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: 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: Suffix: the HSI result msgid "Unencrypted" msgstr "Niezaszyfrowane" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: command line option msgid "Unset the debugging flag during update" msgstr "Usuwa flagę debugowania podczas aktualizacji" #. 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 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 the user if we can update the 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 upload msgid "Upload report now?" msgstr "Wysłać zgłoszenie?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Polecenie fwupdmgr --help wyświetli pomoc" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "OSTRZEŻENIE:" #. 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: 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: 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 "[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 "[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 "[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.7.5/po/pt.po000066400000000000000000002244271420024370600142630ustar00rootroot00000000000000# 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 # Peter J. Mello , 2020 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Portuguese (http://www.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=2; plural=(n != 1);\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" #. 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" #. 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." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%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" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minutos" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segundo" msgstr[1] "%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 we want to shutdown 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:" #. 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 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 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: 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: command description msgid "Build firmware using a sandbox" msgstr "Compila o firmware usando uma sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: remote checksum msgid "Checksum" msgstr "Soma de verificação" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose a branch:" msgstr "Escolha uma ramificação:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Escolha um dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Escolha um tipo de firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Escolha um lançamento:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Escolha um volume:" #. 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: 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 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 msgid "Devices with the latest available firmware version:" msgstr "Dispositivos com a versão mais recente do firmware disponível:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 the user 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" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FICHEIRO-ENTRADA FICHEIRO-SAÍDA [SCRIPT] [SAÍDA]" #. 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: Suffix: the fallback HSI result #. 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 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." #. TRANSLATORS: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Última atualização dos metadados do firmware: %s atrás. Use --force para atualizar novamente." #. 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" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. 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" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instala um blob de firmware em um dispositivo" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instala um ficheiro de firmware neste periférico" 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Depurador Intel DCI" #. 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" #. 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 daemon.conf 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: command line option msgid "Only use IPFS when downloading files" msgstr "Usa apenas IPFS ao baixar ficheiros" #. 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: 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: 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 from the Internet msgid "Requires internet connection" msgstr "Requer ligação à Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reiniciar agora?" #. TRANSLATORS: configuration 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 "Save device state into a JSON file between executions" msgstr "Guarda o estado do dispositivo em um ficheiro JSON entre execuções" #. 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" #. 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: 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra informação verbosa de plugin" #. 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" #. 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: 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 in to 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: 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" #. 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: 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Enviar relatório agora?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Use fwupdmgr --help para ajuda" #. TRANSLATORS: error message explaining command on 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 "AVISO:" #. 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.7.5/po/pt_BR.po000066400000000000000000002241221420024370600146360ustar00rootroot00000000000000# 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-2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Portuguese (Brazil) (http://www.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=2; plural=(n > 1);\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" #. 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 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: 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" #. 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." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%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" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minutos" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segundo" msgstr[1] "%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 "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: 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 we want to shutdown 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:" #. 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 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 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 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: 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: 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: command description msgid "Build firmware using a sandbox" msgstr "Compila o firmware usando uma sandbox" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: remote checksum msgid "Checksum" msgstr "Soma de verificação" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose a branch:" msgstr "Escolha uma ramificação:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Escolha um dispositivo:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Escolha um tipo de firmware:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Escolha um lançamento:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Escolha um volume:" #. 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 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: 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: 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 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 msgid "Devices with the latest available firmware version:" msgstr "Dispositivos com a versão mais recente do firmware disponível:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 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 the user 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 "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 "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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "Habilitado" msgid "Enabled fwupdate debugging" msgstr "Depuração de fwupdate habilitada" #. 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: 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "ARQUIVO-ENTRADA ARQUIVO-SAÍDA [SCRIPT] [SAÍDA]" #. 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 [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: Suffix: the fallback HSI result #. 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 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: 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." #. TRANSLATORS: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Última atualização dos metadados do firmware: %s atrás. Use --force para atualizar novamente." #. 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: 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 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: 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 "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" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Instala um blob de firmware em um dispositivo" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Instala um arquivo de firmware neste periférico" 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: 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 "Habilitado para Intel CET" #. TRANSLATORS: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Depurador Intel DCI" #. 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 gerenciador de boot" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "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 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 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 daemon.conf 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 "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 habilitado, 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: command line option msgid "Only show single PCR value" msgstr "Mostra apenas valor único de PCR" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Usa apenas IPFS ao baixar arquivos" #. 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" 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: 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 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: 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: 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 from the Internet msgid "Requires internet connection" msgstr "Requer conexão com a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reiniciar agora?" #. TRANSLATORS: configuration 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: 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 "Salva um arquivo que permite a geração de IDs de hardware" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Salva o estado do dispositivo em um arquivo JSON entre execuções" #. 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: %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" #. 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: 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Mostra informação verbosa de plugin" #. 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: 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 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" #. 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: 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 in to 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: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. 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: %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: 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: 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 fazer downgrade manual 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 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: 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 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 essa URL para mais informações:" #. TRANSLATORS: ask the user if we can update the 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" 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: ask the user to upload msgid "Upload report now?" msgstr "Enviar relatório agora?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Use fwupdmgr --help para ajuda" #. TRANSLATORS: error message explaining command on 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: 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 "[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.7.5/po/ru.po000066400000000000000000001154721420024370600142650ustar00rootroot00000000000000# 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 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Russian (http://www.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: 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: 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: 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: 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 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: 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: 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: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Завершение обновления требует перезагрузки." #. TRANSLATORS: explain why we want to shutdown 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: 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 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: command description msgid "Build firmware using a sandbox" msgstr "Собрать прошивку, используя песочницу" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Отменить" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Отменено" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Изменено" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Контрольная сумма" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Выберите устройство:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Выберите релиз:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Очистить результаты c последнего обновления" #. 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: multiline description of device msgid "Description" msgstr "Описание" #. TRANSLATORS: command description msgid "Detach to bootloader mode" 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: 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: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Понижение версии прошивки устройства %s…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Получение..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Записать данные SMBIOS из файла" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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: we could not talk to the fwupd daemon 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: 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: 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: 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 дня и могут быть устаревшими." msgstr[1] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." msgstr[2] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." msgstr[3] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." 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: 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 "Получить все устройства, которые поддерживают обновления прошивки" #. 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 "Бездействие…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Установить двоичную прошивку на устройство" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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: 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 Vendor Firmware Service (стабильная прошивка)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (тестовая прошивка)" #. 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 Vendor Firmware Service." #. 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: 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: nothing was updated offline msgid "No updates were applied" 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: console message when not using plymouth msgid "Percentage complete" msgstr "Процент завершения" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Пожалуйста, введите число от 0 до %u:" 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: 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: console message when not using plymouth msgid "Rebooting…" 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 from the Internet msgid "Requires internet connection" msgstr "Требуется подключение к сети Интернет" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Перезагрузить сейчас?" #. TRANSLATORS: configuration 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: 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: command line option msgid "Save device state into a JSON file between executions" msgstr "Сохранить состояние устройства в файл JSON между выполнениями" #. 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: 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 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: this is for plugin development msgid "Show plugin verbose information" 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: 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 "Подпись" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Определить идентификатор(ы) поставщика / продукта устройства DFU" msgid "Specify the number of bytes per USB transfer" msgstr "Определить количество байтов на передачу USB" #. 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 — это бесплатный сервис, действующий как независимое юридическое лицо, которое не имеет связи с системой $OS_RELEASE:NAME$. Ваш распространитель системы может не проверять какие-либо обновления прошивки на совместимость с системой или подключенными устройствами. Каждая прошивка поставляется только производителем оригинального оборудования." #. 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: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Этот инструмент может использовать только пользователь root" #. 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 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: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Неподдерживаемая фоновая служба версии %s, клиент версии %s" #. 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 the user if we can update the 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 "Обновить сохраненные метаданные с текущим содержимым" #. 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: ask the user to upload msgid "Upload report now?" msgstr "Загрузить отчет сейчас?" #. 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.7.5/po/si.po000066400000000000000000000126751420024370600142530ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # හෙළබස, 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Sinhala (http://www.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: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "අනුවාදය %s" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s ට අපනාමය" #. 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 "යාවත්කාලය යොදමින්…" msgid "BYTES" msgstr "බයිට" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "අවලංගු කරන්න" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "අවලංගු කෙරිණි" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "වෙනස් විය" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used msgid "Disabled" msgstr "අබල කර ඇත" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "බාගත වෙමින්…" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled msgid "Enabled" msgstr "සබල කර ඇත" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "මකා දැමෙමින්…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ගොනුවේනම" #. TRANSLATORS: Suffix: the fallback HSI result #. 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: another fwupdtool instance is already running msgid "Failed to lock" msgstr "අගුළු ලෑමට අසමත් විය" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" 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: Suffix: the HSI result msgid "Invalid" msgstr "වලංගු නොවේ" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "පූරණය වෙමින්…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "අගුළු ලා ඇත" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "සහාය නොදක්වයි" #. TRANSLATORS: Suffix: the HSI result msgid "OK" 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: 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: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "උපාංගය නැවත ඇරඹෙමින්…" #. TRANSLATORS: command line option msgid "Show all results" msgstr "සියළුම ප්‍රතිඵල පෙන්වන්න" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "සහාය දක්වයි" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency msgid "Unknown" msgstr "නොදනී" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "අගුළු හැර ඇත" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" 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: writing to the flash chips msgid "Writing…" msgstr "ලියවෙමින්…" fwupd-1.7.5/po/sk.po000066400000000000000000000165311420024370600142500ustar00rootroot00000000000000# 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://www.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 msgid "Cancelled" msgstr "Zrušené" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Zmenené" #. 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: 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 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" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Nainštaluje súbor firmvéru do tohoto hardvéru" 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.7.5/po/sr.po000066400000000000000000000425461420024370600142640ustar00rootroot00000000000000# 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://www.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: command description msgid "Build firmware using a sandbox" msgstr "Изгради фирмвер унутар кутије" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Отказао" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Променио" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Чек-сума" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Изаберите уређај:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 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 "Мирујем…" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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 from the Internet 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: this is for plugin development msgid "Show plugin verbose information" 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" 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 "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.7.5/po/sv.po000066400000000000000000002426451420024370600142720ustar00rootroot00000000000000# 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-2022 # 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://www.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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "En uppdatering kräver en omstart för att färdigställas." #. TRANSLATORS: explain why we want to shutdown msgid "An update requires the system to shutdown to complete." msgstr "En uppdatering kräver att systemet stängs ned för att färdigställas." #. 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 "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 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 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 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: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BYGGAR-XML FILNAMN-MÅL" msgid "BYTES" msgstr "BYTE" #. 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 firmware file" msgstr "Bygg en fast programvarufil" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Bygg fast programvara med en sandlåda" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Avbryt" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Välj en gren:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Välj en enhet:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Välj en typ av fast programvara:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Välj en utgåva:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Välj en 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: 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: 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: 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: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Enhet kan återhämta sig från flashningsfel" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Enhet ändrad:" #. 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: 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: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Enhet är oanvändbar under tiden som uppdateringen pågår" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Enhet borttagen:" #. 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 "Enheten 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 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 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:n" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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: 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 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: ask the user 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: 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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" 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" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "INFIL UTFIL [SKRIPT] [UTMATNING]" #. 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: Suffix: the fallback HSI result #. 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: we could not talk to the fwupd daemon 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" #. 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: 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: 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: 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: program name msgid "Firmware Utility" msgstr "Fast programvaruverktyg" #. 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Senaste uppdatering för metadata för fast programvara: %s sedan. Använd --force för att uppdatera igen." #. 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: 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: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID:n" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. 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 "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: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Säkerhets-ID för värd:" #. 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-enhetsskydd inaktiverat" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU-enhetsskydd aktiverat" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Väntar…" #. 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: length of time the update takes to apply msgid "Install Duration" msgstr "Installationstid" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Installera en fast programvaru-blob på en enhet" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installera en fast programvarufil på denna hårdvara" 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: 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: 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, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP-säkring" #. 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: 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, #. * 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 aktiverad" #. TRANSLATORS: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI-felsökare" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Intern enhet" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Ogiltig" #. 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" 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 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 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: 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 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: 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: smallest version number installable on device msgid "Minimum Version" msgstr "Minimiversion" #. 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 daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "OBS: Detta program kommer endast fungera korrekt som root" #. 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 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" 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: command line option msgid "Only show single PCR value" msgstr "Visa endast enkelt PCR-värde" #. TRANSLATORS: command line option msgid "Only use IPFS when downloading files" msgstr "Använd endast IPFS när du hämtar 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: 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: '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: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Beroenden för insticksmodul saknas" #. 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: 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" msgid "Proceed with upload?" msgstr "Fortsätt med att skicka upp?" #. 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: 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 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: 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 "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 "Rapporterade till fjärrserver" #. 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 from the Internet msgid "Requires internet connection" msgstr "Kräver internetanslutning" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Starta om nu?" #. TRANSLATORS: configuration 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 "Return all the hardware IDs for the machine" msgstr "Returnera alla hårdvaru-ID:n för maskinen" #. 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-bkc` to complete this action." msgstr "Kör `fwupdmgr sync-bkc` 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: 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: 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: 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 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 line option msgid "Save device state into a JSON file between executions" msgstr "Spara enhetstillstånd i en JSON-fil mellan körningar" #. 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" #. 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: command line option msgid "Set the debugging flag during update" msgstr "Ställ in felsökningsflaggan under uppdatering" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Sätter listan av godkänd fast programvara" #. 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: this is for plugin development msgid "Show plugin verbose information" msgstr "Visa utförlig information om insticksmodul" #. 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: 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 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: one line summary of device msgid "Summary" msgstr "Sammanfattning" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "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: 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 "Ändra gren från %s till %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Ändra gren för fast programvara på enheten" #. TRANSLATORS: command description msgid "Sync firmware versions to the host best known configuration" msgstr "Synkronisera versioner för fast programvara till den mest kända konfigurationen hos värden" #. TRANSLATORS: Must be plugged in to 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: 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: 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: 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: 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: 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: 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årdvaruleverantören." #. 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: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Tillverkaren tillhandahöll inte några kommentarer till utgåvan." #. 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-bkc` #, 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: 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 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: We verified the meatdata 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: 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: 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 "Misslyckades med att ansluta till tjänst" #. 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: Suffix: the HSI result msgid "Unencrypted" msgstr "Okrypterad" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency 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: command line option msgid "Unset the debugging flag during update" msgstr "Ta bort felsökningsflaggan under uppdatering" #. 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 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 the user if we can update the 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 upload msgid "Upload report now?" msgstr "Skicka upp rapport nu?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Använd fwupdmgr --help för hjälp" #. TRANSLATORS: error message explaining command on 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: this is a prefix on the console msgid "WARNING:" msgstr "VARNING:" #. 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: 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: 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 "[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 "[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 "[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.7.5/po/test-deps000077500000000000000000000025121420024370600151230ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """ Check dependencies needed for rasterization """ import sys import os err = 0 try: import gi except ImportError: print("Error: missing dependency python gobject introspection (python3-gi)") err = 1 try: gi.require_version("Pango", "1.0") from gi.repository import Pango except ValueError: print("Error: missing pango gobject introspection library") err = 1 try: gi.require_version("PangoCairo", "1.0") from gi.repository import PangoCairo except ValueError: print("Error: missing pangocairo gobject introspection library") err = 1 try: gi.require_version("cairo", "1.0") from gi.repository import cairo except ValueError: print("Error: missing cairo gobject introspection library") err = 1 try: import cairo except ImportError: print("Error: missing dependency python cairo (python3-cairo)") err = 1 # check that LINUGAS lists every language with a .po file with open("po/LINGUAS") 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 print("Error: missing translations for %s" % l[0]) sys.exit(err) fwupd-1.7.5/po/tr.po000066400000000000000000001201341420024370600142530ustar00rootroot00000000000000# 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 # 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://www.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 version" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u gün" msgstr[1] "%u gün" #. 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 cihaz 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 ürün yazılımını etkinleştirin" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Ürün 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 "Ürün 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 ürün 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 we want to shutdown 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 "Ürün 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ı ürün yazılımı:" #. 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 ürün 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 ürün yazılımının sürümünü indirmek 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 "Ürün 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 "Artalan süreci 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ı ürün 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 ürün 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 ürün 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 ürün 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: firmware version of bootloader msgid "Bootloader Version" msgstr "Önyükleyici Sürümü" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Ürün yazılımını korumalı alan kullanarak oluştur" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "İptal" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "İptal Edildi" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Değişti" #. 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 a branch:" msgstr "Bağlı kol seçiniz:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Bir aygıt seç:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Bir ürün yazılımı türü seç:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Sürüm seç:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" msgstr "Bölüm seçiniz:" #. 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: 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: 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: this is when a device is hotplugged msgid "Device removed:" msgstr "Aygıt çıkarıldı:" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Güncelleme yöntemi" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Cihaz için güncelleme yok." #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Bu cihaz için en son güncelleme:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 "Cihaz güvenlik kontrolleri yapma" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Geçmişi veritabanı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" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Desteklenen sistemlerde ürün 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 ürün 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILENAME" #. TRANSLATORS: Suffix: the fallback HSI result #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Başarısız" #. TRANSLATORS: we could not talk to the fwupd daemon msgid "Failed to connect to daemon" msgstr "Artalan sürecine 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 "Ürün yazılımı güncellemesi yüklenemedi" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Tuhaflıklar 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: 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: remote URI msgid "Firmware Base URI" msgstr "Ürün Yazılımı Temel URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Ürün Yazılımı Güncelleme D-Bus Hizmeti" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Ürün Yazılımı Güncelleme Artalan Süreci" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Ürün Yazılımı Yardımcı Programı" msgid "Firmware updates are not supported on this machine." msgstr "Ürün yazılımı güncellemeleri bu makinede desteklenmiyor." msgid "Firmware updates are supported on this machine." msgstr "Ürün 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 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 "Ürün 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 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 "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" msgid "Install signed device firmware" msgstr "İmzalı aygıt ürün yazılımını yükle" msgid "Install signed system firmware" msgstr "İmzalı sistem ürün yazılımını yükle" msgid "Install unsigned device firmware" msgstr "İmzasız aygıt ürün yazılımını yükle" msgid "Install unsigned system firmware" msgstr "İmzasız sistem ürün yazılımını yükle" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Ürün Yazılımı Kuruluyor…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Ürün yazılımını güncellemesi kuruluyor…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Kuruluyor %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI debugger" #. 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: 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 "KEY,VALUE" #. 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" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Tedarikçi Ürün Yazılımı Hizmeti (kararlı ürün yazılımı)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Tedarikçi Ürün Yazılımı Hizmeti (test ürün 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 supported firmware updates" msgstr "Desteklenen ürün yazılımı güncellemelerini listele" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Kullanılabilir ürün 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 "Üstveri İmzası" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Üstveri URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Üst veri Linux Tedarikçi Ürün 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 "Artalan süreci ile istemci uyuşmadı, bunun yerine %s kullanın" msgid "Modify a configured remote" msgstr "Uzak yapılandırmayı değiştir" msgid "Modify daemon configuration" msgstr "Artalan süreci yapılandırmasını değiştir" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Olaylar için artalan sürecini gözetle" #. 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 "Ürün yazılımı kimlik bilgisi bulunamadı" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Ürün 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ı" msgid "No updates available for remaining devices" msgstr "Bu cihaz için yüklenecek 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: 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 "Dizin" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Ürün yazılımı dosyası hakkındaki ayrıntıları ayrıştır ve göster" #. 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 "Ürün yazılımını aygıttan dosyaya oku" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Ürün 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 "Üstveriyi uzak sunucudan yenile" #. 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: metadata is downloaded from the Internet 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: configuration changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Değişikliklerin etkili olması için artalan süreci 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: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI lock" #. 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ı ürün yazılımı listesini ayarlar" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Ürün 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 artalan süreci 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 artalan süreci 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 "Ürün yazılımı güncellemelerinin geçmişini göster" #. TRANSLATORS: this is for plugin development msgid "Show plugin verbose information" msgstr "Eklenti ayrıntılı bilgilerini 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 "Ürün 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 "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 üstveri başarıyla indirildi:" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Ürün 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 "Üstveri 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: 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: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Onaylı ürün 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 ürün yazılımı içerir. Bellenim güncellemesi başarısız olursa, bellenimi elle düşürmenin bir yoluna sahip olmanız gerekir." #. 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 Ürün Yazılımı Yardımcı Programı" #. 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: 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 msgid "Unknown" msgstr "Bilinmiyor" #. 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 artalan süreci 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "Rapor şimdi karşıya yüklensin mi?" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Ürün 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: 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 "Dikkat:" #. 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 "Ürün yazılımını dosyadan aygıta yaz" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Ürün 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 ürün yazılımı güncellemesini doğrulamamış olabilir." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|GUID]" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM olay günlüğü yardımcı aracı" fwupd-1.7.5/po/uk.po000066400000000000000000003123061420024370600142510ustar00rootroot00000000000000# 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-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Ukrainian (http://www.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: 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: 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 "Оновлення 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: 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: 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: 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: 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: 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 "Тепер усі PCR TPM є коректними" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Усі PCR TPM є коректними" #. 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 we want to shutdown 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 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: 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 firmware file" msgstr "Зібрати файл мікропрограми" #. TRANSLATORS: command description msgid "Build firmware using a sandbox" msgstr "Зібрати мікропрограму за допомогою пісочниці" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Скасувати" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "Виберіть гілку:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Виберіть пристрій:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "Виберіть тип мікропрограми:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "Виберіть випуск:" #. TRANSLATORS: get interactive prompt msgid "Choose a 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: 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 "ІД_ПРИСТРОЮ|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: 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: 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 msgid "Devices with no available firmware updates: " msgstr "Пристрої, для яких немає оновлень мікропрограми:" #. 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: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 the user 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 "Шифрована пам'ять" #. 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "ВХІДНИЙ_ФАЙЛ ВИХІДНИЙ_ФАЙЛ [СКРИПТ] [ВИВЕДЕННЯ]" #. 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: Suffix: the fallback HSI result #. 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 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 "Основна адреса мікропрограми" #. 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 день, можливо, вони вже не є актуальними." msgstr[1] "Метадані мікропрограми не оновлювалися %u дні, можливо, вони вже не є актуальними." msgstr[2] "Метадані мікропрограми не оновлювалися %u днів, можливо, вони вже не є актуальними." msgstr[3] "Метадані мікропрограми не оновлювалися %u днів, можливо, вони вже не є актуальними." #. TRANSLATORS: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "Останнє оновлення метаданих мікропрограми було виконано %s тому. Скористайтеся --force, щоб виконати примусове оновлення." #. 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: 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 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: title for host security events msgid "Host Security Events" msgstr "Події захисту вузла" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Ід. захисту основної системи:" #. 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 "Тривалість встановлення" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "Встановити бінарну мікропрограму на пристрій" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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 "OTP FUSE 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, #. * 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Діагностика DCI Intel" #. 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] "Вади" 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 "Ліцензування" 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 "Адреса метаданих" #. 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 daemon.conf 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: we're poking around as a power user msgid "NOTE: This program may only work correctly as root" msgstr "Зауваження: програма зможе працювати належними чином лише від імені користувача root" #. 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 "Немає доступних віддалених сховищ" 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: command line option msgid "Only use IPFS when downloading files" msgstr "Використовувати IPFS лише при отриманні файлів" #. 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: '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 "Пріоритетність" 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 "ВІДДАЛ-ІД" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" 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: 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 "Адреса звіту" #. 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 from the Internet msgid "Requires internet connection" msgstr "Потребує з'єднання із інтернетом" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Перезавантажити зараз?" #. TRANSLATORS: configuration 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: this is shown in the MOTD msgid "Run `fwupdmgr sync-bkc` to complete this action." msgstr "Віддайте команду «fwupdmgr sync-bkc», щоб завершити цю дію." #. 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: 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 "Регіон BIOS SPI" #. 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 "ПІДСИСТЕМА ДРАЙВЕР [ІД_ПРИСТРОЮ|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Зберегти файл, за допомогою якого можна буде створити апаратні ідентифікатори" #. TRANSLATORS: command line option msgid "Save device state into a JSON file between executions" msgstr "Зберігати стан пристрою до файла JSON між виконаннями" #. 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: %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: this is for plugin development msgid "Show plugin verbose information" 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: 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: 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: command description msgid "Sync firmware versions to the host best known configuration" msgstr "Синхронізувати версії мікропрограм із найкращою відомою конфігурацією вузла" #. TRANSLATORS: Must be plugged in to 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 "Відновлення 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 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: 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 "PCR0 TPM відрізняється від реконструкції." #. 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: naughty vendor msgid "The vendor did not supply any release notes." 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-bkc` #, 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 "За допомогою цієї програми адміністратор може застосувати оновлення до 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 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 meatdata 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 "Розділ 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 "У режимі сумісності із застарілим 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: 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 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 "Нам відомо про помилку під час оновлення. Будь ласка, відвідайте цю адресу, щоб дізнатися більше:" #. TRANSLATORS: ask the user if we can update the 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 upload msgid "Upload report now?" msgstr "Вивантажити звіт зараз?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "Скористайтеся fwupdmgr --help, щоб дізнатися більше" #. TRANSLATORS: error message explaining command on 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: 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: 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 "[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 "[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 "[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.7.5/po/zh_CN.po000066400000000000000000002120571420024370600146350ustar00rootroot00000000000000# 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 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Chinese (China) (http://www.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 we want to shutdown 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: command description msgid "Build firmware using a sandbox" msgstr "使用沙箱生成固件" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "取消" #. TRANSLATORS: this is when a device ctrl+c's a watch 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: 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 a branch:" msgstr "选择分支:" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "选择设备:" #. TRANSLATORS: get interactive prompt msgid "Choose a firmware type:" msgstr "选择固件类型:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" msgstr "选择发行版本:" #. TRANSLATORS: get interactive prompt msgid "Choose a volume:" 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: 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 msgid "Devices with no available firmware updates: " msgstr "无固件更新可用的设备:" #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "固件已是最新的设备:" #. TRANSLATORS: Suffix: the HSI result #. TRANSLATORS: Plugin is inactive and not used 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 the user 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]" msgstr "输入文件 输出文件 [脚本] [输出]" #. 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: Suffix: the fallback HSI result #. 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 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: error message for a user who ran fwupdmgr #. refresh recently %1 is an already translated timestamp such #. as 6 hours or 15 seconds #, c-format msgid "Firmware metadata last refresh: %s ago. Use --force to refresh again." msgstr "固件元数据上次刷新:%s前。使用 --force 再次刷新。" #. 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 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 "安装时长" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "在设备上安装固件比特块" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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: Direct Connect Interface (DCI) allows #. * debugging of Intel processors using the USB3 port msgid "Intel DCI debugger" msgstr "Intel DCI 调试器" #. 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 daemon.conf 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: command line option msgid "Only use IPFS when downloading files" msgstr "下载文件时只使用 IPFS" #. 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 from the Internet msgid "Requires internet connection" msgstr "需要互联网连接" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "要现在重启设备吗?" #. TRANSLATORS: configuration 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: this is shown in the MOTD msgid "Run `fwupdmgr sync-bkc` to complete this action." msgstr "运行 `fwupdmgr sync-bkc` 以完成此操作。" #. 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 "Save device state into a JSON file between executions" msgstr "在执行之间保存设备状态到 JSON 文件" #. 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: this is for plugin development msgid "Show plugin verbose information" 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 in to 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "要上传报告吗?" #. 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: error message explaining command on how to get help msgid "Use fwupdmgr --help for help" msgstr "使用 fwupdmgr --help 获取帮助信息" #. TRANSLATORS: error message explaining command on 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: 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: 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.7.5/po/zh_TW.po000066400000000000000000000512631420024370600146670ustar00rootroot00000000000000# 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://www.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: command description msgid "Build firmware using a sandbox" msgstr "使用沙箱建置韌體" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "取消" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "已取消" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "已變更" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "校驗計算碼" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "選擇裝置:" #. TRANSLATORS: get interactive prompt msgid "Choose a release:" 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: 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: Suffix: the HSI result #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled 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 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 "閒置…" #. TRANSLATORS: command description msgid "Install a firmware blob on a device" msgstr "在裝置上安裝韌體 blob" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" 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 from the Internet 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: this is for plugin development msgid "Show plugin verbose information" 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 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 the user if we can update the 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: ask the user to upload msgid "Upload report now?" msgstr "是否立刻上傳報告?" #. 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.7.5/policy/000077500000000000000000000000001420024370600141465ustar00rootroot00000000000000fwupd-1.7.5/policy/its/000077500000000000000000000000001420024370600147455ustar00rootroot00000000000000fwupd-1.7.5/policy/its/polkit.its000066400000000000000000000004651420024370600167750ustar00rootroot00000000000000 fwupd-1.7.5/policy/its/polkit.loc000066400000000000000000000003031420024370600167420ustar00rootroot00000000000000 fwupd-1.7.5/policy/meson.build000066400000000000000000000015131420024370600163100ustar00rootroot00000000000000install_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.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.source_root(), 'policy'), po_dir: join_paths(meson.source_root(), 'po') ) endif fwupd-1.7.5/policy/org.freedesktop.fwupd-unsafe.rules000066400000000000000000000002731420024370600227300ustar00rootroot00000000000000polkit.addRule(function(action, subject) { if (action.id.startsWith("org.freedesktop.fwupd.") && subject.isInGroup("wheel")) { return polkit.Result.YES; } }); fwupd-1.7.5/policy/org.freedesktop.fwupd.policy.in000066400000000000000000000173331420024370600222300ustar00rootroot00000000000000 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 fwupd-1.7.5/policy/org.freedesktop.fwupd.rules000066400000000000000000000003741420024370600214530ustar00rootroot00000000000000polkit.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.7.5/snap/000077500000000000000000000000001420024370600136105ustar00rootroot00000000000000fwupd-1.7.5/snap/hooks/000077500000000000000000000000001420024370600147335ustar00rootroot00000000000000fwupd-1.7.5/snap/hooks/install000077500000000000000000000015721420024370600163340ustar00rootroot00000000000000#!/bin/sh -e install_if_missing() { directory=$(dirname ${2}/${1}) if [ "$2" != "/" ]; then mkdir -p $directory fi if [ -d $directory ]; then install -m 644 -C ${SNAP}/${1} ${2}/${1} fi } #install policykit rules and actions install_if_missing share/polkit-1/actions/org.freedesktop.fwupd.policy /usr install_if_missing share/polkit-1/rules.d/org.freedesktop.fwupd.rules /usr #install dbus related items install_if_missing share/dbus-1/system-services/org.freedesktop.fwupd.service /usr install_if_missing share/dbus-1/system.d/org.freedesktop.fwupd.conf /usr #activation via systemd install_if_missing etc/systemd/system/fwupd-activate.service / systemctl daemon-reload systemctl enable fwupd-activate systemctl start fwupd-activate #kernel modules install_if_missing usr/lib/modules-load.d/fwupd-msr.conf / #optional grub configuration install_if_missing etc/grub.d/35_fwupd / fwupd-1.7.5/snap/hooks/remove000077500000000000000000000004371420024370600161620ustar00rootroot00000000000000#!/bin/sh -e #activation via systemd systemctl stop fwupd-activate systemctl disable fwupd-activate rm /etc/systemd/system/fwupd-activate.service -f systemctl daemon-reload #msr module rm /usr/lib/modules-load.d/fwupd-msr.conf -f #optional grub configuration rm /etc/grub.d/35_fwupd -f fwupd-1.7.5/snap/snapcraft.yaml000066400000000000000000000177531420024370600164720ustar00rootroot00000000000000name: fwupd version-script: cat $SNAPCRAFT_STAGE/version version: 'daily' 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: classic base: core18 architectures: - amd64 apps: dfu-tool: command: dfu-tool.wrapper dbxtool: command: dbxtool.wrapper fwupdtool: command: fwupdtool.wrapper completer: share/bash-completion/completions/fwupdtool fwupd: command: fwupd.wrapper daemon: simple fwupdmgr: command: fwupdmgr.wrapper completer: share/bash-completion/completions/fwupdmgr fwupdagent: command: fwupdagent.wrapper parts: curl: source: https://curl.se/download/curl-7.73.0.tar.bz2 tpm2-tss: plugin: autotools source: https://github.com/tpm2-software/tpm2-tss/releases/download/2.3.0/tpm2-tss-2.3.0.tar.gz configflags: - --disable-doxygen-doc prime: - -include - -bin - -share/man - -lib/pkgconfig build-packages: - libssl-dev nettle: plugin: autotools source: https://ftp.gnu.org/gnu/nettle/nettle-3.5.tar.gz build-packages: - libgmp-dev prime: - -include - -bin - -share/man - -lib/pkgconfig gnutls: plugin: autotools source: https://www.gnupg.org/ftp/gcrypt/gnutls/v3.6/gnutls-3.6.12.tar.xz build-packages: - libtasn1-6-dev - libunistring-dev - libidn2-dev - libunbound-dev - libp11-kit-dev prime: - -include - -bin - -share/man - -lib/pkgconfig after: [nettle] meson: plugin: python source: https://github.com/mesonbuild/meson/releases/download/0.60.2/meson-0.60.2.tar.gz build-packages: - ninja-build - python3-distutils-extra prime: - -bin - -etc - -lib - -share - -usr # this is for the library only, we don't care about the daemon "in-snap" modemmanager: plugin: autotools source: https://github.com/freedesktop/ModemManager.git source-tag: 1.14.0 # build without these; system daemon needs them build-packages: - xsltproc - autoconf-archive configflags: - --without-mbim - --without-qmi prime: - -include - -etc - -sbin - -bin - -share - -lib/*.*a - -lib/pkgconfig - -lib/ModemManager - -lib/systemd - -lib/udev - -lib/girepository-1.0 libmbim: plugin: autotools source: https://github.com/freedesktop/libmbim.git source-tag: 1.24.0 after: [modemmanager] # build without these; system daemon needs them configflags: - --without-udev prime: - -include - -etc - -sbin - -bin - -share - -lib/*.*a - -lib/pkgconfig - -lib/ModemManager - -lib/systemd - -lib/udev - -lib/girepository-1.0 json-glib: plugin: meson source: https://gitlab.gnome.org/GNOME/json-glib.git source-tag: 1.6.6 after: [meson] meson-parameters: [--prefix=/, -Dgtk_doc=disabled, -Dintrospection=disabled, -Dman=false, -Dtests=false] prime: - -include - -bin - -lib/*/pkgconfig - -share libqmi: plugin: autotools source: https://github.com/freedesktop/libqmi.git source-tag: 1.26.0 after: [modemmanager, libmbim] # build without these; system daemon needs them configflags: - --without-udev prime: - -include - -etc - -sbin - -bin - -share - -lib/*.*a - -lib/pkgconfig - -lib/ModemManager - -lib/systemd - -lib/udev - -lib/girepository-1.0 #fetch the latest version of the signed bootloader #this might not match our fwupdx64.efi, but it's better than nothing fwup-efi-signed: build-packages: - python3-apt plugin: make source: contrib/snap/fwup-efi-signed #needed for UEFI plugin to build UX labels build-introspection: plugin: nil stage-packages: - python3-gi - python3-gi-cairo prime: - -etc - -usr - -lib - -var fwupd: plugin: meson meson-parameters: [--prefix=/, -Dtests=false, -Dbuild=all, -Ddocs=none, -Dintrospection=false, -Dman=false, -Dplugin_modem_manager=true, -Dplugin_powerd=false, -Dudevdir=$SNAPCRAFT_STAGE/lib/udev, "-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: | snapcraftctl build echo $(git describe HEAD --always) > $SNAPCRAFT_STAGE/version build-packages: - bash-completion - gcab - gnu-efi - libarchive-dev - libcairo-dev - libefiboot-dev - libefivar-dev - libftdi1-dev - libgudev-1.0-dev - libgcab-dev - libglib2.0-dev - libgpgme11-dev - liblzma-dev - libpango1.0-dev - libpci-dev - libpolkit-gobject-1-dev - libprotobuf-c-dev - libsmbios-dev - libsqlite3-dev - libsystemd-dev - locales - pkg-config - protobuf-c-compiler - systemd - uuid-dev stage-packages: - libgcab-1.0-0 - libarchive13 - libassuan0 - liblcms2-2 - liblzma5 - libefivar1 - libefiboot1 - libusb-1.0-0 - libgudev-1.0-0 - libgpgme11 - libpolkit-gobject-1-0 - libsmbios-c2 - glib-networking - libglib2.0-bin prime: # we explicitly don't want /usr/bin/gpgconf # this will cause gpgme to error finding it # but that also avoids trying to use non-existent # /usr/bin/gpg2 - -usr/bin - -usr/sbin - -usr/share/man - -usr/share/GConf - -etc/X11 - -etc/ldap - -etc/logcheck - -usr/lib/dconf - -usr/lib/gcc - -usr/lib/glib-networking - -usr/lib/gnupg2 - -usr/lib/sasl2 - -usr/lib/systemd - -usr/lib/*/audit - -usr/share/glib-2.0/schemas - -usr/share/X11 - -include - -lib/udev - -lib/*/pkgconfig - -usr/share/lintian - -usr/share/pkgconfig - -usr/share/installed-tests - -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/*/pkgconfig # we don't want system gnutls leaking in - -usr/lib/*/libgnutls* after: [meson, build-introspection, modemmanager, libmbim, libqmi, tpm2-tss, gnutls, curl, json-glib] fix-bash-completion: plugin: make source: contrib/snap/fix-bash-completion after: [fwupd] activate-shutdown: plugin: make source: contrib/snap/activate-shutdown after: [fwupd] 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 after: [fwupd] fwupd-wrappers: plugin: dump source: contrib/snap stage: - dfu-tool.wrapper - dbxtool.wrapper - fwupd-command - fwupdtool.wrapper - fwupd.wrapper - fwupdmgr.wrapper - fwupdagent.wrapper fwupd-1.7.5/src/000077500000000000000000000000001420024370600134365ustar00rootroot00000000000000fwupd-1.7.5/src/README.md000066400000000000000000000113631420024370600147210ustar00rootroot00000000000000# Quirk use Quirks are defined by creating an INI style file in the compiled in quirk location (typically `/usr/share/fwupd/quirks.d`). The quirk is declared by creating a group based upon the `DeviceInstanceId` or `GUID` and then mapping out values to keys. ## All plugins All fwupd devices support the following quirks: ### Plugin Sets the plugin to use for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the plugin name, e.g. `csr` * Minimum fwupd version: **1.1.0** ### Flags Assigns optional quirks to use for a 8bitdo device * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the quirk, e.g. `is-bootloader` * Supported values: * `none`: no device quirks * `is-bootloader`: device is in bootloader mode * Minimum fwupd version: **1.0.3** ### Summary Sets a summary for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * the device summary, e.g. `An open source display colorimeter` * Minimum fwupd version: **1.0.2** ### Icon Adds an icon name for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the device icon name, e.g. `media-removable` * Minimum fwupd version: **1.0.2** ### Name Sets a name for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the device name, e.g. `ColorHug` * Minimum fwupd version: **1.0.2** ### Guid Adds an extra GUID for a specific hardware device. If the value provided is not already a suitable GUID, it will be converted to one. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696` * Minimum fwupd version: **1.0.3** ### CounterpartGuid Adds an counterpart GUID for a specific hardware device. If the value provided is not already a suitable GUID, it will be converted to one. 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. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696` * Minimum fwupd version: **1.1.2** ### ParentGuid Adds an extra GUID to mark as the parent device. If the value provided is not already a suitable GUID, it will be converted to one. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696` * Minimum fwupd version: **1.1.2** ### Children Adds one or more virtual devices to a physical device. To set the object type of the child device use a pipe before the object type, for instance: `FuRts54xxDeviceUSB\VID_0763&PID_2806&I2C_01` If the type of device is not specified the parent device type is used. If the values provided are not already suitable GUIDs, they will be converted. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: The virtual device, delimited by a comma * Minimum fwupd version: **1.1.2** ### Vendor Sets a vendor name for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the vendor, e.g. `Hughski Limited` * Minimum fwupd version: **1.0.3** ### VendorId Sets a vendor ID for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: the vendor, e.g. `USB:0x123A` * Minimum fwupd version: **1.1.2** ### Version Sets a version for a specific hardware device. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: Version number, e.g. `1.2` * Minimum fwupd version: **1.0.3** ### FirmwareSizeMin Sets the minimum allowed firmware size. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: A number in bytes, e.g. `512` * Minimum fwupd version: **1.1.2** ### FirmwareSizeMax Sets the maximum allowed firmware size. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: A number in bytes, e.g. `1024` * Minimum fwupd version: **1.1.2** ### InstallDuration Sets the estimated time to flash the device * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: A number in seconds, e.g. `60` * Minimum fwupd version: **1.1.3** ### VersionFormat Sets the version format the device should use for conversion. * Key: the device ID, e.g. `USB\VID_0763&PID_2806` * Value: The quirk format, e.g. `quad` * Minimum fwupd version: **1.2.0** ## Plugin specific Plugins may add support for additional quirks that are relevant only for those plugins. View them by looking at the `README.md` in plugin directories. ## Example Here is an example as seen in the CSR plugin. ```ini [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 ``` Additional samples can be found in other plugins. fwupd-1.7.5/src/fu-bluez-backend.c000066400000000000000000000151711420024370600167250ustar00rootroot00000000000000/* * 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_debug("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; g_auto(GStrv) names = 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_debug("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, 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, 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(void) { return FU_BACKEND(g_object_new(FU_TYPE_BLUEZ_BACKEND, "name", "bluez", NULL)); } fwupd-1.7.5/src/fu-bluez-backend.h000066400000000000000000000004501420024370600167240ustar00rootroot00000000000000/* * 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(void); fwupd-1.7.5/src/fu-config.c000066400000000000000000000326541420024370600154710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuConfig" #include "config.h" #include #include #include "fu-common.h" #include "fu-config.h" enum { SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; static void fu_config_finalize(GObject *obj); struct _FuConfig { GObject parent_instance; GPtrArray *monitors; /* (element-type GFileMonitor) */ 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 *filenames; /* (element-type utf-8) */ guint64 archive_size_max; guint idle_timeout; gchar *host_bkc; gboolean update_motd; gboolean enumerate_all_devices; gboolean ignore_power; gboolean only_trusted; }; G_DEFINE_TYPE(FuConfig, fu_config, G_TYPE_OBJECT) static void fu_config_emit_changed(FuConfig *self) { g_debug("::configuration changed"); g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } static gboolean fu_config_reload(FuConfig *self, GError **error) { guint64 archive_size_max; guint idle_timeout; g_auto(GStrv) approved_firmware = NULL; g_auto(GStrv) blocked_firmware = NULL; g_auto(GStrv) uri_schemes = NULL; g_auto(GStrv) devices = NULL; g_auto(GStrv) plugins = NULL; g_autofree gchar *domains = NULL; g_autofree gchar *host_bkc = NULL; g_autoptr(GKeyFile) keyfile = g_key_file_new(); g_autoptr(GError) error_update_motd = NULL; g_autoptr(GError) error_ignore_power = NULL; g_autoptr(GError) error_only_trusted = NULL; g_autoptr(GError) error_enumerate_all = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); /* we have to load each file into a buffer as g_key_file_load_from_file() clears the * GKeyFile state before loading each file, and we want to allow the mutable version to be * incomplete and just *override* a specific option */ for (guint i = 0; i < self->filenames->len; i++) { const gchar *fn = g_ptr_array_index(self->filenames, i); g_debug("trying to load config values from %s", fn); if (g_file_test(fn, G_FILE_TEST_EXISTS)) { g_autoptr(GBytes) blob = fu_common_get_contents_bytes(fn, error); if (blob == NULL) return FALSE; fu_byte_array_append_bytes(buf, blob); } } /* load if either file found */ if (buf->len > 0) { if (!g_key_file_load_from_data(keyfile, (const gchar *)buf->data, buf->len, G_KEY_FILE_NONE, error)) return FALSE; } /* get disabled devices */ g_ptr_array_set_size(self->disabled_devices, 0); devices = g_key_file_get_string_list(keyfile, "fwupd", "DisabledDevices", NULL, /* length */ NULL); 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 = g_key_file_get_string_list(keyfile, "fwupd", "DisabledPlugins", NULL, /* length */ NULL); if (plugins != NULL) { for (guint i = 0; plugins[i] != NULL; i++) { g_ptr_array_add(self->disabled_plugins, g_strdup(plugins[i])); } } /* get approved firmware */ g_ptr_array_set_size(self->approved_firmware, 0); approved_firmware = g_key_file_get_string_list(keyfile, "fwupd", "ApprovedFirmware", NULL, /* length */ NULL); 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 = g_key_file_get_string_list(keyfile, "fwupd", "BlockedFirmware", NULL, /* length */ NULL); 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 = g_key_file_get_string_list(keyfile, "fwupd", "UriSchemes", NULL, /* length */ NULL); 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])); } } if (self->uri_schemes->len == 0) { g_ptr_array_add(self->uri_schemes, g_strdup("file")); g_ptr_array_add(self->uri_schemes, g_strdup("https")); g_ptr_array_add(self->uri_schemes, g_strdup("http")); g_ptr_array_add(self->uri_schemes, g_strdup("ipfs")); } /* get maximum archive size, defaulting to something sane */ archive_size_max = g_key_file_get_uint64(keyfile, "fwupd", "ArchiveSizeMax", NULL); if (archive_size_max > 0) { self->archive_size_max = archive_size_max * 0x100000; } else { guint64 memory_size = fu_common_get_memory_size(); g_autofree gchar *str = NULL; if (memory_size > 0) { self->archive_size_max = MAX(memory_size / 4, G_MAXSIZE); str = g_format_size(self->archive_size_max); g_debug("using autodetected max archive size %s", str); } else { self->archive_size_max = 512 * 0x100000; str = g_format_size(self->archive_size_max); g_debug("using fallback max archive size %s", str); } } /* get idle timeout */ idle_timeout = g_key_file_get_uint64(keyfile, "fwupd", "IdleTimeout", NULL); if (idle_timeout > 0) self->idle_timeout = idle_timeout; /* get the domains to run in verbose */ domains = g_key_file_get_string(keyfile, "fwupd", "VerboseDomains", NULL); if (domains != NULL && domains[0] != '\0') g_setenv("FWUPD_VERBOSE", domains, TRUE); /* whether to update the motd on changes */ self->update_motd = g_key_file_get_boolean(keyfile, "fwupd", "UpdateMotd", &error_update_motd); if (!self->update_motd && error_update_motd != NULL) { g_debug("failed to read UpdateMotd key: %s", error_update_motd->message); self->update_motd = TRUE; } /* whether to only show supported devices for some plugins */ self->enumerate_all_devices = g_key_file_get_boolean(keyfile, "fwupd", "EnumerateAllDevices", &error_enumerate_all); /* if error parsing or missing, we want to default to true */ if (!self->enumerate_all_devices && error_enumerate_all != NULL) { g_debug("failed to read EnumerateAllDevices key: %s", error_enumerate_all->message); self->enumerate_all_devices = TRUE; } /* whether to ignore power levels for updates */ self->ignore_power = g_key_file_get_boolean(keyfile, "fwupd", "IgnorePower", &error_ignore_power); if (!self->ignore_power && error_ignore_power != NULL) { g_debug("failed to read IgnorePower key: %s", error_ignore_power->message); self->ignore_power = FALSE; } /* whether to allow untrusted firmware *at all* even with PolicyKit auth */ self->only_trusted = g_key_file_get_boolean(keyfile, "fwupd", "OnlyTrusted", &error_only_trusted); if (!self->only_trusted && error_only_trusted != NULL) { g_debug("failed to read OnlyTrusted key: %s", error_only_trusted->message); self->only_trusted = TRUE; } /* fetch host best known configuration */ host_bkc = g_key_file_get_string(keyfile, "fwupd", "HostBkc", NULL); if (host_bkc != NULL && host_bkc[0] != '\0') self->host_bkc = g_steal_pointer(&host_bkc); 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); g_debug("%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); } gboolean fu_config_set_key_value(FuConfig *self, const gchar *key, const gchar *value, GError **error) { g_autoptr(GKeyFile) keyfile = g_key_file_new(); const gchar *fn; /* sanity check */ if (self->filenames->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no config to load"); return FALSE; } /* only write the file in /etc */ fn = g_ptr_array_index(self->filenames, 0); if (!g_key_file_load_from_file(keyfile, fn, G_KEY_FILE_KEEP_COMMENTS, error)) return FALSE; g_key_file_set_string(keyfile, "fwupd", key, value); if (!g_key_file_save_to_file(keyfile, fn, error)) return FALSE; return fu_config_reload(self, error); } gboolean fu_config_load(FuConfig *self, GError **error) { g_autofree gchar *configdir_mut = fu_common_get_path(FU_PATH_KIND_LOCALCONFDIR_PKG); g_autofree gchar *configdir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); g_return_val_if_fail(self->filenames->len == 0, FALSE); /* load the main daemon config file */ g_ptr_array_add(self->filenames, g_build_filename(configdir, "daemon.conf", NULL)); g_ptr_array_add(self->filenames, g_build_filename(configdir_mut, "daemon.conf", NULL)); if (!fu_config_reload(self, error)) return FALSE; /* set up a notify watches */ for (guint i = 0; i < self->filenames->len; i++) { const gchar *fn = g_ptr_array_index(self->filenames, i); g_autoptr(GFile) file = g_file_new_for_path(fn); g_autoptr(GFileMonitor) monitor = NULL; monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); if (monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(monitor), "changed", G_CALLBACK(fu_config_monitor_changed_cb), self); g_ptr_array_add(self->monitors, g_steal_pointer(&monitor)); } /* success */ return TRUE; } guint fu_config_get_idle_timeout(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), 0); return self->idle_timeout; } GPtrArray * fu_config_get_disabled_devices(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->disabled_devices; } GPtrArray * fu_config_get_blocked_firmware(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->blocked_firmware; } guint fu_config_get_uri_scheme_prio(FuConfig *self, const gchar *scheme) { #if GLIB_CHECK_VERSION(2, 54, 0) guint idx = G_MAXUINT; g_ptr_array_find_with_equal_func(self->uri_schemes, scheme, g_str_equal, &idx); return idx; #else for (guint i = 0; i < self->uri_schemes->len; i++) const gchar *scheme_tmp = g_ptr_array_index(self->uri_schemes, i); if (g_str_equal(scheme_tmp, scheme)) return i; } return G_MAXUINT; #endif } guint64 fu_config_get_archive_size_max(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), 0); return self->archive_size_max; } GPtrArray * fu_config_get_disabled_plugins(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->disabled_plugins; } GPtrArray * fu_config_get_approved_firmware(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->approved_firmware; } gboolean fu_config_get_update_motd(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); return self->update_motd; } gboolean fu_config_get_ignore_power(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); return self->ignore_power; } gboolean fu_config_get_only_trusted(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); return self->only_trusted; } gboolean fu_config_get_enumerate_all_devices(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); return self->enumerate_all_devices; } const gchar * fu_config_get_host_bkc(FuConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->host_bkc; } 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); } static void fu_config_init(FuConfig *self) { self->filenames = g_ptr_array_new_with_free_func(g_free); 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->uri_schemes = g_ptr_array_new_with_free_func(g_free); self->monitors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } static void fu_config_finalize(GObject *obj) { FuConfig *self = FU_CONFIG(obj); for (guint i = 0; i < self->monitors->len; i++) { GFileMonitor *monitor = g_ptr_array_index(self->monitors, i); g_file_monitor_cancel(monitor); } g_ptr_array_unref(self->filenames); g_ptr_array_unref(self->monitors); 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_free(self->host_bkc); G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj); } FuConfig * fu_config_new(void) { FuConfig *self; self = g_object_new(FU_TYPE_CONFIG, NULL); return FU_CONFIG(self); } fwupd-1.7.5/src/fu-config.h000066400000000000000000000022301420024370600154610ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-remote.h" #define FU_TYPE_CONFIG (fu_config_get_type()) G_DECLARE_FINAL_TYPE(FuConfig, fu_config, FU, CONFIG, GObject) FuConfig * fu_config_new(void); gboolean fu_config_load(FuConfig *self, GError **error); gboolean fu_config_set_key_value(FuConfig *self, const gchar *key, const gchar *value, GError **error); guint64 fu_config_get_archive_size_max(FuConfig *self); guint fu_config_get_idle_timeout(FuConfig *self); GPtrArray * fu_config_get_disabled_devices(FuConfig *self); GPtrArray * fu_config_get_disabled_plugins(FuConfig *self); GPtrArray * fu_config_get_approved_firmware(FuConfig *self); GPtrArray * fu_config_get_blocked_firmware(FuConfig *self); guint fu_config_get_uri_scheme_prio(FuConfig *self, const gchar *protocol); gboolean fu_config_get_update_motd(FuConfig *self); gboolean fu_config_get_enumerate_all_devices(FuConfig *self); gboolean fu_config_get_ignore_power(FuConfig *self); gboolean fu_config_get_only_trusted(FuConfig *self); const gchar * fu_config_get_host_bkc(FuConfig *self); fwupd-1.7.5/src/fu-debug.c000066400000000000000000000140071420024370600153020ustar00rootroot00000000000000/* * Copyright (C) 2010 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDebug" #include #include #include #include #include typedef struct { GOptionGroup *group; gboolean verbose; gboolean console; gboolean no_timestamp; gboolean no_domain; gchar **plugin_verbose; gchar **daemon_verbose; } FuDebug; 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->plugin_verbose); g_strfreev(self->daemon_verbose); g_free(self); } static gboolean fu_debug_filter_cb(FuDebug *self, const gchar *log_domain, GLogLevelFlags log_level) { const gchar *domains = g_getenv("FWUPD_VERBOSE"); g_auto(GStrv) domains_str = NULL; /* include important things by default only */ if (domains == NULL) { if (log_level == G_LOG_LEVEL_INFO || log_level == G_LOG_LEVEL_CRITICAL || log_level == G_LOG_LEVEL_WARNING || log_level == G_LOG_LEVEL_ERROR) { return TRUE; } return FALSE; } /* everything */ if (g_strcmp0(domains, "*") == 0) return TRUE; /* filter on domain */ domains_str = g_strsplit(domains, ",", -1); return g_strv_contains((const gchar *const *)domains_str, log_domain); } 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_autoptr(GString) domain = NULL; /* should ignore */ if (!fu_debug_filter_cb(self, log_domain, log_level)) return; /* time header */ if (!self->no_timestamp) { g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); timestamp = g_strdup_printf("%02i:%02i:%02i:%04i", 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, NULL); if (timestamp != NULL) g_printerr("%s ", timestamp); if (domain != NULL) g_printerr("%s ", domain->str); g_printerr("%s\n", ascii_message); 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, 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, 0x1B, 0); break; } } static gboolean fu_debug_pre_parse_hook(GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *)data; const GOptionEntry main_entries[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &self->verbose, /* 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}, {"plugin-verbose", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &self->plugin_verbose, /* TRANSLATORS: this is for plugin development */ N_("Show plugin verbose information"), "PLUGIN-NAME"}, {"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}}; /* add main entry */ g_option_context_add_main_entries(context, main_entries, NULL); return TRUE; } static gboolean fu_debug_post_parse_hook(GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *)data; /* verbose? */ if (self->verbose) { g_setenv("FWUPD_VERBOSE", "*", TRUE); } else if (self->daemon_verbose != NULL) { g_autofree gchar *str = g_strjoinv(",", self->daemon_verbose); g_setenv("FWUPD_VERBOSE", str, TRUE); } /* redirect all domains to be able to change FWUPD_VERBOSE at runtime */ g_log_set_default_handler(fu_debug_handler_cb, self); /* are we on an actual TTY? */ self->console = (isatty(fileno(stderr)) == 1); g_debug("Verbose debugging %s (on console %i)", self->verbose ? "enabled" : "disabled", self->console); /* allow each plugin to be extra verbose */ if (self->plugin_verbose != NULL) { for (guint i = 0; self->plugin_verbose[i] != NULL; i++) { g_autofree gchar *name_caps = NULL; g_autofree gchar *varname = NULL; name_caps = g_ascii_strup(self->plugin_verbose[i], -1); varname = g_strdup_printf("FWUPD_%s_VERBOSE", name_caps); g_debug("setting %s=1", varname); g_setenv(varname, "1", TRUE); } } return TRUE; } /*(transfer): full */ GOptionGroup * fu_debug_get_option_group(void) { FuDebug *self = g_new0(FuDebug, 1); 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); return g_option_group_ref(self->group); } fwupd-1.7.5/src/fu-debug.h000066400000000000000000000002751420024370600153110ustar00rootroot00000000000000/* * Copyright (C) 2010 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include GOptionGroup * fu_debug_get_option_group(void); fwupd-1.7.5/src/fu-device-list.c000066400000000000000000001022141420024370600164220ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceList" #include "config.h" #include #include #include "fwupd-error.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-mutex.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_debug("::added %s", fu_device_get_id(device)); g_signal_emit(self, signals[SIGNAL_ADDED], 0, device); } static void fu_device_list_emit_device_removed(FuDeviceList *self, FuDevice *device) { g_debug("::removed %s", fu_device_get_id(device)); g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device); } static void fu_device_list_emit_device_changed(FuDeviceList *self, FuDevice *device) { g_debug("::changed %s", fu_device_get_id(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); 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); 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_debug("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_debug("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_debug("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); } /** * 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_debug("device %s not found", fu_device_get_id(device)); return; } /* we can't do anything with an unconnected device */ fu_device_inhibit(item->device, "unconnected", "Device has been removed"); /* 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_get_remove_delay(item->device) > 0) { 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_debug("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_debug("adding GUID %s to device", guid_tmp); fu_device_add_counterpart_guid(device_new, guid_tmp); } else { g_debug("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) { /* 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_debug("%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_debug("%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_uninhibit(item->device, "unconnected"); /* optional debug */ if (g_getenv("FWUPD_DEVICE_LIST_VERBOSE") != NULL) { g_autofree gchar *str = fu_device_list_to_string(self); g_debug("\n%s", str); } } 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_debug("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_debug("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) { guint64 private_flags; GPtrArray *vendor_ids; /* 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_debug("copying old vendor ID %s to new device", vendor_id); fu_device_add_vendor_id(device, vendor_id); } /* copy over custom flags */ private_flags = fu_device_get_private_flags(item->device); if (private_flags != 0) { g_debug("copying old custom flags 0x%x to new device", (guint)private_flags); fu_device_set_private_flags(device, private_flags); } /* 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_debug("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_debug("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); } /* device won't come back in right mode */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_debug("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_debug("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); if (g_getenv("FWUPD_DEVICE_LIST_VERBOSE") != NULL) { g_autofree gchar *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 (g_strcmp0(fu_device_get_id(device), fu_device_get_id(item->device)) == 0) { g_debug("found existing device %s", fu_device_get_id(device)); if (device != item->device) { fu_device_incorporate_update_state(device, 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; } /* the old device again */ if (item->device_old != NULL && g_strcmp0(fu_device_get_id(device), fu_device_get_id(item->device_old)) == 0) { g_debug("found old device %s, swapping", fu_device_get_id(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_debug("found existing device %s, reusing item", fu_device_get_id(item->device)); fu_device_list_replace(self, item, device); fu_device_uninhibit(device, "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_debug("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_uninhibit(device, "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_debug("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_uninhibit(device, "unconnected"); return; } else { g_debug("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)) 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_debug("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_debug("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; /* dump to console */ if (g_getenv("FWUPD_DEVICE_LIST_VERBOSE") != NULL) { g_autofree gchar *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_common_strjoin_array(",", 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_debug("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.7.5/src/fu-device-list.h000066400000000000000000000017561420024370600164400ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-device.h" #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.7.5/src/fu-engine-helper.c000066400000000000000000000101411420024370600167310ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include #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 *tag, GError **error) { g_autoptr(GPtrArray) rels = NULL; /* 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); if (fwupd_release_has_tag(rel, tag)) 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_KIND_ACTIVE); 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 */ 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; 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_common_get_path(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_common_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-bkc` 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); } fwupd-1.7.5/src/fu-engine-helper.h000066400000000000000000000003331420024370600167400ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-engine.h" gboolean fu_engine_update_motd(FuEngine *self, GError **error); fwupd-1.7.5/src/fu-engine-request.c000066400000000000000000000050521420024370600171470ustar00rootroot00000000000000/* * 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; FuEngineRequestKind kind; FwupdFeatureFlags feature_flags; FwupdDeviceFlags device_flags; gchar *locale; }; G_DEFINE_TYPE(FuEngineRequest, fu_engine_request, G_TYPE_OBJECT) 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; } FuEngineRequestKind fu_engine_request_get_kind(FuEngineRequest *self) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FU_ENGINE_REQUEST_KIND_UNKNOWN); return self->kind; } 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)); 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->device_flags = FWUPD_DEVICE_FLAG_NONE; self->feature_flags = FWUPD_FEATURE_FLAG_NONE; } static void fu_engine_request_class_init(FuEngineRequestClass *klass) { } FuEngineRequest * fu_engine_request_new(FuEngineRequestKind kind) { FuEngineRequest *self; self = g_object_new(FU_TYPE_ENGINE_REQUEST, NULL); self->kind = kind; return FU_ENGINE_REQUEST(self); } fwupd-1.7.5/src/fu-engine-request.h000066400000000000000000000023571420024370600171610ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ENGINE_REQUEST (fu_engine_request_get_type()) G_DECLARE_FINAL_TYPE(FuEngineRequest, fu_engine_request, FU, ENGINE_REQUEST, GObject) typedef enum { FU_ENGINE_REQUEST_KIND_UNKNOWN, FU_ENGINE_REQUEST_KIND_ACTIVE, FU_ENGINE_REQUEST_KIND_ONLY_SUPPORTED, } FuEngineRequestKind; FuEngineRequest * fu_engine_request_new(FuEngineRequestKind kind); FuEngineRequestKind fu_engine_request_get_kind(FuEngineRequest *self); 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.7.5/src/fu-engine.c000066400000000000000000007065511420024370600154750ustar00rootroot00000000000000/* * 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 #include #include #ifdef HAVE_UTSNAME_H #include #endif #include #ifdef _WIN32 #include #include #include #endif #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-resources.h" #include "fwupd-security-attr-private.h" #include "fu-backend.h" #include "fu-cabinet.h" #include "fu-cfu-offer.h" #include "fu-cfu-payload.h" #include "fu-common-cab.h" #include "fu-common.h" #include "fu-config.h" #include "fu-context-private.h" #include "fu-debug.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-efi-firmware-file.h" #include "fu-efi-firmware-filesystem.h" #include "fu-efi-firmware-section.h" #include "fu-efi-firmware-volume.h" #include "fu-engine-helper.h" #include "fu-engine-request.h" #include "fu-engine.h" #include "fu-hash.h" #include "fu-history.h" #include "fu-idle.h" #include "fu-ifd-bios.h" #include "fu-ifd-firmware.h" #include "fu-kenv.h" #include "fu-keyring-utils.h" #include "fu-mutex.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fu-plugin.h" #include "fu-progress.h" #include "fu-quirks.h" #include "fu-remote-list.h" #include "fu-security-attr.h" #include "fu-security-attrs-private.h" #include "fu-udev-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 #include "fu-archive-firmware.h" #include "fu-dfu-firmware.h" #include "fu-dfuse-firmware.h" #include "fu-fmap-firmware.h" #include "fu-ihex-firmware.h" #include "fu-srec-firmware.h" /* 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 static void fu_engine_finalize(GObject *obj); static void fu_engine_ensure_security_attrs(FuEngine *self); struct _FuEngine { GObject parent_instance; FuAppFlags app_flags; GPtrArray *backends; FuConfig *config; FuRemoteList *remote_list; FuDeviceList *device_list; FwupdStatus status; gboolean tainted; gboolean write_history; guint percentage; FuHistory *history; FuIdle *idle; XbSilo *silo; XbQuery *query_component_by_guid; guint coldplug_id; FuPluginList *plugin_list; GPtrArray *plugin_filter; FuContext *ctx; GHashTable *runtime_versions; GHashTable *compile_versions; GHashTable *approved_firmware; /* (nullable) */ GHashTable *blocked_firmware; /* (nullable) */ gchar *host_machine_id; JcatContext *jcat_context; gboolean loaded; gchar *host_security_id; FuSecurityAttrs *host_security_attrs; GPtrArray *local_monitors; /* (element-type GFileMonitor) */ }; 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 void fu_engine_emit_changed(FuEngine *self) { g_signal_emit(self, signals[SIGNAL_CHANGED], 0); fu_engine_idle_reset(self); /* update the motd */ if (self->loaded && fu_config_get_update_motd(self->config)) { g_autoptr(GError) error_local = NULL; if (!fu_engine_update_motd(self, &error_local)) g_debug("failed to update MOTD: %s", error_local->message); } } static void fu_engine_emit_device_changed(FuEngine *self, FuDevice *device) { /* invalidate host security attributes */ g_clear_pointer(&self->host_security_id, g_free); g_signal_emit(self, signals[SIGNAL_DEVICE_CHANGED], 0, device); } FuContext * fu_engine_get_context(FuEngine *self) { return self->ctx; } /** * fu_engine_get_status: * @self: a #FuEngine * * Gets the current engine status. * * Returns: a #FwupdStatus, e.g. %FWUPD_STATUS_DECOMPRESSING **/ FwupdStatus fu_engine_get_status(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), 0); return self->status; } static void fu_engine_set_status(FuEngine *self, FwupdStatus status) { if (self->status == status) return; self->status = status; /* emit changed */ g_debug("Emitting PropertyChanged('Status'='%s')", fwupd_status_to_string(status)); g_signal_emit(self, signals[SIGNAL_STATUS_CHANGED], 0, status); } static void fu_engine_generic_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) { fu_engine_emit_device_changed(self, 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)) { g_warning("failed to record history for %s: %s", fu_device_get_id(device), error_local->message); } } fu_engine_emit_device_changed(self, device); } static void fu_engine_device_request_cb(FuDevice *device, FwupdRequest *request, FuEngine *self) { g_debug("Emitting DeviceRequest('Message'='%s')", fwupd_request_get_message(request)); g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); } 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::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_battery_inhibit(FuEngine *self, FuDevice *device) { if (fu_config_get_ignore_power(self->config)) return; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && (fu_context_get_battery_state(self->ctx) == FU_BATTERY_STATE_DISCHARGING || fu_context_get_battery_state(self->ctx) == FU_BATTERY_STATE_EMPTY)) { fu_device_inhibit(device, "battery-system", "Cannot install update when not on AC power"); return; } if (fu_context_get_battery_level(self->ctx) != FU_BATTERY_VALUE_INVALID && fu_context_get_battery_threshold(self->ctx) != FU_BATTERY_VALUE_INVALID && fu_context_get_battery_level(self->ctx) < fu_context_get_battery_threshold(self->ctx)) { g_autofree gchar *reason = NULL; reason = g_strdup_printf( "Cannot install update when system battery is not at least %u%%", fu_context_get_battery_threshold(self->ctx)); fu_device_inhibit(device, "battery-system", reason); return; } fu_device_uninhibit(device, "battery-system"); } 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_inhibit(device, "lid-closed-system", "Cannot install update when the lid is closed"); return; } fu_device_uninhibit(device, "lid-closed-system"); } static void fu_engine_device_added_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_watch_device(self, device); fu_engine_ensure_device_battery_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); 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); 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, device); } /* convert hex and decimal versions to dotted style */ static gchar * fu_engine_get_release_version(FuEngine *self, FuDevice *dev, XbNode *rel, GError **error) { FwupdVersionFormat fmt = fu_device_get_version_format(dev); const gchar *version; guint64 ver_uint32; /* get version */ version = xb_node_get_attr(rel, "version"); if (version == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version unset"); return 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) return g_strdup(version); /* parse as integer */ ver_uint32 = fu_common_strtoull(version); if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN || ver_uint32 == 0 || ver_uint32 > G_MAXUINT32) return g_strdup(version); /* convert to dotted decimal */ return fu_common_version_from_uint32((guint32)ver_uint32, fmt); } static gint fu_engine_scheme_compare_cb(gconstpointer a, gconstpointer b, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); const gchar *location1 = *((const gchar **)a); const gchar *location2 = *((const gchar **)b); g_autofree gchar *scheme1 = fu_common_uri_get_scheme(location1); g_autofree gchar *scheme2 = fu_common_uri_get_scheme(location2); guint prio1 = fu_config_get_uri_scheme_prio(self->config, scheme1); guint prio2 = fu_config_get_uri_scheme_prio(self->config, scheme2); if (prio1 < prio2) return -1; if (prio1 > prio2) return 1; return 0; } static gboolean fu_engine_set_release_from_artifact(FuEngine *self, FwupdRelease *rel, FwupdRemote *remote, XbNode *artifact, GError **error) { const gchar *filename; guint64 size; g_autoptr(GPtrArray) locations = NULL; g_autoptr(GPtrArray) checksums = NULL; /* filename */ filename = xb_node_query_text(artifact, "filename", NULL); if (filename != NULL) fwupd_release_set_filename(rel, 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_common_uri_get_scheme(xb_node_get_text(n)); if (scheme != NULL) { guint prio = fu_config_get_uri_scheme_prio(self->config, scheme); if (prio == G_MAXUINT) continue; } /* build the complete URI */ if (remote != NULL) { g_autofree gchar *uri = NULL; uri = fwupd_remote_build_firmware_uri(remote, xb_node_get_text(n), NULL); if (uri != NULL) { fwupd_release_add_location(rel, uri); continue; } } fwupd_release_add_location(rel, 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(rel, xb_node_get_text(n)); } } /* size */ size = xb_node_query_text_as_uint(artifact, "size[@type='installed']", NULL); if (size != G_MAXUINT64) fwupd_release_set_size(rel, size); /* success */ return TRUE; } 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, FuDevice *dev, FwupdRelease *rel, GError **error) { GPtrArray *guids = fu_device_get_guids(dev); g_autoptr(XbQuery) query = NULL; g_autoptr(GError) error_query = NULL; /* prepare query with bound GUID parameter */ query = xb_query_new_full(self->silo, "local/components/component[@merge='append']/provides/" "firmware[text()=?]/../../releases/release[@version=?]/../../" "tags/tag", XB_QUERY_FLAG_OPTIMIZE | XB_QUERY_FLAG_USE_INDEXES, &error_query); if (query == NULL) { if (g_error_matches(error_query, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_query)); return FALSE; } /* 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) tags = NULL; #if LIBXMLB_CHECK_VERSION(0, 3, 0) g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); #endif /* bind GUID and then query */ #if LIBXMLB_CHECK_VERSION(0, 3, 0) 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, fwupd_release_get_version(rel), NULL); tags = xb_silo_query_with_context(self->silo, query, &context, &error_local); #else if (!xb_query_bind_str(query, 0, guid, error)) { g_prefix_error(error, "failed to bind GUID: "); return FALSE; } if (!xb_query_bind_str(query, 1, fwupd_release_get_version(rel), error)) { g_prefix_error(error, "failed to bind version: "); return FALSE; } tags = xb_silo_query_full(self->silo, query, &error_local); #endif 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); fwupd_release_add_tag(rel, xb_node_get_text(tag)); } } /* success */ return TRUE; } static gboolean fu_engine_set_release_from_appstream(FuEngine *self, FuEngineRequest *request, FuDevice *dev, FwupdRelease *rel, XbNode *component, XbNode *release, GError **error) { FwupdRemote *remote = NULL; const gchar *tmp; const gchar *remote_id; guint64 tmp64; g_autofree gchar *description_xpath = NULL; g_autofree gchar *name_xpath = NULL; g_autofree gchar *namevs_xpath = NULL; g_autofree gchar *summary_xpath = NULL; g_autofree gchar *version_rel = 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; /* set from the component */ tmp = xb_node_query_text(component, "id", NULL); if (tmp != NULL) fwupd_release_set_appstream_id(rel, tmp); tmp = xb_node_query_text(component, "url[@type='homepage']", NULL); if (tmp != NULL) fwupd_release_set_homepage(rel, tmp); tmp = xb_node_query_text(component, "project_license", NULL); if (tmp != NULL) fwupd_release_set_license(rel, tmp); name_xpath = fu_engine_request_get_localized_xpath(request, "name"); tmp = xb_node_query_text(component, name_xpath, NULL); if (tmp != NULL) fwupd_release_set_name(rel, tmp); summary_xpath = fu_engine_request_get_localized_xpath(request, "summary"); tmp = xb_node_query_text(component, summary_xpath, NULL); if (tmp != NULL) fwupd_release_set_summary(rel, tmp); namevs_xpath = fu_engine_request_get_localized_xpath(request, "name_variant_suffix"); tmp = xb_node_query_text(component, namevs_xpath, NULL); if (tmp != NULL) fwupd_release_set_name_variant_suffix(rel, tmp); tmp = xb_node_query_text(component, "branch", NULL); if (tmp != NULL) fwupd_release_set_branch(rel, tmp); tmp = xb_node_query_text(component, "developer_name", NULL); if (tmp != NULL) fwupd_release_set_vendor(rel, tmp); /* refresh the device and release to the new version format too */ fu_engine_md_refresh_device_from_component(self, dev, component); /* the version is fixed up at runtime */ version_rel = fu_engine_get_release_version(self, dev, release, error); if (version_rel == NULL) return FALSE; fwupd_release_set_version(rel, version_rel); /* optional release ID -- currently a integer but maybe namespaced in the future */ fwupd_release_set_id(rel, xb_node_get_attr(release, "id")); /* find the remote */ 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); remote = fu_remote_list_get_by_id(self->remote_list, remote_id); if (remote == NULL) g_warning("no remote found for release %s", version_rel); } tmp = xb_node_query_text(component, "../custom/value[@key='LVFS::Distributor']", NULL); if (g_strcmp0(tmp, "community") == 0) fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_IS_COMMUNITY); artifact = xb_node_query_first(release, "artifacts/artifact", NULL); if (artifact != NULL) { if (!fu_engine_set_release_from_artifact(self, rel, remote, artifact, error)) return FALSE; } description_xpath = fu_engine_request_get_localized_xpath(request, "description"); description = xb_node_query_first(release, 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 (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_AFFECTS_FDE) && request != NULL && !fu_engine_request_has_feature_flag(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(rel, FWUPD_RELEASE_FLAG_IS_COMMUNITY) && request != NULL && !fu_engine_request_has_feature_flag(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(rel, str->str); } if (artifact == NULL) { tmp = xb_node_query_text(release, "location", NULL); if (tmp != NULL) { g_autofree gchar *uri = NULL; if (remote != NULL) uri = fwupd_remote_build_firmware_uri(remote, tmp, NULL); if (uri == NULL) uri = g_strdup(tmp); fwupd_release_add_location(rel, uri); } } if (fwupd_release_get_locations(rel)->len == 0 && remote != NULL && fwupd_remote_get_kind(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(rel, uri); } } if (artifact == NULL) { tmp = xb_node_query_text(release, "checksum[@target='content']", NULL); if (tmp != NULL) fwupd_release_set_filename(rel, tmp); } tmp = xb_node_query_text(release, "url[@type='details']", NULL); if (tmp != NULL) fwupd_release_set_details_url(rel, tmp); tmp = xb_node_query_text(release, "url[@type='source']", NULL); if (tmp != NULL) fwupd_release_set_source_url(rel, tmp); if (artifact == NULL) { tmp = xb_node_query_text(release, "checksum[@target='container']", NULL); if (tmp != NULL) fwupd_release_add_checksum(rel, tmp); } if (artifact == NULL) { tmp64 = xb_node_query_text_as_uint(release, "size[@type='installed']", NULL); if (tmp64 != G_MAXUINT64) fwupd_release_set_size(rel, tmp64); } if (fwupd_release_get_size(rel) == 0) { GBytes *sz = xb_node_get_data(release, "fwupd::ReleaseSize"); if (sz != NULL) { const guint64 *sizeptr = g_bytes_get_data(sz, NULL); fwupd_release_set_size(rel, *sizeptr); } } tmp = xb_node_get_attr(release, "urgency"); if (tmp != NULL) fwupd_release_set_urgency(rel, fwupd_release_urgency_from_string(tmp)); tmp64 = xb_node_get_attr_as_uint(release, "install_duration"); if (tmp64 != G_MAXUINT64) fwupd_release_set_install_duration(rel, tmp64); tmp64 = xb_node_get_attr_as_uint(release, "timestamp"); if (tmp64 != G_MAXUINT64) fwupd_release_set_created(rel, 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(rel, 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(rel, xb_node_get_text(tag)); } } issues = xb_node_query(component, "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(rel, xb_node_get_text(n)); } } tmp = xb_node_query_text(component, "screenshots/screenshot/caption", NULL); if (tmp != NULL) fwupd_release_set_detach_caption(rel, tmp); tmp = xb_node_query_text(component, "screenshots/screenshot/image", NULL); if (tmp != NULL) { if (remote != NULL) { g_autofree gchar *img = NULL; img = fwupd_remote_build_firmware_uri(remote, tmp, error); if (img == NULL) return FALSE; fwupd_release_set_detach_image(rel, img); } else { fwupd_release_set_detach_image(rel, tmp); } } tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateProtocol']", NULL); if (tmp != NULL) fwupd_release_set_protocol(rel, tmp); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateMessage']", NULL); if (tmp != NULL) fwupd_release_set_update_message(rel, tmp); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateImage']", NULL); if (tmp != NULL) { if (remote != NULL) { g_autofree gchar *img = NULL; img = fwupd_remote_build_firmware_uri(remote, tmp, error); if (img == NULL) return FALSE; fwupd_release_set_update_image(rel, img); } else { fwupd_release_set_update_image(rel, tmp); } } if (xb_node_get_attr(release, "date_eol") != NULL) fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_END_OF_LIFE); /* sort the locations by scheme */ g_ptr_array_sort_with_data(fwupd_release_get_locations(rel), fu_engine_scheme_compare_cb, self); /* add any client-side BKC tags */ if (!fu_engine_add_local_release_metadata(self, dev, rel, error)) return FALSE; /* success */ return TRUE; } /* finds the remote-id for the first firmware in the silo that matches this * container checksum */ static const gchar * fu_engine_get_remote_id_for_checksum(FuEngine *self, const gchar *csum) { g_autofree gchar *xpath = NULL; g_autoptr(XbNode) key = NULL; xpath = g_strdup_printf("components/component[@type='firmware']/releases/release/" "checksum[@target='container'][text()='%s']/../../" "../../custom/value[@key='fwupd::RemoteId']", csum); key = xb_silo_query_first(self->silo, xpath, NULL); if (key == NULL) return NULL; return xb_node_get_text(key); } /** * 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(self, device); fu_engine_emit_changed(self); return TRUE; } gboolean fu_engine_modify_config(FuEngine *self, const gchar *key, const gchar *value, GError **error) { const gchar *keys[] = {"ArchiveSizeMax", "ApprovedFirmware", "BlockedFirmware", "DisabledDevices", "DisabledPlugins", "EnumerateAllDevices", "HostBkc", "IdleTimeout", "IgnorePower", "OnlyTrusted", "UpdateMotd", "UriSchemes", "VerboseDomains", 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; } /* modify, effective next reboot */ return fu_config_set_key_value(self->config, 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", 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); } /** * 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_autoptr(FuDevice) device = NULL; /* find the correct device */ device = fu_history_get_device_by_id(self->history, device_id, error); if (device == NULL) return FALSE; /* support adding a subset of device flags */ if (g_strcmp0(key, "Flags") == 0) { FwupdDeviceFlags flag = fwupd_device_flag_from_string(value); if (flag == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not a valid flag", key); return FALSE; } if (flag != FWUPD_DEVICE_FLAG_REPORTED && flag != FWUPD_DEVICE_FLAG_NOTIFIED) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flag %s cannot be set from client", key); return FALSE; } fu_device_add_flag(device, flag); return fu_history_modify_device(self->history, device, error); } /* others invalid */ 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_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; /* 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(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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf("%s/verify/%s.xml", localstatedir, device_id); if (!fu_common_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) { XbNode *component; g_autoptr(GError) error_local = NULL; #if LIBXMLB_CHECK_VERSION(0, 3, 0) g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); #endif /* no components in silo */ if (self->query_component_by_guid == NULL) return NULL; #if LIBXMLB_CHECK_VERSION(0, 3, 0) 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); #else if (!xb_query_bind_str(self->query_component_by_guid, 0, guid, &error_local)) { g_warning("failed to bind 0: %s", error_local->message); return NULL; } component = xb_silo_query_first_full(self->silo, self->query_component_by_guid, &error_local); #endif 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_common_get_path(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; #if LIBXMLB_CHECK_VERSION(0, 3, 0) g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); #endif /* bind GUID and then query */ #if LIBXMLB_CHECK_VERSION(0, 3, 0) 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); #else if (!xb_query_bind_str(query, 0, guid, error)) { g_prefix_error(error, "failed to bind string: "); return NULL; } releases = xb_silo_query_full(self->silo, query, &error_local); #endif 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_common_version_parse_from_format(rel_ver, fmt); if (fu_common_vercmp_full(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_autoptr(GString) checksums_device = g_string_new(NULL); 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)); } for (guint i = 0; i < checksums->len; i++) { const gchar *hash_tmp = g_ptr_array_index(checksums, i); xb_string_append_union(checksums_device, "%s", hash_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->str); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_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_common_vercmp_full(version, version_req, fmt); ret = rc == 0; } else if (g_strcmp0(tmp, "ne") == 0) { rc = fu_common_vercmp_full(version, version_req, fmt); ret = rc != 0; } else if (g_strcmp0(tmp, "lt") == 0) { rc = fu_common_vercmp_full(version, version_req, fmt); ret = rc < 0; } else if (g_strcmp0(tmp, "gt") == 0) { rc = fu_common_vercmp_full(version, version_req, fmt); ret = rc > 0; } else if (g_strcmp0(tmp, "le") == 0) { rc = fu_common_vercmp_full(version, version_req, fmt); ret = rc <= 0; } else if (g_strcmp0(tmp, "ge") == 0) { rc = fu_common_vercmp_full(version, version_req, fmt); ret = rc >= 0; } else if (g_strcmp0(tmp, "glob") == 0) { ret = fu_common_fnmatch(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_check_requirement_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_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_check_requirement_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_common_strjoin_array("|", 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_engine_check_requirement_firmware(FuEngine *self, XbNode *req, FuDevice *device, FwupdInstallFlags flags, GError **error) { guint64 depth; g_autoptr(FuDevice) device_actual = g_object_ref(device); g_autoptr(GError) error_local = NULL; /* look at the parent device */ depth = xb_node_get_attr_as_uint(req, "depth"); if (depth != G_MAXUINT64) { for (guint64 i = 0; i < depth; i++) { FuDevice *device_tmp = fu_device_get_parent(device_actual); if (device_actual == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No parent device for %s " "(%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT ")", fu_device_get_name(device_actual), i, depth); return FALSE; } g_set_object(&device_actual, device_tmp); } } /* old firmware version */ if (xb_node_get_text(req) == NULL) { const gchar *version = fu_device_get_version(device_actual); if (!fu_engine_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) { const gchar *version = fu_device_get_version_bootloader(device_actual); if (!fu_engine_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_check_requirement_vendor_id(self, req, device_actual, error); } /* child version */ if (g_strcmp0(xb_node_get_text(req), "not-child") == 0) return fu_engine_check_requirement_not_child(self, req, device_actual, error); /* another device */ if (fwupd_guid_is_valid(xb_node_get_text(req))) { const gchar *guid = xb_node_get_text(req); const gchar *version; /* find if the other device exists */ if (depth == G_MAXUINT64) { g_autoptr(FuDevice) device_tmp = NULL; device_tmp = fu_device_list_get_by_guid(self->device_list, guid, error); if (device_tmp == NULL) return FALSE; g_set_object(&device_actual, device_tmp); /* look for a sibling */ } else if (depth == 0) { FuDevice *child = NULL; FuDevice *parent = fu_device_get_parent(device_actual); GPtrArray *children; if (parent == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No parent specified for device %s", fu_device_get_name(device_actual)); return FALSE; } 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_guid(child, guid)) break; child = NULL; } if (child == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sibling found with GUID of %s", guid); return FALSE; } g_set_object(&device_actual, child); /* verify the parent device has the GUID */ } else { if (!fu_device_has_guid(device_actual, guid)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No GUID of %s on parent device %s", guid, fu_device_get_name(device_actual)); 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_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; } return TRUE; } /* not supported */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle firmware requirement '%s'", xb_node_get_text(req)); return FALSE; } static gboolean fu_engine_check_requirement_id(FuEngine *self, XbNode *req, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *version = g_hash_table_lookup(self->runtime_versions, 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_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_check_requirement_hardware(FuEngine *self, XbNode *req, GError **error) { g_auto(GStrv) hwid_split = NULL; /* 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(self->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_check_requirement_client(FuEngine *self, FuEngineRequest *request, XbNode *req, GError **error) { FwupdFeatureFlags flags; g_auto(GStrv) feature_split = NULL; /* 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_check_requirement(FuEngine *self, FuEngineRequest *request, XbNode *req, FuDevice *device, FwupdInstallFlags flags, GError **error) { /* ensure component requirement */ if (g_strcmp0(xb_node_get_element(req), "id") == 0) return fu_engine_check_requirement_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_check_requirement_firmware(self, req, device, flags, error); } /* ensure hardware requirement */ if (g_strcmp0(xb_node_get_element(req), "hardware") == 0) return fu_engine_check_requirement_hardware(self, req, error); /* ensure client requirement */ if (g_strcmp0(xb_node_get_element(req), "client") == 0) return fu_engine_check_requirement_client(self, request, req, 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; } gboolean fu_engine_check_trust(FuEngine *self, FuInstallTask *task, GError **error) { if (fu_config_get_only_trusted(self->config) && (fu_install_task_get_trust_flags(task) & FWUPD_TRUST_FLAG_PAYLOAD) == 0) { g_autofree gchar *sysconfdir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *fn = g_build_filename(sysconfdir, "daemon.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; } static gboolean fu_engine_check_soft_requirement(FuEngine *self, FuEngineRequest *request, XbNode *req, FuDevice *device, FwupdInstallFlags flags, GError **error) { g_autoptr(GError) error_local = NULL; if (!fu_engine_check_requirement(self, request, req, device, flags, &error_local)) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_debug("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; } gboolean fu_engine_check_requirements(FuEngine *self, FuEngineRequest *request, FuInstallTask *task, FwupdInstallFlags flags, GError **error) { FuDevice *device = fu_install_task_get_device(task); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) reqs_hard = NULL; g_autoptr(GPtrArray) reqs_soft = NULL; /* all install task checks require a device */ if (device != NULL && fu_engine_request_get_kind(request) == FU_ENGINE_REQUEST_KIND_ACTIVE) { if (!fu_install_task_check_requirements(task, flags, error)) return FALSE; } /* do engine checks */ reqs_hard = xb_node_query(fu_install_task_get_component(task), "requires/*", 0, &error_local); if (reqs_hard != NULL) { for (guint i = 0; i < reqs_hard->len; i++) { XbNode *req = g_ptr_array_index(reqs_hard, i); if (!fu_engine_check_requirement(self, request, req, device, flags, error)) return FALSE; } } else 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; } /* soft requirements */ g_clear_error(&error_local); reqs_soft = xb_node_query(fu_install_task_get_component(task), "suggests/*|recommends/*", 0, &error_local); if (reqs_soft != NULL) { for (guint i = 0; i < reqs_soft->len; i++) { XbNode *req = g_ptr_array_index(reqs_soft, i); if (!fu_engine_check_soft_requirement(self, request, req, device, flags, error)) return FALSE; } } else 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; } return TRUE; } void fu_engine_idle_reset(FuEngine *self) { fu_idle_reset(self->idle); } 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 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", "DistroId"}, {"VERSION_ID", "DistroVersion"}, {"VARIANT_ID", "DistroVariant"}, {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 gchar * fu_engine_get_proc_cmdline(GError **error) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(GString) cmdline_safe = g_string_new(NULL); const gchar *ignore[] = { "", "auto", "boot", "BOOT_IMAGE", "console", "cryptdevice", "cryptkey", "earlycon", "earlyprintk", "ether", "initrd", "ip", "LANG", "loglevel", "luks.key", "luks.name", "luks.options", "luks.uuid", "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", "showopts", "splash", "swap", "systemd.mask", "systemd.unit", "systemd.verity_root_data", "systemd.verity_root_hash", "systemd.wants", "verbose", "vt.handoff", "zfs", NULL, /* last entry */ }; /* get a PII-safe kernel command line */ if (!g_file_get_contents("/proc/cmdline", &buf, &bufsz, error)) return NULL; if (bufsz > 0) { g_auto(GStrv) tokens = fu_common_strnsplit(buf, bufsz - 1, " ", -1); for (guint i = 0; tokens[i] != NULL; i++) { g_auto(GStrv) kv = NULL; if (strlen(tokens[i]) == 0) continue; kv = g_strsplit(tokens[i], "=", 2); if (g_strv_contains(ignore, kv[0])) continue; if (cmdline_safe->len > 0) g_string_append(cmdline_safe, " "); g_string_append(cmdline_safe, tokens[i]); } } return g_string_free(g_steal_pointer(&cmdline_safe), FALSE); } static gchar * fu_engine_get_kernel_cmdline(GError **error) { /* Linuxish */ if (g_file_test("/proc/cmdline", G_FILE_TEST_EXISTS)) return fu_engine_get_proc_cmdline(error); /* BSDish */ return fu_kenv_get_string("kernel_options", error); } static gboolean fu_engine_get_report_metadata_kernel_cmdline(GHashTable *hash, GError **error) { g_autofree gchar *cmdline = fu_engine_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) { 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(self->compile_versions); g_autoptr(GList) runtime_keys = g_hash_table_get_keys(self->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(self->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(self->runtime_versions, id); g_hash_table_insert(hash, g_strdup_printf("RuntimeVersion(%s)", id), g_strdup(version)); } if (!fu_engine_get_report_metadata_os_release(hash, error)) return NULL; if (!fu_engine_get_report_metadata_kernel_cmdline(hash, error)) return NULL; /* these affect the report credibility */ fu_engine_add_report_metadata_bool(hash, "FwupdTainted", self->tainted); #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_config_get_host_bkc(self->config); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostBkc"), g_strdup(tmp)); /* DMI data */ tmp = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_PRODUCT_NAME); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostProduct"), g_strdup(tmp)); tmp = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_FAMILY); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostFamily"), g_strdup(tmp)); tmp = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_PRODUCT_SKU); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostSku"), g_strdup(tmp)); tmp = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_MANUFACTURER); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostVendor"), 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); 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); 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; } 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); 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; } return TRUE; } /** * fu_engine_install_tasks: * @self: a #FuEngine * @request: a #FuEngineRequest * @install_tasks: (element-type FuInstallTask): 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_check_requirements() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install_tasks(FuEngine *self, FuEngineRequest *request, GPtrArray *install_tasks, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FwupdFeatureFlags feature_flags = fu_engine_request_get_feature_flags(request); 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, "update"); g_assert(locker != NULL); /* notify the plugins about the composite action */ devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index(install_tasks, i); g_debug("composite update %u: %s", i + 1, fu_device_get_id(fu_install_task_get_device(task))); g_ptr_array_add(devices, g_object_ref(fu_install_task_get_device(task))); } 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_steps(progress, install_tasks->len); for (guint i = 0; i < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index(install_tasks, i); if (!fu_engine_install(self, task, blob_cab, fu_progress_get_child(progress), flags, feature_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 < install_tasks->len; i++) { FuInstallTask *task = g_ptr_array_index(install_tasks, i); FuDevice *device = fu_install_task_get_device(task); 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_debug("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 */ if (!fu_engine_composite_cleanup(self, devices_new, error)) { g_prefix_error(error, "failed to cleanup composite action: "); return FALSE; } /* success */ return TRUE; } static FwupdRelease * fu_engine_create_release_metadata(FuEngine *self, FuDevice *device, FuPlugin *plugin, GError **error) { GPtrArray *metadata_sources; g_autoptr(FwupdRelease) release = fwupd_release_new(); 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 NULL; fwupd_release_add_metadata(release, metadata_hash); if (fu_plugin_get_report_metadata(plugin) != NULL) fwupd_release_add_metadata(release, fu_plugin_get_report_metadata(plugin)); metadata_device = fu_device_report_metadata_pre(device); if (metadata_device != NULL) fwupd_release_add_metadata(release, metadata_device); /* 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_warning("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( release, fu_plugin_get_report_metadata(plugin_tmp)); } } } return g_steal_pointer(&release); } 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 } 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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *trigger = fu_common_get_path(FU_PATH_KIND_OFFLINE_TRIGGER); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* does already exist */ filename = fu_common_realpath(trigger, NULL); if (g_strcmp0(filename, symlink_target) == 0) { g_debug("%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, 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_common_get_path(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_common_get_path(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_debug("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_install_release(FuEngine *self, FuDevice *device_orig, XbNode *component, XbNode *rel, FuProgress *progress, FwupdInstallFlags flags, FwupdFeatureFlags feature_flags, GError **error) { FuPlugin *plugin; FwupdVersionFormat fmt; GBytes *blob_fw; const gchar *tmp; g_autofree gchar *version_orig = NULL; g_autofree gchar *version_rel = NULL; g_autoptr(FuDevice) device_tmp = NULL; g_autoptr(FuDevice) device = g_object_ref(device_orig); g_autoptr(GBytes) blob_fw2 = NULL; g_autoptr(GError) error_local = NULL; /* set this for the callback */ self->write_history = (flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0; /* get per-release firmware blob */ blob_fw = xb_node_get_data(rel, "fwupd::FirmwareBlob"); if (blob_fw == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to get firmware blob from release"); return FALSE; } /* use a bubblewrap helper script to build the firmware */ tmp = g_object_get_data(G_OBJECT(component), "fwupd::BuilderScript"); if (tmp != NULL) { const gchar *tmp2 = g_object_get_data(G_OBJECT(component), "fwupd::BuilderOutput"); if (tmp2 == NULL) tmp2 = "firmware.bin"; blob_fw2 = fu_common_firmware_builder(blob_fw, tmp, tmp2, error); if (blob_fw2 == NULL) return FALSE; } else { blob_fw2 = g_bytes_ref(blob_fw); } /* get the plugin */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* schedule this for the next reboot if not in system-update.target, * but first check if allowed on battery power */ version_rel = fu_engine_get_release_version(self, device, rel, error); if (version_rel == NULL) { g_prefix_error(error, "failed to get release version: "); return FALSE; } /* add device to database */ if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { g_autoptr(FwupdRelease) release_tmp = NULL; release_tmp = fu_engine_create_release_metadata(self, device, plugin, error); if (release_tmp == NULL) return FALSE; tmp = xb_node_query_text(component, "releases/release/checksum[@target='container']", NULL); if (tmp != NULL) fwupd_release_add_checksum(release_tmp, tmp); fwupd_release_set_version(release_tmp, version_rel); if (!fu_history_add_device(self->history, device, release_tmp, error)) return FALSE; } /* install firmware blob */ version_orig = g_strdup(fu_device_get_version(device)); if (!fu_engine_install_blob(self, device, blob_fw2, progress, flags, feature_flags, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM)) { fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_FAILED); } 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; } /* for online updates, verify the version changed if not a re-install */ fmt = fu_device_get_version_format(device); if (version_rel != NULL && fu_common_vercmp_full(version_orig, version_rel, fmt) != 0 && fu_common_vercmp_full(version_orig, fu_device_get_version(device), fmt) == 0 && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { g_autofree gchar *str = NULL; fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); str = g_strdup_printf("device version not updated on success, %s != %s", version_rel, fu_device_get_version(device)); fu_device_set_update_error(device, str); } return TRUE; } typedef struct { gboolean ret; GError **error; FuEngine *self; FuDevice *device; } FuEngineSortHelper; static gint fu_engine_sort_release_versions_cb(gconstpointer a, gconstpointer b, gpointer user_data) { FuEngineSortHelper *helper = (FuEngineSortHelper *)user_data; XbNode *na = *((XbNode **)a); XbNode *nb = *((XbNode **)b); g_autofree gchar *va = NULL; g_autofree gchar *vb = NULL; /* already failed */ if (!helper->ret) return 0; /* get the semver from the release */ va = fu_engine_get_release_version(helper->self, helper->device, na, helper->error); if (va == NULL) { g_prefix_error(helper->error, "failed to get release version: "); return 0; } vb = fu_engine_get_release_version(helper->self, helper->device, nb, helper->error); if (vb == NULL) { g_prefix_error(helper->error, "failed to get release version: "); return 0; } return fu_common_vercmp_full(va, vb, fu_device_get_version_format(helper->device)); } static gboolean fu_engine_sort_releases(FuEngine *self, FuDevice *device, GPtrArray *rels, GError **error) { FuEngineSortHelper helper = { .ret = TRUE, .self = self, .device = device, .error = error, }; g_ptr_array_sort_with_data(rels, fu_engine_sort_release_versions_cb, &helper); return helper.ret; } /** * fu_engine_install: * @self: a #FuEngine * @task: a #FuInstallTask * @blob_cab: the #GBytes of the .cab file * @progress: a #FuProgress * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_OLDER * @feature_flags: feature flags, e.g. %FWUPD_FEATURE_FLAG_NONE * @error: (nullable): optional return location for an error * * Installs a specific firmware file on a device. * * By this point all the requirements and tests should have been done in * fu_engine_check_requirements() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install(FuEngine *self, FuInstallTask *task, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, FwupdFeatureFlags feature_flags, GError **error) { XbNode *component = fu_install_task_get_component(task); g_autoptr(FuDevice) device = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbNode) rel_newest = NULL; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(XB_IS_NODE(component), FALSE); g_return_val_if_fail(blob_cab != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not in bootloader mode */ device = g_object_ref(fu_install_task_get_device(task)); if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { const gchar *tmp = NULL; /* both optional; the plugin can specify a fallback */ tmp = xb_node_query_text(component, "screenshots/screenshot/caption", NULL); if (tmp != NULL) fu_device_set_update_message(device, tmp); tmp = xb_node_query_text(component, "screenshots/screenshot/image", NULL); if (tmp != NULL) fu_device_set_update_image(device, tmp); } /* get the newest version */ #if LIBXMLB_CHECK_VERSION(0, 2, 0) 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_newest = xb_node_query_first_full(component, query, &error_local); #else rel_newest = xb_node_query_first(component, "releases/release", &error_local); #endif if (rel_newest == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "No releases in the firmware component: %s", error_local->message); 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; g_autoptr(FwupdRelease) release_tmp = NULL; g_autofree gchar *version_rel = NULL; version_rel = fu_engine_get_release_version(self, device, rel_newest, error); if (version_rel == NULL) { g_prefix_error(error, "failed to get release version: "); return FALSE; } plugin = fu_plugin_list_find_by_name(self->plugin_list, "upower", NULL); if (plugin != NULL) { if (!fu_plugin_runner_prepare(plugin, device, flags, error)) return FALSE; } fu_progress_set_status(progress, FWUPD_STATUS_SCHEDULING); release_tmp = fu_engine_create_release_metadata(self, device, plugin, error); if (release_tmp == NULL) return FALSE; fwupd_release_set_version(release_tmp, version_rel); return fu_engine_schedule_update(self, device, release_tmp, blob_cab, flags, error); } /* install each intermediate release, or install only the newest version */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES)) { g_autoptr(GPtrArray) rels = NULL; #if LIBXMLB_CHECK_VERSION(0, 2, 0) rels = xb_node_query_full(component, query, &error_local); #else rels = xb_node_query(component, "releases/release", 0, &error_local); #endif if (rels == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "No releases in the firmware component: %s", error_local->message); return FALSE; } if (!fu_engine_sort_releases(self, device, rels, error)) return FALSE; fu_progress_set_steps(progress, rels->len); for (guint i = 0; i < rels->len; i++) { XbNode *rel = g_ptr_array_index(rels, i); if (!fu_engine_install_release(self, device, component, rel, fu_progress_get_child(progress), flags, feature_flags, error)) return FALSE; fu_progress_step_done(progress); } } else { if (!fu_engine_install_release(self, device, component, rel_newest, progress, flags, feature_flags, error)) return FALSE; } /* 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); /* make the UI update */ fu_engine_emit_changed(self); 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_device: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets a specific device. * * 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; /* 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 NULL; } /* get the new device */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device after replug: "); 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, 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, flags, error); } /* same as FuDevice->cleanup, but with the device open */ static gboolean fu_engine_device_cleanup(FuEngine *self, FuDevice *device, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_debug("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, 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_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *configfile = g_build_filename(configdir, "daemon.conf", NULL); g_warning("Ignoring deprecated flag provided by client " "'FWUPD_INSTALL_FLAG_IGNORE_POWER'. To ignore power levels, modify %s", configfile); } if (fu_config_get_ignore_power(self->config)) return TRUE; /* not charging */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && (fu_context_get_battery_state(self->ctx) == FU_BATTERY_STATE_DISCHARGING || fu_context_get_battery_state(self->ctx) == FU_BATTERY_STATE_EMPTY)) { 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_context_get_battery_level(self->ctx) != FU_BATTERY_VALUE_INVALID && fu_context_get_battery_threshold(self->ctx) != FU_BATTERY_VALUE_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, FwupdInstallFlags flags, const gchar *device_id, 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; } /* don't rely on a plugin clearing this */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); if (!fu_engine_device_check_power(self, device, flags, error)) return FALSE; str = fu_device_to_string(device); g_debug("prepare -> %s", str); if (!fu_engine_device_prepare(self, device, 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, flags, error)) return FALSE; } /* wait for 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; } return TRUE; } static gboolean fu_engine_cleanup(FuEngine *self, FwupdInstallFlags flags, const gchar *device_id, 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; } str = fu_device_to_string(device); g_debug("cleanup -> %s", str); if (!fu_engine_device_cleanup(self, device, 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, flags, error)) return FALSE; } /* wait for 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; } 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; /* 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; } str = fu_device_to_string(device); g_debug("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; } /* 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; /* 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_debug("attach -> %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_attach(plugin, device, progress, error)) return FALSE; 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_debug("activate -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; g_debug("Activating %s", fu_device_get_name(device)); if (!fu_plugin_runner_activate(plugin, device, progress, error)) return FALSE; fu_engine_emit_device_changed(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_debug("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_debug("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; } 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(FuDevice) device_pending = 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; } device_pending = fu_history_get_device_by_id(self->history, device_id, NULL); str = fu_device_to_string(device); g_debug("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)) { g_autoptr(GError) error_attach = NULL; g_autoptr(GError) error_cleanup = NULL; /* attack back into runtime then cleanup */ if (!fu_plugin_runner_attach(plugin, device, progress, &error_attach)) { g_warning("failed to attach device after failed update: %s", error_attach->message); } if (!fu_engine_cleanup(self, flags, device_id, &error_cleanup)) { g_warning("failed to update-cleanup after failed update: %s", error_cleanup->message); } return FALSE; } /* cleanup */ if (device_pending != NULL) { const gchar *tmp; FwupdRelease *release; /* update history database */ fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); if (!fu_history_modify_device(self->history, device, error)) return FALSE; /* delete cab file */ release = fu_device_get_release_default(device_pending); tmp = fwupd_release_get_filename(release); if (tmp != NULL && g_str_has_prefix(tmp, FWUPD_LIBEXECDIR)) { g_autoptr(GError) error_delete = NULL; g_autoptr(GFile) file = NULL; file = g_file_new_for_path(tmp); if (!g_file_delete(file, NULL, &error_delete)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to delete %s: %s", tmp, error_delete->message); return FALSE; } } } return TRUE; } GBytes * fu_engine_firmware_dump(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = 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); } 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_autoptr(GTimer) timer = g_timer_new(); /* 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); /* plugins can set FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED to run again, but they * must return TRUE rather than an error */ device_id = g_strdup(fu_device_get_id(device)); do { g_autoptr(FuDevice) device_tmp = NULL; /* 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; } /* signal to all the plugins the update is about to happen */ if (!fu_engine_prepare(self, flags, device_id, error)) return FALSE; /* progress */ if (!fu_engine_set_progress(self, device_id, progress, error)) return FALSE; if (fu_progress_get_steps(progress) == 0) { 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 */ } /* detach to bootloader mode */ if (!fu_engine_detach(self, device_id, fu_progress_get_child(progress), feature_flags, error)) return FALSE; fu_progress_step_done(progress); /* install */ if (!fu_engine_write_firmware(self, device_id, blob_fw, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* attach into runtime mode */ if (!fu_engine_attach(self, device_id, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* get the new version number */ if (!fu_engine_reload(self, device_id, error)) return FALSE; fu_progress_step_done(progress); /* 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; /* not sure we can do any better than this */ fu_progress_reset(progress); } while (TRUE); /* signal to all the plugins the update has happened */ if (!fu_engine_cleanup(self, flags, device_id, error)) return FALSE; /* make the UI update */ g_debug("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; /* print what we've got */ components = xb_silo_query(self->silo, "components/component[@type='firmware']", 0, NULL); if (components == NULL) return TRUE; g_debug("%u components now in silo", components->len); /* 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[@type='firmware']/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[@type='firmware']/" "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; } 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 XbBuilderSource * fu_engine_create_metadata_builder_source(FuEngine *self, const gchar *fn, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autofree gchar *xml = NULL; g_debug("building metadata for %s", fn); blob = fu_common_get_contents_bytes(fn, error); if (blob == NULL) return NULL; /* convert the silo for the CAB into a XbBuilderSource */ 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; if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, 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); files = fu_common_get_files_recursive(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_debug("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; 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_KIND_ONLY_SUPPORTED); 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; } /* 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(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(self, device); } } static void fu_engine_md_refresh_device_name(FuEngine *self, FuDevice *device, XbNode *component) { const gchar *name = NULL; /* require data */ if (component == NULL) return; /* copy 1:1 */ name = xb_node_query_text(component, "name", NULL); if (name != NULL) { fu_device_set_name(device, name); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME); } } static void fu_engine_md_refresh_device_vendor(FuEngine *self, FuDevice *device, XbNode *component) { const gchar *vendor = NULL; /* require data */ if (component == NULL) return; /* copy 1:1 */ vendor = xb_node_query_text(component, "developer_name", NULL); if (vendor != NULL) { fu_device_set_vendor(device, vendor); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); } } static void fu_engine_md_refresh_device_icon(FuEngine *self, FuDevice *device, XbNode *component) { const gchar *icon = NULL; /* require data */ if (component == NULL) return; /* copy 1:1 */ icon = xb_node_query_text(component, "icon", NULL); if (icon != NULL) { fu_device_add_icon(device, icon); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON); } } static const gchar * fu_common_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"; return NULL; } static void fu_engine_md_refresh_device_name_category(FuEngine *self, FuDevice *device, XbNode *component) { const gchar *name = NULL; g_autoptr(GPtrArray) cats = NULL; /* require data */ if (component == NULL) return; /* 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_common_device_category_to_name(xb_node_get_text(n)); if (name != NULL) break; } if (name != NULL) { fu_device_set_name(device, name); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY); } } 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_engine_md_refresh_device_verfmt(FuEngine *self, FuDevice *device, XbNode *component) { FwupdVersionFormat verfmt = FWUPD_VERSION_FORMAT_UNKNOWN; g_autoptr(GPtrArray) verfmts = NULL; /* require data */ if (component == NULL) return; /* 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(device) != verfmt) { fu_device_set_version_format(device, verfmt); if (fu_device_get_version_raw(device) != 0x0) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint32(fu_device_get_version_raw(device), verfmt); fu_device_set_version(device, version); } if (fu_device_get_version_lowest_raw(device) != 0x0) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint32(fu_device_get_version_lowest_raw(device), verfmt); fu_device_set_version_lowest(device, version); } if (fu_device_get_version_bootloader_raw(device) != 0x0) { g_autofree gchar *version = NULL; version = fu_common_version_from_uint32( fu_device_get_version_bootloader_raw(device), verfmt); fu_device_set_version_bootloader(device, version); } } /* do not try to do this again */ fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); } void fu_engine_md_refresh_device_from_component(FuEngine *self, FuDevice *device, XbNode *component) { /* set the name */ if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME)) fu_engine_md_refresh_device_name(self, device, component); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY)) fu_engine_md_refresh_device_name_category(self, device, component); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON)) fu_engine_md_refresh_device_icon(self, device, component); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR)) fu_engine_md_refresh_device_vendor(self, device, component); /* fix the version */ if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT)) fu_engine_md_refresh_device_verfmt(self, device, component); } static void fu_engine_md_refresh_devices(FuEngine *self) { g_autoptr(GPtrArray) devices = fu_device_list_get_all(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 */ fu_engine_md_refresh_device_from_component(self, device, component); } } static gboolean fu_engine_load_metadata_store_local(FuEngine *self, XbBuilder *builder, FuPathKind path_kind, GError **error) { g_autofree gchar *fn = fu_common_get_path(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_common_filename_glob(metadata_path, "*.xml", &error_local); if (metadata_fns == NULL) { g_debug("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_debug("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); /* 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_get_enabled(remote)) 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_debug("building 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_common_get_path(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_config_changed_cb(FuConfig *config, FuEngine *self) { fu_idle_set_timeout(self->idle, fu_config_get_idle_timeout(config)); } 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 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 LIBJCAT_CHECK_VERSION(0, 1, 3) if (jcat_result_get_method(result) == JCAT_BLOB_METHOD_SIGNATURE) return g_object_ref(result); #else guint verify_kind = 0; g_autoptr(JcatEngine) engine = NULL; g_object_get(result, "engine", &engine, NULL); g_object_get(engine, "verify-kind", &verify_kind, NULL); if (verify_kind == 2) /* SIGNATURE */ return g_object_ref(result); #endif } /* 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_common_get_contents_bytes(fwupd_remote_get_filename_cache(remote), error); if (blob == NULL) return NULL; blob_sig = fu_common_get_contents_bytes(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_debug("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_get_enabled(remote)) { 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_debug("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_common_set_contents_bytes(fwupd_remote_get_filename_cache(remote), bytes_raw, error)) return FALSE; if (keyring_kind != FWUPD_KEYRING_KIND_NONE) { if (!fu_common_set_contents_bytes(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 = g_input_stream_read_bytes(stream_fd, 0x100000, NULL, error); if (bytes_raw == NULL) return FALSE; /* read signature */ bytes_sig = g_input_stream_read_bytes(stream_sig, 0x100000, NULL, 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_autoptr(XbSilo) silo = NULL; 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_cabinet_set_size_max(cabinet, fu_engine_get_archive_size_max(self)); fu_cabinet_set_jcat_context(cabinet, self->jcat_context); if (!fu_cabinet_parse(cabinet, blob_cab, FU_CABINET_PARSE_FLAG_NONE, error)) return NULL; silo = fu_cabinet_get_silo(cabinet); fu_engine_set_status(self, FWUPD_STATUS_IDLE); return g_steal_pointer(&silo); } static FuDevice * fu_engine_get_result_from_component(FuEngine *self, FuEngineRequest *request, XbNode *component, GError **error) { FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE; g_autofree gchar *description_xpath = NULL; g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; 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) release = NULL; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif dev = fu_device_new_with_context(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_set_name(dev, fu_device_get_name(device)); fu_device_set_flags(dev, fu_device_get_flags(device)); fu_device_set_internal_flags(dev, fu_device_get_internal_flags(device)); fu_device_set_id(dev, fu_device_get_id(device)); fu_device_set_version_raw(dev, fu_device_get_version_raw(device)); fu_device_set_version_format(dev, fu_device_get_version_format(device)); fu_device_set_version(dev, fu_device_get_version(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); fwupd_release_add_tag(rel, xb_node_get_text(tag)); } } /* check we can install it */ task = fu_install_task_new(NULL, component); if (!fu_engine_check_requirements(self, request, task, FWUPD_INSTALL_FLAG_IGNORE_VID_PID, &error_reqs)) { fu_device_inhibit(dev, "failed-reqs", error_reqs->message); /* continue */ } /* verify trust */ #if LIBXMLB_CHECK_VERSION(0, 2, 0) query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return NULL; release = xb_node_query_first_full(component, query, &error_local); #else release = xb_node_query_first(component, "releases/release", &error_local); #endif if (release == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get release: %s", error_local->message); return NULL; } if (!fu_keyring_get_release_flags(release, &release_flags, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("Ignoring verification: %s", error_local->message); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return NULL; } } /* 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); } rel = fwupd_release_new(); fwupd_release_set_flags(rel, release_flags); if (!fu_engine_set_release_from_appstream(self, request, dev, rel, component, release, error)) return NULL; fu_device_add_release(dev, rel); 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; } /** * 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) { const gchar *remote_id; g_autofree gchar *csum = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) details = NULL; g_autoptr(XbSilo) silo = 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_common_get_contents_fd(fd, fu_engine_get_archive_size_max(self), error); if (blob == NULL) return 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; /* does this exist in any enabled remote */ csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, blob); remote_id = fu_engine_get_remote_id_for_checksum(self, csum); /* 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; dev = fu_engine_get_result_from_component(self, request, component, error); if (dev == NULL) return NULL; if (remote_id != NULL) { FwupdRelease *rel = fu_device_get_release_default(dev); fwupd_release_set_remote_id(rel, remote_id); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED); } if (fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT)) fu_engine_md_refresh_device_verfmt(self, dev, component); /* 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(FuInstallTask) task = fu_install_task_new(dev, component); g_autoptr(GError) error_req = NULL; if (!fu_engine_check_requirements( self, request, task, 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, &error_req)) { g_debug("%s failed requirement checks: %s", fu_device_get_id(dev), error_req->message); fu_device_inhibit(dev, "failed-reqs", error_req->message); } else { g_debug("%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); } 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_all(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 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_all(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); } /** * 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 = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); devices = fu_history_get_devices(self->history, error); if (devices == NULL) return NULL; 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_instance_id(dev, "main-system-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); FwupdRelease *rel; GPtrArray *csums; /* get the checksums */ rel = fu_device_get_release_default(dev); if (rel == NULL) continue; /* 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); const gchar *remote_id = fu_engine_get_remote_id_for_checksum(self, csum); if (remote_id != NULL) { fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED); fwupd_release_set_remote_id(rel, remote_id); break; } } } return g_steal_pointer(&devices); } #if !GLIB_CHECK_VERSION(2, 62, 0) static GPtrArray * g_ptr_array_copy(GPtrArray *array, GCopyFunc func, gpointer user_data) { GPtrArray *new = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < array->len; i++) { GObject *obj = g_ptr_array_index(array, i); g_ptr_array_add(new, g_object_ref(obj)); } return new; } #endif /** * 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); FwupdRelease *rel_a = FWUPD_RELEASE(*((FwupdRelease **)a)); FwupdRelease *rel_b = FWUPD_RELEASE(*((FwupdRelease **)b)); gint rc; /* first by branch */ rc = g_strcmp0(fwupd_release_get_branch(rel_b), fwupd_release_get_branch(rel_a)); if (rc != 0) return rc; /* then by version */ return fu_common_vercmp_full(fwupd_release_get_version(rel_b), fwupd_release_get_version(rel_a), fu_device_get_version_format(device)); } 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_debug("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, FwupdRelease *rel) { GPtrArray *csums = fwupd_release_get_checksums(rel); 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(FuInstallTask) task = fu_install_task_new(device, component); g_autoptr(GPtrArray) releases_tmp = NULL; if (!fu_engine_check_requirements( self, request, task, 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, error)) return FALSE; /* 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 *release = g_ptr_array_index(releases_tmp, i); const gchar *remote_id; const gchar *update_message; const gchar *update_image; gint vercmp; GPtrArray *checksums; GPtrArray *locations; g_autoptr(FwupdRelease) rel = fwupd_release_new(); g_autoptr(GError) error_loop = NULL; /* create new FwupdRelease for the XbNode */ if (!fu_engine_set_release_from_appstream(self, request, device, rel, component, release, &error_loop)) { g_warning("failed to set release for component: %s", error_loop->message); continue; } /* fall back to quirk-provided value */ if (fwupd_release_get_install_duration(rel) == 0) fwupd_release_set_install_duration(rel, fu_device_get_install_duration(device)); /* invalid */ locations = fwupd_release_get_locations(rel); if (locations->len == 0) continue; checksums = fwupd_release_get_checksums(rel); if (checksums->len == 0) continue; /* different branch */ if (g_strcmp0(fwupd_release_get_branch(rel), fu_device_get_branch(device)) != 0) { if ((feature_flags & FWUPD_FEATURE_FLAG_SWITCH_BRANCH) == 0) { g_debug("client does not understand branches, skipping %s:%s", fwupd_release_get_branch(rel), fwupd_release_get_version(rel)); continue; } fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH); } /* test for upgrade or downgrade */ vercmp = fu_common_vercmp_full(fwupd_release_get_version(rel), fu_device_get_version(device), fmt); if (vercmp > 0) fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_IS_UPGRADE); else if (vercmp < 0) fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_IS_DOWNGRADE); /* lower than allowed to downgrade to */ if (fu_device_get_version_lowest(device) != NULL && fu_common_vercmp_full(fwupd_release_get_version(rel), fu_device_get_version_lowest(device), fmt) < 0) { fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_BLOCKED_VERSION); } /* manually blocked */ if (fu_engine_check_release_is_blocked(self, rel)) fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); /* check if remote is filtering firmware */ remote_id = fwupd_release_get_remote_id(rel); if (remote_id != NULL) { FwupdRemote *remote = fu_engine_get_remote_by_id(self, remote_id, NULL); if (remote != NULL && fwupd_remote_get_approval_required(remote) && !fu_engine_check_release_is_approved(self, rel)) { fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); } } /* add update message if exists but device doesn't already have one */ update_message = fwupd_release_get_update_message(rel); 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(rel); if (fwupd_device_get_update_image(FWUPD_DEVICE(device)) == NULL && update_image != NULL) { fwupd_device_set_update_image(FWUPD_DEVICE(device), update_image); } /* success */ g_ptr_array_add(releases, g_steal_pointer(&rel)); } /* 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; GPtrArray *releases; const gchar *version; g_autoptr(GError) error_all = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) branches = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GString) xpath = g_string_new(NULL); /* get device version */ 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_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; } /* get all the components that provide any of these GUIDs */ device_guids = fu_device_get_guids(device); for (guint i = 0; i < device_guids->len; i++) { const gchar *guid = g_ptr_array_index(device_guids, i); xb_string_append_union(xpath, "components/component[@type='firmware']/" "provides/firmware[@type=$'flashed'][text()=$'%s']/" "../..", guid); } components = xb_silo_query(self->silo, xpath->str, 0, &error_local); if (components == 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_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases found"); return NULL; } g_propagate_error(error, g_steal_pointer(&error_local)); return NULL; } /* find all the releases that pass all the requirements */ releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); 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)) { if (error_all == NULL) { error_all = g_steal_pointer(&error_tmp); continue; } /* assume the domain and code is the same */ g_prefix_error(&error_all, "%s, ", error_tmp->message); } } /* 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 GLIB_CHECK_VERSION(2, 54, 3) if (g_ptr_array_find_with_equal_func(branches, branch_tmp, g_str_equal, NULL)) continue; #endif 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) { if (error_all != NULL) { g_propagate_prefixed_error(error, g_steal_pointer(&error_all), "No releases found: "); return NULL; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases found"); return NULL; } return 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_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); return g_steal_pointer(&releases); } /** * 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_debug("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; } 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; /* 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_debug("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) { 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; } /* success */ return g_object_ref(FWUPD_DEVICE(device)); } static void fu_engine_plugins_setup(FuEngine *self) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); 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, &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_message("disabling plugin because: %s", error->message); } } } static void fu_engine_plugins_coldplug(FuEngine *self) { GPtrArray *plugins; g_autoptr(GString) str = g_string_new(NULL); /* exec */ plugins = fu_plugin_list_get_all(self->plugin_list); 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, &error)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); g_message("disabling plugin because: %s", error->message); } } /* 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_debug("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_debug("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(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 */ 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); 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); 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); } } 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_debug("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_debug("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; /* 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; /* 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_common_vercmp_full(fu_device_get_version(device), fwupd_release_get_version(release), fu_device_get_version_format(device)) != 0) { g_debug("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); } } } 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_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_debug("%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)); } /* 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) release = NULL; release = xb_node_query_first(component, "releases/release", NULL); if (release != NULL) { g_autoptr(FwupdRelease) rel = fwupd_release_new(); g_autoptr(GError) error_local = NULL; if (!fu_engine_set_release_from_appstream(self, NULL, device, rel, component, release, &error_local)) { g_warning("failed to set AppStream release: %s", error_local->message); } else { fu_device_add_release(device, rel); } } } } /* set or clear the SUPPORTED flag */ fu_engine_ensure_device_supported(self, device); /* fixup the name and format as needed */ fu_engine_md_refresh_device_from_component(self, device, component); /* 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_common_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); /* fix order */ fu_device_list_depsolve_order(self->device_list, device); /* fixup the name and format as needed from cached metadata */ if (component != NULL) fu_engine_md_refresh_device_from_component(self, device, component); /* match the metadata so clients can tell if the device is worthy */ fu_engine_ensure_device_supported(self, device); 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, tmp); } } static void fu_engine_plugin_config_changed_cb(FuPlugin *plugin, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); g_info("config file for %s changed, sending SHUTDOWN", fu_plugin_get_name(plugin)); fu_engine_set_status(self, FWUPD_STATUS_SHUTDOWN); } 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_debug("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_debug("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_debug("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) { if (fu_plugin_is_open(plugin)) { /* plugin does not match built version */ if (fu_plugin_get_build_hash(plugin) == NULL) { const gchar *name = fu_plugin_get_name(plugin); g_warning("%s should call fu_plugin_set_build_hash()", name); self->tainted = TRUE; } else if (g_strcmp0(fu_plugin_get_build_hash(plugin), FU_BUILD_HASH) != 0) { const gchar *name = fu_plugin_get_name(plugin); g_warning("%s has incorrect built version %s", name, fu_plugin_get_build_hash(plugin)); self->tainted = TRUE; } } fu_plugin_list_add(self->plugin_list, plugin); } static gboolean fu_engine_is_plugin_name_disabled(FuEngine *self, const gchar *name) { GPtrArray *disabled = fu_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 (fu_common_fnmatch(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); fu_common_string_replace(str, "-", "_"); 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_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; } gboolean fu_engine_get_tainted(FuEngine *self) { return self->tainted; } 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_config_get_host_bkc(self->config) == NULL) return ""; return fu_config_get_host_bkc(self->config); } #ifdef HAVE_HSI static void fu_engine_ensure_security_attrs_tainted(FuEngine *self) { gboolean disabled_plugins = FALSE; GPtrArray *disabled = fu_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_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") && g_strcmp0(name_tmp, "invalid") != 0) { disabled_plugins = TRUE; break; } } if (self->tainted) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_TAINTED); return; } if (self->plugin_filter->len > 0 || disabled_plugins) { 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); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED); } static gchar * fu_engine_attrs_calculate_hsi_for_chassis(FuEngine *self) { guint val; /* get chassis type from SMBIOS data */ val = fu_context_get_smbios_integer(self->ctx, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05); if (val == G_MAXUINT) return g_strdup("HSI-INVALID:chassis"); /* verify HSI makes sense for this chassis type */ switch (val) { 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; } /* failed */ return g_strdup_printf("HSI-INVALID:chassis[0x%02x]", val); } 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_debug("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; } #endif 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_all(self->device_list); g_autoptr(GPtrArray) items = NULL; g_autoptr(GError) error = NULL; /* already valid */ if (self->host_security_id != NULL) return; /* clear old values */ fu_security_attrs_remove_all(self->host_security_attrs); /* built in */ 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); } /* 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); } } /* set the obsoletes flag for each attr */ fu_security_attrs_depsolve(self->host_security_attrs); /* distil into one simple string */ g_free(self->host_security_id); self->host_security_id = fu_engine_attrs_calculate_hsi_for_chassis(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); fu_security_attrs_append_internal(events, attr); } } /* success */ return g_steal_pointer(&events); } gboolean fu_engine_load_plugins(FuEngine *self, GError **error) { const gchar *fn; g_autoptr(GDir) dir = NULL; g_autofree gchar *plugin_path = NULL; g_autofree gchar *suffix = g_strdup_printf(".%s", G_MODULE_SUFFIX); 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); /* search */ plugin_path = fu_common_get_path(FU_PATH_KIND_PLUGINDIR_PKG); dir = g_dir_open(plugin_path, 0, error); if (dir == NULL) return FALSE; while ((fn = g_dir_read_name(dir)) != NULL) { g_autofree gchar *filename = NULL; g_autofree gchar *name = NULL; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) firmware_gtypes = NULL; /* ignore non-plugins */ if (!g_str_has_suffix(fn, suffix)) continue; /* is disabled */ name = fu_plugin_guess_name_from_fn(fn); if (name == NULL) continue; if (fu_engine_is_plugin_name_disabled(self, name) || !fu_engine_is_plugin_name_enabled(self, name)) { g_ptr_array_add(plugins_disabled, g_steal_pointer(&name)); continue; } /* open module */ filename = g_build_filename(plugin_path, fn, NULL); plugin = fu_plugin_new(self->ctx); fu_plugin_set_name(plugin, name); /* if loaded from fu_engine_load() open the plugin */ firmware_gtypes = fu_context_get_firmware_gtype_ids(self->ctx); if (firmware_gtypes->len > 0) { if (!fu_plugin_open(plugin, filename, &error_local)) { g_warning("cannot load: %s", error_local->message); fu_engine_add_plugin(self, plugin); continue; } } /* runtime disabled */ if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) { g_ptr_array_add(plugins_disabled_rt, g_steal_pointer(&name)); 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); g_signal_connect(FU_PLUGIN(plugin), "config-changed", G_CALLBACK(fu_engine_plugin_config_changed_cb), self); /* add */ fu_engine_add_plugin(self, plugin); } /* 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_debug("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_debug("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; } guint64 fu_engine_get_archive_size_max(FuEngine *self) { return fu_config_get_archive_size_max(self->config); } static void fu_engine_backend_device_removed_cb(FuBackend *backend, FuDevice *device, FuEngine *self) { g_autoptr(GPtrArray) devices = NULL; /* debug */ if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { 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_all(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_debug("not auto-removing backend device %s [%s] due to flags", fu_device_get_name(device_tmp), fu_device_get_id(device_tmp)); continue; } g_debug("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); } } } static void fu_engine_backend_device_added_cb(FuBackend *backend, FuDevice *device, FuEngine *self) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) possible_plugins = NULL; /* super useful for plugin development */ if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device)); g_debug("%s added %s", fu_backend_get_name(backend), str); } /* add any extra quirks */ fu_device_set_context(device, self->ctx); if (!fu_device_probe(device, &error_local)) { g_warning("failed to probe device %s: %s", fu_device_get_backend_id(device), error_local->message); return; } /* super useful for plugin development */ if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device)); g_debug("%s added %s", fu_backend_get_name(backend), str); } /* can be specified using a quirk */ possible_plugins = fu_device_get_possible_plugins(device); for (guint i = 0; i < possible_plugins->len; i++) { FuPlugin *plugin; const gchar *plugin_name = g_ptr_array_index(possible_plugins, i); g_autoptr(GError) error = NULL; plugin = fu_plugin_list_find_by_name(self->plugin_list, plugin_name, NULL); if (plugin == NULL) continue; if (!fu_plugin_runner_backend_device_added(plugin, device, &error)) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { g_debug("%s ignoring: %s", fu_plugin_get_name(plugin), error->message); } continue; } g_warning("failed to add device %s: %s", fu_device_get_backend_id(device), error->message); continue; } } } 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 */ if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { 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_all(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)) 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)); } } /* 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)) { 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 udev device %s: %s", fu_plugin_get_name(plugin_tmp), fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(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_debug("no %s plugin for HwId %s: %s", plugins[i], hwid, error_local->message); continue; } g_debug("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_debug("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_debug("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_set_device_metadata(self->history, fu_device_get_id(dev_history), fwupd_release_get_metadata(rel_history), error)) { g_prefix_error(error, "failed to set metadata: "); return FALSE; } } /* the system is running with the new firmware version */ if (fu_common_vercmp_full(fu_device_get_version(dev), fwupd_release_get_version(rel_history), fu_device_get_version_format(dev)) == 0) { GPtrArray *checksums; g_debug("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(self->history, dev_history, error); } /* does the plugin know the update failure */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(dev), error); if (plugin == NULL) return FALSE; 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_debug("falling back to generic failure"); fu_device_set_update_state(dev_history, FWUPD_UPDATE_STATE_FAILED); fu_device_set_update_error(dev_history, "failed to run update on reboot"); } 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(self->history, dev_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)) { 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_debug("client certificate now exists: %s", error->message); return; } g_message("failed to sign using keyring: %s", error->message); return; } g_debug("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 minimum_battery = fu_common_strtoull(battery_str); if (minimum_battery > 100) { g_warning("invalid minimum battery level specified: " "%" G_GUINT64_FORMAT, minimum_battery); 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_common_get_path(path_kinds[i]); if (!fu_common_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; GFile *file; g_autoptr(GError) error_local = NULL; g_autofree gchar *base = fu_common_get_path(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 /** * fu_engine_load: * @self: a #FuEngine * @flags: engine load flags, e.g. %FU_ENGINE_LOAD_FLAG_READONLY * @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, GError **error) { FuQuirksLoadFlags quirks_flags = FU_QUIRKS_LOAD_FLAG_NONE; GPtrArray *guids; guint backend_cnt = 0; g_autoptr(GPtrArray) checksums_approved = NULL; g_autoptr(GPtrArray) checksums_blocked = NULL; g_autoptr(GError) error_quirks = NULL; g_autoptr(GError) error_local = 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; /* 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_debug("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(self->config, error)) { g_prefix_error(error, "Failed to load config: "); return FALSE; } /* read remotes */ if (flags & FU_ENGINE_LOAD_FLAG_REMOTES) { FuRemoteListLoadFlags remote_list_flags = FU_REMOTE_LIST_LOAD_FLAG_NONE; 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; if (!fu_remote_list_load(self->remote_list, remote_list_flags, error)) { g_prefix_error(error, "Failed to load remotes: "); return FALSE; } } /* create client certificate */ fu_engine_ensure_client_certificate(self); /* get hardcoded approved and blocked firmware */ checksums_approved = fu_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_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); } /* set up idle exit */ if ((self->app_flags & FU_APP_FLAGS_NO_IDLE_SOURCES) == 0) fu_idle_set_timeout(self->idle, fu_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); /* load SMBIOS and the hwids */ if (flags & FU_ENGINE_LOAD_FLAG_HWINFO) fu_context_load_hwinfo(self->ctx, NULL); /* set quirks for each hwid */ 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); } /* load AppStream metadata */ if (!fu_engine_load_metadata_store(self, flags, error)) { g_prefix_error(error, "Failed to load AppStream data: "); return FALSE; } /* 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, "dfu", FU_TYPE_DFU_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "dfuse", FU_TYPE_DFUSE_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, "archive", FU_TYPE_ARCHIVE_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "smbios", FU_TYPE_SMBIOS); fu_context_add_firmware_gtype(self->ctx, "efi-firmware-file", FU_TYPE_EFI_FIRMWARE_FILE); 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); /* 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, &error_backend)) { g_debug("failed to setup backend %s: %s", fu_backend_get_name(backend), error_backend->message); continue; } backend_cnt++; } if (backend_cnt == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "all backends failed setup"); return FALSE; } } /* delete old data files */ if (!fu_engine_cleanup_state(error)) { g_prefix_error(error, "Failed to clean up: "); return FALSE; } /* load plugin */ if (!fu_engine_load_plugins(self, error)) { g_prefix_error(error, "Failed to load plugins: "); return FALSE; } /* set up battery threshold */ 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_setup(self); fu_engine_plugins_coldplug(self); } /* coldplug 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_get_enabled(backend)) continue; 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); if (!fu_backend_coldplug(backend, &error_backend)) { g_warning("failed to coldplug backend %s: %s", fu_backend_get_name(backend), error_backend->message); continue; } } } /* update the db for devices that were updated during the reboot */ if (!fu_engine_update_history_database(self, error)) return FALSE; 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_class_init(FuEngineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_engine_finalize; /** * 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); } void fu_engine_add_app_flag(FuEngine *self, FuAppFlags app_flags) { g_return_if_fail(FU_IS_ENGINE(self)); self->app_flags |= app_flags; } static void fu_engine_context_battery_changed_cb(FuContext *ctx, GParamSpec *pspec, FuEngine *self) { g_autoptr(GPtrArray) devices = fu_device_list_get_all(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_battery_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); } } static void fu_engine_idle_status_notify_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self) { FwupdStatus status = fu_idle_get_status(idle); if (status == FWUPD_STATUS_SHUTDOWN) fu_engine_set_status(self, status); } static void fu_engine_init(FuEngine *self) { #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; self->percentage = 0; self->status = FWUPD_STATUS_IDLE; self->config = fu_config_new(); self->remote_list = fu_remote_list_new(); self->device_list = fu_device_list_new(); self->ctx = fu_context_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->runtime_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->compile_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); fu_context_set_runtime_versions(self->ctx, self->runtime_versions); fu_context_set_compile_versions(self->ctx, self->compile_versions); 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::battery-state", G_CALLBACK(fu_engine_context_battery_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::lid-state", G_CALLBACK(fu_engine_context_battery_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::battery-level", G_CALLBACK(fu_engine_context_battery_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::battery-threshold", G_CALLBACK(fu_engine_context_battery_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_IDLE(self->idle), "notify::status", G_CALLBACK(fu_engine_idle_status_notify_cb), self); /* backends */ #ifdef HAVE_GUSB g_ptr_array_add(self->backends, fu_usb_backend_new()); #endif #ifdef HAVE_GUDEV g_ptr_array_add(self->backends, fu_udev_backend_new(fu_context_get_udev_subsystems(self->ctx))); #endif #ifdef HAVE_BLUEZ g_ptr_array_add(self->backends, fu_bluez_backend_new()); #endif /* setup Jcat context */ self->jcat_context = jcat_context_new(); keyring_path = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); jcat_context_set_keyring_path(self->jcat_context, keyring_path); sysconfdir = fu_common_get_path(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); #if G_USB_CHECK_VERSION(0, 3, 1) fu_engine_add_runtime_version(self, "org.freedesktop.gusb", g_usb_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 g_hash_table_insert(self->compile_versions, g_strdup("org.freedesktop.fwupd"), g_strdup(VERSION)); #ifdef HAVE_GUSB g_hash_table_insert(self->compile_versions, g_strdup("org.freedesktop.gusb"), g_strdup_printf("%i.%i.%i", G_USB_MAJOR_VERSION, G_USB_MINOR_VERSION, G_USB_MICRO_VERSION)); #endif } static void fu_engine_finalize(GObject *obj) { FuEngine *self = FU_ENGINE(obj); 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->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); 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->runtime_versions); g_hash_table_unref(self->compile_versions); g_object_unref(self->plugin_list); G_OBJECT_CLASS(fu_engine_parent_class)->finalize(obj); } FuEngine * fu_engine_new(FuAppFlags app_flags) { FuEngine *self; self = g_object_new(FU_TYPE_ENGINE, NULL); self->app_flags = app_flags; return FU_ENGINE(self); } fwupd-1.7.5/src/fu-engine.h000066400000000000000000000163541420024370600154750ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "fwupd-device.h" #include "fwupd-enums.h" #include "fu-common.h" #include "fu-context.h" #include "fu-engine-request.h" #include "fu-install-task.h" #include "fu-plugin.h" #include "fu-security-attrs.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 * * 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, /*< private >*/ FU_ENGINE_LOAD_FLAG_LAST } FuEngineLoadFlags; FuEngine * fu_engine_new(FuAppFlags app_flags); void fu_engine_add_app_flag(FuEngine *self, FuAppFlags app_flags); void fu_engine_add_plugin_filter(FuEngine *self, const gchar *plugin_glob); void fu_engine_idle_reset(FuEngine *self); gboolean fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, GError **error); gboolean fu_engine_load_plugins(FuEngine *self, GError **error); gboolean fu_engine_get_tainted(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); const gchar * fu_engine_get_host_security_id(FuEngine *self); FwupdStatus fu_engine_get_status(FuEngine *self); XbSilo * fu_engine_get_silo_from_blob(FuEngine *self, GBytes *blob_cab, GError **error); guint64 fu_engine_get_archive_size_max(FuEngine *self); GPtrArray * fu_engine_get_plugins(FuEngine *self); 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); 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(FuEngine *self, FuInstallTask *task, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, FwupdFeatureFlags feature_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_tasks(FuEngine *self, FuEngineRequest *request, GPtrArray *install_tasks, 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); void fu_engine_add_blocked_firmware(FuEngine *self, const gchar *checksum); 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); FuContext * fu_engine_get_context(FuEngine *engine); void fu_engine_md_refresh_device_from_component(FuEngine *self, FuDevice *device, XbNode *component); 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_runtime_version(FuEngine *self, const gchar *component_id, const gchar *version); gboolean fu_engine_check_trust(FuEngine *self, FuInstallTask *task, GError **error); gboolean fu_engine_check_requirements(FuEngine *self, FuEngineRequest *request, FuInstallTask *task, FwupdInstallFlags flags, GError **error); void fu_engine_set_silo(FuEngine *self, XbSilo *silo); XbNode * fu_engine_get_component_by_guids(FuEngine *self, FuDevice *device); gboolean fu_engine_schedule_update(FuEngine *self, FuDevice *device, FwupdRelease *release, GBytes *blob_cab, FwupdInstallFlags flags, GError **error); fwupd-1.7.5/src/fu-history.c000066400000000000000000001163231420024370600157210ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHistory" #include "config.h" #include #include #include #include #include #ifdef HAVE_SQLITE #include #endif #include #include "fwupd-security-attr-private.h" #include "fu-common.h" #include "fu-device-private.h" #include "fu-history.h" #include "fu-mutex.h" #include "fu-security-attr.h" #define FU_HISTORY_CURRENT_SCHEMA_VERSION 7 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; FwupdRelease *release; /* create new result */ device = fu_device_new(); release = fu_device_get_release_default(device); /* 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_NO_QUIRKS); /* 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); 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);" "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_debug("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 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; } /* 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_debug("building initial database"); else if (schema_ver > 1) g_debug("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; 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_common_get_path(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, " "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)); return fu_history_stmt_exec(self, stmt, NULL, error); #else return TRUE; #endif } /** * fu_history_set_device_metadata: * @self: a #FuHistory * @device_id: a DeviceID string * @metadata: a #GHashTable of string:string * @error: (nullable): optional return location for an error * * Modify a device in the history database * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.5.0 **/ gboolean fu_history_set_device_metadata(FuHistory *self, const gchar *device_id, GHashTable *metadata, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autofree gchar *metadata_str = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(device_id != NULL, 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 %s", device_id); rc = sqlite3_prepare_v2(self->db, "UPDATE history SET " "metadata = ?1 " "WHERE device_id = ?2;", -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; } /* metadata is stored as a simple string */ metadata_str = _convert_hash_to_string(metadata); sqlite3_bind_text(stmt, 1, metadata_str, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, device_id, -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) " "VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10," "?11,?12,?13,?14,?15,?16)", -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); 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 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 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.7.5/src/fu-history.h000066400000000000000000000033041420024370600157200ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-device.h" #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_set_device_metadata(FuHistory *self, const gchar *device_id, GHashTable *metadata, 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.7.5/src/fu-idle.c000066400000000000000000000116051420024370600151320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIdle" #include "config.h" #include #include "fu-idle.h" #include "fu-mutex.h" static void fu_idle_finalize(GObject *obj); struct _FuIdle { GObject parent_instance; GPtrArray *items; /* of FuIdleItem */ GRWLock items_mutex; guint idle_id; guint timeout; FwupdStatus status; }; enum { PROP_0, PROP_STATUS, PROP_LAST }; static void fu_idle_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuIdle *self = FU_IDLE(object); switch (prop_id) { case PROP_STATUS: g_value_set_uint(value, self->status); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_idle_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } typedef struct { gchar *reason; guint32 token; } FuIdleItem; G_DEFINE_TYPE(FuIdle, fu_idle, G_TYPE_OBJECT) FwupdStatus fu_idle_get_status(FuIdle *self) { g_return_val_if_fail(FU_IS_IDLE(self), FWUPD_STATUS_UNKNOWN); return self->status; } static void fu_idle_set_status(FuIdle *self, FwupdStatus status) { if (self->status == status) return; self->status = status; g_debug("status now %s", fwupd_status_to_string(status)); g_object_notify(G_OBJECT(self), "status"); } static gboolean fu_idle_check_cb(gpointer user_data) { FuIdle *self = FU_IDLE(user_data); fu_idle_set_status(self, FWUPD_STATUS_SHUTDOWN); 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; } void fu_idle_reset(FuIdle *self) { g_return_if_fail(FU_IS_IDLE(self)); fu_idle_stop(self); if (self->items->len == 0) fu_idle_start(self); } void fu_idle_uninhibit(FuIdle *self, guint32 token) { g_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&self->items_mutex); g_return_if_fail(FU_IS_IDLE(self)); g_return_if_fail(token != 0); g_return_if_fail(locker != NULL); for (guint i = 0; i < self->items->len; i++) { FuIdleItem *item = g_ptr_array_index(self->items, i); if (item->token == token) { g_debug("uninhibiting: %s", item->reason); g_ptr_array_remove_index(self->items, i); break; } } fu_idle_reset(self); } guint32 fu_idle_inhibit(FuIdle *self, const gchar *reason) { FuIdleItem *item; g_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&self->items_mutex); g_return_val_if_fail(FU_IS_IDLE(self), 0); g_return_val_if_fail(reason != NULL, 0); g_return_val_if_fail(locker != NULL, 0); g_debug("inhibiting: %s", reason); item = g_new0(FuIdleItem, 1); item->reason = g_strdup(reason); item->token = g_random_int_range(1, G_MAXINT); g_ptr_array_add(self->items, item); fu_idle_reset(self); return item->token; } 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, const gchar *reason) { FuIdleLocker *locker = g_new0(FuIdleLocker, 1); locker->idle = g_object_ref(self); locker->token = fu_idle_inhibit(self, 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); GParamSpec *pspec; object_class->finalize = fu_idle_finalize; object_class->get_property = fu_idle_get_property; object_class->set_property = fu_idle_set_property; /** * FuIdle:status: * * The status of the idle monitor. */ pspec = g_param_spec_uint("status", NULL, NULL, FWUPD_STATUS_UNKNOWN, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_STATUS, pspec); } static void fu_idle_init(FuIdle *self) { self->status = FWUPD_STATUS_IDLE; self->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_idle_item_free); g_rw_lock_init(&self->items_mutex); } static void fu_idle_finalize(GObject *obj) { FuIdle *self = FU_IDLE(obj); fu_idle_stop(self); g_rw_lock_clear(&self->items_mutex); 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.7.5/src/fu-idle.h000066400000000000000000000016731420024370600151430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-device.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, const gchar *reason); void fu_idle_uninhibit(FuIdle *self, guint32 token); void fu_idle_set_timeout(FuIdle *self, guint timeout); void fu_idle_reset(FuIdle *self); FwupdStatus fu_idle_get_status(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, const gchar *reason); void fu_idle_locker_free(FuIdleLocker *locker); G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIdleLocker, fu_idle_locker_free) fwupd-1.7.5/src/fu-install-task.c000066400000000000000000000373351420024370600166330ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuInstallTask" #include "config.h" #include #include "fu-common-version.h" #include "fu-common.h" #include "fu-device-private.h" #include "fu-install-task.h" #include "fu-keyring-utils.h" struct _FuInstallTask { GObject parent_instance; FuDevice *device; XbNode *component; FwupdReleaseFlags trust_flags; gboolean is_downgrade; }; G_DEFINE_TYPE(FuInstallTask, fu_install_task, G_TYPE_OBJECT) /** * fu_install_task_get_device: * @self: a #FuInstallTask * * Gets the device for this task. * * Returns: (transfer none): the device **/ FuDevice * fu_install_task_get_device(FuInstallTask *self) { g_return_val_if_fail(FU_IS_INSTALL_TASK(self), NULL); return self->device; } /** * fu_install_task_get_component: * @self: a #FuInstallTask * * Gets the component for this task. * * Returns: (transfer none): the component **/ XbNode * fu_install_task_get_component(FuInstallTask *self) { g_return_val_if_fail(FU_IS_INSTALL_TASK(self), NULL); return self->component; } /** * fu_install_task_get_trust_flags: * @self: a #FuInstallTask * * Gets the trust flags for this task. * * NOTE: This is only set after fu_install_task_check_requirements() has been * called successfully. * * Returns: the #FwupdReleaseFlags, e.g. #FWUPD_TRUST_FLAG_PAYLOAD **/ FwupdReleaseFlags fu_install_task_get_trust_flags(FuInstallTask *self) { g_return_val_if_fail(FU_IS_INSTALL_TASK(self), FALSE); return self->trust_flags; } /** * fu_install_task_get_is_downgrade: * @self: a #FuInstallTask * * Gets if this task is to downgrade firmware. * * NOTE: This is only set after fu_install_task_check_requirements() has been * called successfully. * * Returns: %TRUE if versions numbers are going backwards **/ gboolean fu_install_task_get_is_downgrade(FuInstallTask *self) { g_return_val_if_fail(FU_IS_INSTALL_TASK(self), FALSE); return self->is_downgrade; } static gchar * fu_install_task_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_install_task_check_verfmt(FuInstallTask *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_install_task_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_install_task_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; } static gboolean fu_install_task_check_requirements_version_check(FuInstallTask *self, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) reqs = NULL; reqs = xb_node_query(fu_install_task_get_component(self), "requires/*", 0, &error_local); if (reqs == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, error_local->message); return FALSE; } for (guint i = 0; i < reqs->len; i++) { XbNode *req = g_ptr_array_index(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; } /** * fu_install_task_check_requirements: * @self: a #FuInstallTask * @flags: install flags, e.g. #FWUPD_INSTALL_FLAG_ALLOW_OLDER * @error: (nullable): optional return location for an error * * Checks any requirements of this task. 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 requirements passed **/ gboolean fu_install_task_check_requirements(FuInstallTask *self, FwupdInstallFlags flags, GError **error) { const gchar *branch_new; const gchar *branch_old; const gchar *protocol; const gchar *version; const gchar *version_release_raw; const gchar *version_lowest; gboolean matches_guid = FALSE; gint vercmp; g_autofree gchar *version_release = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) provides = NULL; g_autoptr(GPtrArray) verfmts = NULL; g_autoptr(XbNode) release = NULL; #if LIBXMLB_CHECK_VERSION(0, 2, 0) g_autoptr(XbQuery) query = NULL; #endif g_return_val_if_fail(FU_IS_INSTALL_TASK(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* does this component provide a GUID the device has */ provides = xb_node_query(self->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_install_task_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(self->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) && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_autofree gchar *str = NULL; str = fu_common_strjoin_array("|", 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(self->component, "branch", NULL); branch_old = fu_device_get_branch(self->device); if ((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_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 ((flags & FWUPD_INSTALL_FLAG_OFFLINE) == 0 && (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; } /* get latest release */ #if LIBXMLB_CHECK_VERSION(0, 2, 0) query = xb_query_new_full(xb_node_get_silo(self->component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; release = xb_node_query_first_full(self->component, query, NULL); #else release = xb_node_query_first(self->component, "releases/release", NULL); #endif if (release == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "%s [%s] has no firmware update metadata", fu_device_get_name(self->device), fu_device_get_id(self->device)); return FALSE; } /* is this a downgrade or re-install */ version_release_raw = xb_node_get_attr(release, "version"); if (version_release_raw == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Release has no firmware version"); return FALSE; } /* check the version formats match if set in the release */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && (flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0) { verfmts = xb_node_query(self->component, "custom/value[@key='LVFS::VersionFormat']", 0, NULL); if (verfmts != NULL) { if (!fu_install_task_check_verfmt(self, verfmts, 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_common_vercmp_full(version_lowest, version, fu_device_get_version_format(self->device)) > 0 && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, "Specified firmware is older than the minimum " "required version '%s < %s'", version, version_lowest); return FALSE; } /* check semver */ if (fu_device_get_version_format(self->device) == FWUPD_VERSION_FORMAT_PLAIN) { version_release = g_strdup(version_release_raw); } else { version_release = fu_common_version_parse_from_format(version_release_raw, fu_device_get_version_format(self->device)); } vercmp = fu_common_vercmp_full(version, version_release, 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 && (flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_VERSION_SAME, "Specified firmware is already installed '%s'", version_release); return FALSE; } self->is_downgrade = vercmp > 0; if (self->is_downgrade && (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0 && (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'", version_release, version); return FALSE; } /* verify */ if (!fu_keyring_get_release_flags(release, &self->trust_flags, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("Ignoring verification for %s: %s", fu_device_get_name(self->device), error_local->message); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } return TRUE; } /** * fu_install_task_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_install_task_get_action_id(FuInstallTask *self) { /* relax authentication checks for removable devices */ if (!fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_INTERNAL)) { if (self->is_downgrade) { if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.downgrade-hotplug-trusted"; return "org.freedesktop.fwupd.downgrade-hotplug"; } if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.update-hotplug-trusted"; return "org.freedesktop.fwupd.update-hotplug"; } /* internal device */ if (self->is_downgrade) { if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.downgrade-internal-trusted"; return "org.freedesktop.fwupd.downgrade-internal"; } if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) return "org.freedesktop.fwupd.update-internal-trusted"; return "org.freedesktop.fwupd.update-internal"; } static void fu_install_task_init(FuInstallTask *self) { self->trust_flags = FWUPD_TRUST_FLAG_NONE; } static void fu_install_task_finalize(GObject *object) { FuInstallTask *self = FU_INSTALL_TASK(object); if (self->component != NULL) g_object_unref(self->component); if (self->device != NULL) g_object_unref(self->device); G_OBJECT_CLASS(fu_install_task_parent_class)->finalize(object); } static void fu_install_task_class_init(FuInstallTaskClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_install_task_finalize; } /** * fu_install_task_compare: * @task1: first task to compare. * @task2: second task to compare. * * Compares two install tasks. * * Returns: 1, 0 or -1 if @task1 is greater, equal, or less than @task2, respectively. **/ gint fu_install_task_compare(FuInstallTask *task1, FuInstallTask *task2) { FuDevice *device1 = fu_install_task_get_device(task1); FuDevice *device2 = fu_install_task_get_device(task2); 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; } /** * fu_install_task_new: * @device: a device * @component: a Xmlb node * * Creates a new install task that may or may not be valid. * * Returns: (transfer full): the #FuInstallTask **/ FuInstallTask * fu_install_task_new(FuDevice *device, XbNode *component) { FuInstallTask *self; self = g_object_new(FU_TYPE_TASK, NULL); if (component != NULL) self->component = g_object_ref(component); if (device != NULL) self->device = g_object_ref(device); return FU_INSTALL_TASK(self); } fwupd-1.7.5/src/fu-install-task.h000066400000000000000000000016031420024370600166250ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-device.h" #define FU_TYPE_TASK (fu_install_task_get_type()) G_DECLARE_FINAL_TYPE(FuInstallTask, fu_install_task, FU, INSTALL_TASK, GObject) FuInstallTask * fu_install_task_new(FuDevice *device, XbNode *component); FuDevice * fu_install_task_get_device(FuInstallTask *self); XbNode * fu_install_task_get_component(FuInstallTask *self); FwupdReleaseFlags fu_install_task_get_trust_flags(FuInstallTask *self); gboolean fu_install_task_get_is_downgrade(FuInstallTask *self); gboolean fu_install_task_check_requirements(FuInstallTask *self, FwupdInstallFlags flags, GError **error); const gchar * fu_install_task_get_action_id(FuInstallTask *self); gint fu_install_task_compare(FuInstallTask *task1, FuInstallTask *task2); fwupd-1.7.5/src/fu-keyring-utils.c000066400000000000000000000021121420024370600170140ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuKeyring" #include "fu-keyring-utils.h" #include #include #include "fwupd-error.h" /** * fu_keyring_get_release_flags: * @release: the reelase node * @flags: (out): flags for the release, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * @error: (nullable): optional return location for an error * * Uses the correct keyring to get the trust flags for a given release. * * Returns: %TRUE if @flags has been set **/ gboolean fu_keyring_get_release_flags(XbNode *release, FwupdReleaseFlags *flags, GError **error) { GBytes *blob; blob = g_object_get_data(G_OBJECT(release), "fwupd::ReleaseFlags"); if (blob == NULL) return TRUE; if (g_bytes_get_size(blob) != sizeof(FwupdReleaseFlags)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid fwupd::ReleaseFlags set by loader"); return FALSE; } memcpy(flags, g_bytes_get_data(blob, NULL), sizeof(FwupdReleaseFlags)); return TRUE; } fwupd-1.7.5/src/fu-keyring-utils.h000066400000000000000000000004331420024370600170250ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-enums.h" gboolean fu_keyring_get_release_flags(XbNode *release, FwupdReleaseFlags *flags, GError **error); fwupd-1.7.5/src/fu-main.c000066400000000000000000002141741420024370600151470ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_MALLOC_H #include #endif #ifdef HAVE_POLKIT #include #endif #ifdef HAVE_SYSTEMD #include #endif #include #include #include #include "fwupd-device-private.h" #include "fwupd-plugin-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-request-private.h" #include "fwupd-resources.h" #include "fwupd-security-attr-private.h" #include "fu-common.h" #include "fu-debug.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-install-task.h" #include "fu-security-attrs-private.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 */ typedef enum { FU_MAIN_MACHINE_KIND_PHYSICAL, FU_MAIN_MACHINE_KIND_VIRTUAL, FU_MAIN_MACHINE_KIND_CONTAINER, } FuMainMachineKind; typedef struct { FwupdFeatureFlags feature_flags; GHashTable *hints; /* str:str */ } FuSenderItem; typedef struct { GDBusConnection *connection; GDBusNodeInfo *introspection_daemon; GDBusProxy *proxy_uid; GMainLoop *loop; GFileMonitor *argv0_monitor; GHashTable *sender_items; /* sender:FuSenderItem */ #if GLIB_CHECK_VERSION(2, 63, 3) GMemoryMonitor *memory_monitor; #endif #ifdef HAVE_POLKIT PolkitAuthority *authority; #endif guint owner_id; guint process_quit_id; FuEngine *engine; gboolean update_in_progress; gboolean pending_sigterm; FuMainMachineKind machine_kind; } FuMainPrivate; static gboolean fu_main_sigterm_cb(gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; if (!priv->update_in_progress) { g_main_loop_quit(priv->loop); return G_SOURCE_REMOVE; } g_warning("Received SIGTERM during a firmware update, ignoring"); priv->pending_sigterm = TRUE; return G_SOURCE_CONTINUE; } static void fu_main_engine_changed_cb(FuEngine *engine, FuMainPrivate *priv) { /* not yet connected */ if (priv->connection == NULL) return; g_dbus_connection_emit_signal(priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "Changed", NULL, NULL); } static void fu_main_engine_device_added_cb(FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceAdded", g_variant_new_tuple(&val, 1), NULL); } static void fu_main_engine_device_removed_cb(FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceRemoved", g_variant_new_tuple(&val, 1), NULL); } static void fu_main_engine_device_changed_cb(FuEngine *engine, FuDevice *device, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceChanged", g_variant_new_tuple(&val, 1), NULL); } static void fu_main_engine_device_request_cb(FuEngine *engine, FwupdRequest *request, FuMainPrivate *priv) { GVariant *val; /* not yet connected */ if (priv->connection == NULL) return; val = fwupd_request_to_variant(FWUPD_REQUEST(request)); g_dbus_connection_emit_signal(priv->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceRequest", g_variant_new_tuple(&val, 1), NULL); } static void fu_main_emit_property_changed(FuMainPrivate *priv, const gchar *property_name, GVariant *property_value) { GVariantBuilder builder; GVariantBuilder invalidated_builder; /* not yet connected */ if (priv->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( priv->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_main_set_status(FuMainPrivate *priv, FwupdStatus status) { g_debug("Emitting PropertyChanged('Status'='%s')", fwupd_status_to_string(status)); fu_main_emit_property_changed(priv, "Status", g_variant_new_uint32(status)); } static void fu_main_engine_status_changed_cb(FuEngine *engine, FwupdStatus status, FuMainPrivate *priv) { fu_main_set_status(priv, status); /* engine has gone idle */ if (status == FWUPD_STATUS_SHUTDOWN) g_main_loop_quit(priv->loop); } static FuEngineRequest * fu_main_create_request(FuMainPrivate *priv, const gchar *sender, GError **error) { FuSenderItem *sender_item; FwupdDeviceFlags device_flags = FWUPD_DEVICE_FLAG_NONE; uid_t calling_uid = 0; g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); 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); } g_return_val_if_fail(sender != NULL, NULL); /* did the client set the list of supported features or any hints */ sender_item = g_hash_table_lookup(priv->sender_items, sender); if (sender_item != NULL) { const gchar *locale = g_hash_table_lookup(sender_item->hints, "locale"); if (locale != NULL) fu_engine_request_set_locale(request, locale); fu_engine_request_set_feature_flags(request, sender_item->feature_flags); } /* are we root and therefore trusted? */ value = g_dbus_proxy_call_sync(priv->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 (calling_uid == 0) device_flags |= FWUPD_DEVICE_FLAG_TRUSTED; fu_engine_request_set_device_flags(request, device_flags); /* success */ return g_steal_pointer(&request); } static GVariant * fu_main_device_array_to_variant(FuMainPrivate *priv, FuEngineRequest *request, GPtrArray *devices, GError **error) { GVariantBuilder builder; g_return_val_if_fail(devices->len > 0, NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 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), fu_engine_request_get_device_flags(request)); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } static GVariant * fu_main_plugin_array_to_variant(GPtrArray *plugins) { GVariantBuilder builder; g_return_val_if_fail(plugins->len > 0, NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 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_main_release_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++) { 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_main_remote_array_to_variant(GPtrArray *remotes) { GVariantBuilder builder; g_return_val_if_fail(remotes->len > 0, NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 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); } static GVariant * fu_main_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); } typedef struct { GDBusMethodInvocation *invocation; FuEngineRequest *request; #ifdef HAVE_POLKIT PolkitSubject *subject; #endif GPtrArray *install_tasks; GPtrArray *action_ids; GPtrArray *checksums; guint64 flags; GBytes *blob_cab; FuMainPrivate *priv; gchar *device_id; gchar *remote_id; gchar *key; gchar *value; XbSilo *silo; } FuMainAuthHelper; static void fu_main_auth_helper_free(FuMainAuthHelper *helper) { if (helper->blob_cab != NULL) g_bytes_unref(helper->blob_cab); #ifdef HAVE_POLKIT if (helper->subject != NULL) g_object_unref(helper->subject); #endif if (helper->silo != NULL) g_object_unref(helper->silo); if (helper->request != NULL) g_object_unref(helper->request); if (helper->install_tasks != NULL) g_ptr_array_unref(helper->install_tasks); if (helper->action_ids != NULL) g_ptr_array_unref(helper->action_ids); if (helper->checksums != NULL) g_ptr_array_unref(helper->checksums); g_free(helper->device_id); g_free(helper->remote_id); g_free(helper->key); g_free(helper->value); g_object_unref(helper->invocation); g_free(helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainAuthHelper, fu_main_auth_helper_free) #pragma clang diagnostic pop #ifdef HAVE_POLKIT /* error may or may not already have been set */ static gboolean fu_main_authorization_is_valid(PolkitAuthorizationResult *auth, GError **error) { /* failed */ if (auth == NULL) { g_autofree gchar *message = g_strdup((*error)->message); g_clear_error(error); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Could not check for auth: %s", message); return FALSE; } /* did not auth */ if (!polkit_authorization_result_get_is_authorized(auth)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Failed to obtain auth"); return FALSE; } /* success */ return TRUE; } #else static gboolean fu_main_authorization_is_trusted(FuEngineRequest *request, GError **error) { FwupdDeviceFlags flags = fu_engine_request_get_device_flags(request); if ((flags & FWUPD_DEVICE_FLAG_TRUSTED) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "permission denied: untrusted client process"); return FALSE; } return TRUE; } #endif /* HAVE_POLKIT */ static void fu_main_authorize_unlock_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* authenticated */ if (!fu_engine_unlock(helper->priv->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_main_authorize_set_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* 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->priv->engine, csum); } g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_main_authorize_set_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* success */ if (!fu_engine_set_blocked_firmware(helper->priv->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_main_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; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* authenticated */ sig = fu_engine_self_sign(helper->priv->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_main_modify_config_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ if (!fu_engine_modify_config(helper->priv->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_main_progress_percentage_changed_cb(FuProgress *progress, guint percentage, FuMainPrivate *priv) { g_debug("Emitting PropertyChanged('Percentage'='%u%%')", percentage); fu_main_emit_property_changed(priv, "Percentage", g_variant_new_uint32(percentage)); } static void fu_main_progress_status_changed_cb(FuProgress *progress, FwupdStatus status, FuMainPrivate *priv) { fu_main_set_status(priv, status); } static void fu_main_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); #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* progress */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_main_progress_percentage_changed_cb), helper->priv); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_main_progress_status_changed_cb), helper->priv); /* authenticated */ if (!fu_engine_activate(helper->priv->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_main_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); #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* progress */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_main_progress_percentage_changed_cb), helper->priv); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_main_progress_status_changed_cb), helper->priv); /* authenticated */ if (!fu_engine_verify_update(helper->priv->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_main_authorize_modify_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #else if (!fu_main_authorization_is_trusted(helper->request, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } #endif /* HAVE_POLKIT */ /* authenticated */ if (!fu_engine_modify_remote(helper->priv->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); } static void fu_main_authorize_install_queue(FuMainAuthHelper *helper); #ifdef HAVE_POLKIT static void fu_main_authorize_install_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; g_autoptr(PolkitAuthorizationResult) auth = NULL; /* get result */ fu_main_set_status(helper->priv, FWUPD_STATUS_IDLE); auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); if (!fu_main_authorization_is_valid(auth, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* do the next authentication action ID */ fu_main_authorize_install_queue(g_steal_pointer(&helper)); } #endif /* HAVE_POLKIT */ static void fu_main_authorize_install_queue(FuMainAuthHelper *helper_ref) { FuMainPrivate *priv = helper_ref->priv; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); gboolean ret; #ifdef HAVE_POLKIT /* still more things to to authenticate */ if (helper->action_ids->len > 0) { g_autofree gchar *action_id = g_strdup(g_ptr_array_index(helper->action_ids, 0)); g_autoptr(PolkitSubject) subject = g_object_ref(helper->subject); g_ptr_array_remove_index(helper->action_ids, 0); polkit_authority_check_authorization( priv->authority, subject, action_id, NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_install_cb, g_steal_pointer(&helper)); return; } #endif /* HAVE_POLKIT */ /* all authenticated, so install all the things */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_main_progress_percentage_changed_cb), helper->priv); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_main_progress_status_changed_cb), helper->priv); /* all authenticated, so install all the things */ priv->update_in_progress = TRUE; ret = fu_engine_install_tasks(helper->priv->engine, helper->request, helper->install_tasks, helper->blob_cab, progress, helper->flags, &error); priv->update_in_progress = FALSE; if (priv->pending_sigterm) g_main_loop_quit(priv->loop); if (!ret) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } #if !GLIB_CHECK_VERSION(2, 54, 0) static gboolean g_ptr_array_find(GPtrArray *haystack, gconstpointer needle, guint *index_) { for (guint i = 0; i < haystack->len; i++) { gconstpointer *tmp = g_ptr_array_index(haystack, i); if (tmp == needle) { if (index_ != NULL) { *index_ = i; return TRUE; } } } return FALSE; } #endif static gint fu_main_install_task_sort_cb(gconstpointer a, gconstpointer b) { FuInstallTask *task_a = *((FuInstallTask **)a); FuInstallTask *task_b = *((FuInstallTask **)b); return fu_install_task_compare(task_a, task_b); } static gboolean fu_main_install_with_helper(FuMainAuthHelper *helper_ref, GError **error) { FuMainPrivate *priv = helper_ref->priv; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices_possible = NULL; g_autoptr(GPtrArray) errors = 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(priv->engine, error); if (devices_possible == NULL) return FALSE; } else { g_autoptr(FuDevice) device = NULL; device = fu_engine_get_device(priv->engine, helper->device_id, error); if (device == NULL) 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; } /* parse silo */ helper->silo = fu_engine_get_silo_from_blob(priv->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->install_tasks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); errors = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free); 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); const gchar *action_id; g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new(device, component); if (!fu_engine_check_requirements(priv->engine, helper->request, task, 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(errors, g_steal_pointer(&error_local)); continue; } /* make a second pass using possibly updated version format now */ fu_engine_md_refresh_device_from_component(priv->engine, device, component); if (!fu_engine_check_requirements(priv->engine, helper->request, task, 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(errors, g_steal_pointer(&error_local)); continue; } if (!fu_engine_check_trust(priv->engine, task, &error_local)) { 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); /* get the action IDs for the valid device */ action_id = fu_install_task_get_action_id(task); 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->install_tasks, g_steal_pointer(&task)); } } /* order the install tasks by the device priority */ g_ptr_array_sort(helper->install_tasks, fu_main_install_task_sort_cb); /* nothing suitable */ if (helper->install_tasks->len == 0) { GError *error_tmp = fu_common_error_array_get_best(errors); g_propagate_error(error, error_tmp); return FALSE; } /* authenticate all things in the action_ids */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); fu_main_authorize_install_queue(g_steal_pointer(&helper)); return TRUE; } static FuSenderItem * fu_main_ensure_sender_item(FuMainPrivate *priv, const gchar *sender) { FuSenderItem *sender_item = NULL; /* operating in point-to-point mode */ if (sender == NULL) sender = ""; sender_item = g_hash_table_lookup(priv->sender_items, sender); if (sender_item == NULL) { sender_item = g_new0(FuSenderItem, 1); sender_item->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(priv->sender_items, g_strdup(sender), sender_item); } return sender_item; } static gboolean fu_main_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_main_schedule_process_quit_cb(gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; g_debug("daemon asked to quit, shutting down"); priv->process_quit_id = 0; g_main_loop_quit(priv->loop); return G_SOURCE_REMOVE; } static void fu_main_schedule_process_quit(FuMainPrivate *priv) { /* busy? */ if (priv->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 (priv->process_quit_id != 0) g_source_remove(priv->process_quit_id); priv->process_quit_id = g_idle_add(fu_main_schedule_process_quit_cb, priv); } static void fu_main_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) { FuMainPrivate *priv = (FuMainPrivate *)user_data; GVariant *val = NULL; g_autoptr(FuEngineRequest) request = NULL; g_autoptr(GError) error = NULL; /* build request */ request = fu_main_create_request(priv, sender, &error); if (request == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* activity */ fu_engine_idle_reset(priv->engine); if (g_strcmp0(method_name, "GetDevices") == 0) { g_autoptr(GPtrArray) devices = NULL; g_debug("Called %s()", method_name); devices = fu_engine_get_devices(priv->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_device_array_to_variant(priv, 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_main_plugin_array_to_variant(fu_engine_get_plugins(priv->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_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_releases(priv->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_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(priv->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(priv->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(priv->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; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif /* HAVE_POLKIT */ g_variant_get(parameters, "(^as)", &checksums); checksums_str = g_strjoinv(",", checksums); g_debug("Called %s(%s)", method_name, checksums_str); /* authenticate */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->priv = priv; 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])); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.set-approved-firmware", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_set_approved_firmware_cb, g_steal_pointer(&helper)); #else fu_main_authorize_set_approved_firmware_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ return; } if (g_strcmp0(method_name, "SetBlockedFirmware") == 0) { g_autofree gchar *checksums_str = NULL; g_auto(GStrv) checksums = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif g_variant_get(parameters, "(^as)", &checksums); checksums_str = g_strjoinv(",", checksums); g_debug("Called %s(%s)", method_name, checksums_str); /* authenticate */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->priv = priv; 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])); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.set-approved-firmware", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_set_blocked_firmware_cb, g_steal_pointer(&helper)); #else fu_main_authorize_set_blocked_firmware_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ 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_main_schedule_process_quit(priv); 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; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif 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_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper->priv = priv; helper->value = g_steal_pointer(&value); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.self-sign", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_self_sign_cb, g_steal_pointer(&helper)); #else fu_main_authorize_self_sign_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ 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_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_downgrades(priv->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_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_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_upgrades(priv->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_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(priv->engine, &error); if (remotes == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_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(priv->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_device_array_to_variant(priv, 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) { g_autoptr(FuSecurityAttrs) attrs = NULL; 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 (priv->machine_kind != FU_MAIN_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(priv->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; g_autoptr(FuSecurityAttrs) attrs = NULL; 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(priv->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(priv->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, "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(priv->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_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } result = fu_engine_get_results(priv->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) { 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(priv->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); return; } if (g_strcmp0(method_name, "Unlock") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif /* HAVE_POLKIT */ g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* authenticate */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->priv = priv; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.device-unlock", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_unlock_cb, g_steal_pointer(&helper)); #else fu_main_authorize_unlock_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ return; } if (g_strcmp0(method_name, "Activate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_main_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* authenticate */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->priv = priv; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.device-activate", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_activate_cb, g_steal_pointer(&helper)); #else fu_main_authorize_activate_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ return; } if (g_strcmp0(method_name, "ModifyConfig") == 0) { g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif g_variant_get(parameters, "(ss)", &key, &value); g_debug("Called %s(%s=%s)", method_name, key, value); /* authenticate */ helper = g_new0(FuMainAuthHelper, 1); helper->priv = priv; helper->key = g_steal_pointer(&key); helper->value = g_steal_pointer(&value); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.modify-config", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_modify_config_cb, g_steal_pointer(&helper)); #else fu_main_modify_config_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ 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; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif /* 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->priv = priv; /* authenticate */ fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); #ifdef HAVE_POLKIT subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.modify-remote", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_modify_remote_cb, g_steal_pointer(&helper)); #else fu_main_authorize_modify_remote_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ return; } if (g_strcmp0(method_name, "VerifyUpdate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; #ifdef HAVE_POLKIT g_autoptr(PolkitSubject) subject = NULL; #endif /* check the id exists */ g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_main_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->priv = priv; /* authenticate */ #ifdef HAVE_POLKIT fu_main_set_status(priv, FWUPD_STATUS_WAITING_FOR_AUTH); subject = polkit_system_bus_name_new(sender); polkit_authority_check_authorization( priv->authority, subject, "org.freedesktop.fwupd.verify-update", NULL, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, NULL, fu_main_authorize_verify_update_cb, g_steal_pointer(&helper)); #else fu_main_authorize_verify_update_cb(NULL, NULL, g_steal_pointer(&helper)); #endif /* HAVE_POLKIT */ 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_main_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_main_progress_percentage_changed_cb), priv); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_main_progress_status_changed_cb), priv); if (!fu_engine_verify(priv->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) { FuSenderItem *sender_item; guint64 feature_flags_u64 = 0; 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 */ sender_item = fu_main_ensure_sender_item(priv, sender); sender_item->feature_flags = feature_flags_u64; g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "SetHints") == 0) { FuSenderItem *sender_item; const gchar *prop_key; const gchar *prop_value; g_autoptr(GVariantIter) iter = NULL; g_variant_get(parameters, "(a{ss})", &iter); g_debug("Called %s()", method_name); sender_item = fu_main_ensure_sender_item(priv, sender); while (g_variant_iter_next(iter, "{&s&s}", &prop_key, &prop_value)) { g_debug("got hint %s=%s", prop_key, prop_value); g_hash_table_insert(sender_item->hints, g_strdup(prop_key), g_strdup(prop_value)); } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "Install") == 0) { 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_main_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->priv = priv; /* 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_get_archive_size_max(priv->engine); helper->blob_cab = fu_common_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 */ #ifdef HAVE_POLKIT helper->subject = polkit_system_bus_name_new(sender); #endif /* HAVE_POLKIT */ if (!fu_main_install_with_helper(g_steal_pointer(&helper), &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* async return */ return; } if (g_strcmp0(method_name, "GetDetails") == 0) { 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(priv->engine, request, fd, &error); if (results == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_main_result_array_to_variant(results); g_dbus_method_invocation_return_value(invocation, val); 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_main_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) { FuMainPrivate *priv = (FuMainPrivate *)user_data; /* activity */ fu_engine_idle_reset(priv->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(priv->engine)); if (g_strcmp0(property_name, "Tainted") == 0) return g_variant_new_boolean(fu_engine_get_tainted(priv->engine)); if (g_strcmp0(property_name, "Status") == 0) return g_variant_new_uint32(fu_engine_get_status(priv->engine)); if (g_strcmp0(property_name, "HostProduct") == 0) return g_variant_new_string(fu_engine_get_host_product(priv->engine)); if (g_strcmp0(property_name, "HostMachineId") == 0) { const gchar *tmp = fu_engine_get_host_machine_id(priv->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(priv->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); /* 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_main_register_object(FuMainPrivate *priv) { guint registration_id; static const GDBusInterfaceVTable interface_vtable = {fu_main_daemon_method_call, fu_main_daemon_get_property, NULL}; registration_id = g_dbus_connection_register_object(priv->connection, FWUPD_DBUS_PATH, priv->introspection_daemon->interfaces[0], &interface_vtable, priv, /* user_data */ NULL, /* user_data_free_func */ NULL); /* GError** */ g_assert(registration_id > 0); } static void fu_main_dbus_bus_acquired_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; g_autoptr(GError) error = NULL; priv->connection = g_object_ref(connection); fu_main_register_object(priv); /* connect to D-Bus directly */ priv->proxy_uid = g_dbus_proxy_new_sync(priv->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 (priv->proxy_uid == NULL) { g_warning("cannot connect to DBus: %s", error->message); return; } } static void fu_main_dbus_name_acquired_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { g_debug("acquired name: %s", name); } static void fu_main_dbus_name_lost_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; if (priv->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(priv->loop); } static void fu_main_dbus_connection_closed_cb(GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; g_debug("client connection closed: %s", error != NULL ? error->message : "unknown"); g_clear_object(&priv->connection); } static gboolean fu_main_dbus_new_connection_cb(GDBusServer *server, GDBusConnection *connection, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; g_set_object(&priv->connection, connection); g_signal_connect(connection, "closed", G_CALLBACK(fu_main_dbus_connection_closed_cb), priv); fu_main_register_object(priv); return TRUE; } static gboolean fu_main_timed_exit_cb(gpointer user_data) { GMainLoop *loop = (GMainLoop *)user_data; g_main_loop_quit(loop); return G_SOURCE_REMOVE; } static void fu_main_argv_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuMainPrivate *priv = (FuMainPrivate *)user_data; /* can do straight away? */ if (priv->update_in_progress) { g_warning("binary changed during a firmware update, ignoring"); return; } g_debug("binary changed, shutting down"); g_main_loop_quit(priv->loop); } #if GLIB_CHECK_VERSION(2, 63, 3) static void fu_main_memory_monitor_warning_cb(GMemoryMonitor *memory_monitor, GMemoryMonitorWarningLevel level, FuMainPrivate *priv) { /* can do straight away? */ if (priv->update_in_progress) { g_warning("OOM during a firmware update, ignoring"); priv->pending_sigterm = TRUE; return; } g_debug("OOM event, shutting down"); g_main_loop_quit(priv->loop); } #endif static GDBusNodeInfo * fu_main_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_resource_lookup_data(fu_get_resource(), 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); } 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; } static void fu_main_private_free(FuMainPrivate *priv) { g_hash_table_unref(priv->sender_items); if (priv->process_quit_id != 0) g_source_remove(priv->process_quit_id); if (priv->loop != NULL) g_main_loop_unref(priv->loop); if (priv->owner_id > 0) g_bus_unown_name(priv->owner_id); if (priv->proxy_uid != NULL) g_object_unref(priv->proxy_uid); if (priv->engine != NULL) g_object_unref(priv->engine); if (priv->connection != NULL) g_object_unref(priv->connection); #ifdef HAVE_POLKIT if (priv->authority != NULL) g_object_unref(priv->authority); #endif if (priv->argv0_monitor != NULL) { g_file_monitor_cancel(priv->argv0_monitor); g_object_unref(priv->argv0_monitor); } if (priv->introspection_daemon != NULL) g_dbus_node_info_unref(priv->introspection_daemon); #if GLIB_CHECK_VERSION(2, 63, 3) if (priv->memory_monitor != NULL) g_object_unref(priv->memory_monitor); #endif g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainPrivate, fu_main_private_free) #pragma clang diagnostic pop static void fu_main_sender_item_free(FuSenderItem *sender_item) { g_hash_table_unref(sender_item->hints); g_free(sender_item); } 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 */ _("Exit after a small delay"), NULL}, {"immediate-exit", '\0', 0, G_OPTION_ARG_NONE, &immediate_exit, /* TRANSLATORS: exit straight away, used for automatic profiling */ _("Exit after the engine has loaded"), NULL}, {NULL}}; g_autoptr(FuMainPrivate) priv = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) argv0_file = g_file_new_for_path(argv[0]); g_autoptr(GOptionContext) context = NULL; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); /* 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; } /* create new objects */ priv = g_new0(FuMainPrivate, 1); priv->sender_items = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)fu_main_sender_item_free); priv->loop = g_main_loop_new(NULL, FALSE); /* load engine */ priv->engine = fu_engine_new(FU_APP_FLAGS_NONE); g_signal_connect(FU_ENGINE(priv->engine), "changed", G_CALLBACK(fu_main_engine_changed_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), "device-changed", G_CALLBACK(fu_main_engine_device_changed_cb), priv); g_signal_connect(FU_ENGINE(priv->engine), "device-request", G_CALLBACK(fu_main_engine_device_request_cb), priv); g_signal_connect(FU_ENGINE(priv->engine), "status-changed", G_CALLBACK(fu_main_engine_status_changed_cb), priv); if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, &error)) { g_printerr("Failed to load engine: %s\n", error->message); return EXIT_FAILURE; } g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGTERM, fu_main_sigterm_cb, priv, NULL); /* restart the daemon if the binary gets replaced */ priv->argv0_monitor = g_file_monitor_file(argv0_file, G_FILE_MONITOR_NONE, NULL, &error); g_signal_connect(G_FILE_MONITOR(priv->argv0_monitor), "changed", G_CALLBACK(fu_main_argv_changed_cb), priv); #if GLIB_CHECK_VERSION(2, 63, 3) /* shut down on low memory event as we can just rescan hardware */ priv->memory_monitor = g_memory_monitor_dup_default(); g_signal_connect(G_MEMORY_MONITOR(priv->memory_monitor), "low-memory-warning", G_CALLBACK(fu_main_memory_monitor_warning_cb), priv); #endif /* load introspection from file */ priv->introspection_daemon = fu_main_load_introspection(FWUPD_DBUS_INTERFACE ".xml", &error); if (priv->introspection_daemon == NULL) { g_printerr("Failed to load introspection: %s\n", error->message); return EXIT_FAILURE; } #ifdef HAVE_POLKIT /* get authority */ priv->authority = polkit_authority_get_sync(NULL, &error); if (priv->authority == NULL) { g_printerr("Failed to load authority: %s\n", error->message); return EXIT_FAILURE; } #endif /* are we a VM? */ if (fu_main_is_hypervisor()) { priv->machine_kind = FU_MAIN_MACHINE_KIND_VIRTUAL; } else if (fu_main_is_container()) { priv->machine_kind = FU_MAIN_MACHINE_KIND_CONTAINER; } /* own the object */ if (socket_filename != NULL) { g_autofree gchar *address = g_strdup_printf("unix:path=%s", socket_filename); g_autofree gchar *guid = g_dbus_generate_guid(); g_autoptr(GDBusServer) server = NULL; /* this must be owned by root */ #ifndef HAVE_SYSTEMD g_unlink(socket_filename); #endif server = g_dbus_server_new_sync(address, G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, guid, NULL, NULL, &error); if (server == NULL) { g_printerr("Failed to create D-Bus server: %s\n", error->message); return EXIT_FAILURE; } g_dbus_server_start(server); g_signal_connect(server, "new-connection", G_CALLBACK(fu_main_dbus_new_connection_cb), priv); } else { priv->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_main_dbus_bus_acquired_cb, fu_main_dbus_name_acquired_cb, fu_main_dbus_name_lost_cb, priv, NULL); } /* 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, priv->loop); else if (timed_exit) g_timeout_add_seconds(5, fu_main_timed_exit_cb, priv->loop); #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")); g_main_loop_run(priv->loop); #ifdef HAVE_SYSTEMD /* notify the service manager */ sd_notify(0, "STOPPING=1"); #endif /* success */ return EXIT_SUCCESS; } fwupd-1.7.5/src/fu-offline.c000066400000000000000000000205041420024370600156350ustar00rootroot00000000000000/* * 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-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_common_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_common_spawn_sync(argv, NULL, NULL, 1500, NULL, &error_local)) { argv[2] = "--updates"; if (!fu_common_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_common_spawn_sync(argv, NULL, NULL, 200, NULL, &error_local)) { /* fall back to really old mode that should be supported */ argv[2] = "--shutdown"; if (!fu_common_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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *trigger = fu_common_get_path(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); /* 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 */ 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_common_vercmp_full(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.7.5/src/fu-plugin-list.c000066400000000000000000000224101420024370600164600ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPluginList" #include "config.h" #include #include "fwupd-error.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_debug("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); 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_debug("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_debug("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_debug("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 && !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) continue; if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; g_debug("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.7.5/src/fu-plugin-list.h000066400000000000000000000011671420024370600164730ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-plugin.h" #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.7.5/src/fu-polkit-agent.c000066400000000000000000000116061420024370600166140ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * 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-common.h" #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_error("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_common_find_program_in_path("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", 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", 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.7.5/src/fu-polkit-agent.h000066400000000000000000000006241420024370600166170ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2011 Lennart Poettering * Copyright (C) 2012 Matthias Klumpp * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once gboolean fu_polkit_agent_open(GError **error); void fu_polkit_agent_close(void); fwupd-1.7.5/src/fu-progressbar.c000066400000000000000000000254261420024370600165540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuProgressBar" #include "config.h" #include #include #include "fu-common.h" #include "fu-progressbar.h" static void fu_progressbar_finalize(GObject *obj); struct _FuProgressbar { GObject parent_instance; GMainContext *main_ctx; FwupdStatus status; gboolean spinner_count_up; /* chars */ guint spinner_idx; /* chars */ guint length_percentage; /* chars */ guint length_status; /* chars */ guint percentage; GSource *timer_source; gint64 last_animated; /* monotonic */ GTimer *time_elapsed; gdouble last_estimate; gboolean interactive; }; G_DEFINE_TYPE(FuProgressbar, fu_progressbar, G_TYPE_OBJECT) static const gchar * fu_progressbar_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: /* TRANSLATORS: waiting for device to do something */ return _("Waiting…"); break; default: break; } /* TRANSLATORS: current daemon status is unknown */ return _("Unknown"); } static void fu_progressbar_erase_line(FuProgressbar *self) { if (!self->interactive) return; g_print("\033[G"); } static gboolean fu_progressbar_estimate_ready(FuProgressbar *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; } 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_progressbar_time_remaining_str(FuProgressbar *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_progressbar_refresh(FuProgressbar *self, FwupdStatus status, guint percentage) { const gchar *title; guint i; gboolean is_idle_newline = FALSE; g_autoptr(GString) str = g_string_new(NULL); /* erase previous line */ fu_progressbar_erase_line(self); /* add status */ if (status == FWUPD_STATUS_IDLE) { percentage = 100; status = self->status; is_idle_newline = TRUE; } else if (status == FWUPD_STATUS_WAITING_FOR_AUTH) { is_idle_newline = TRUE; } if (percentage == 100) is_idle_newline = TRUE; title = fu_progressbar_status_to_string(status); g_string_append(str, title); for (i = fu_common_strwidth(str->str); i < self->length_status; i++) g_string_append_c(str, ' '); /* add progressbar */ g_string_append(str, "["); if (percentage > 0) { for (i = 0; i < (self->length_percentage - 1) * 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_progressbar_estimate_ready(self, percentage)) { g_autofree gchar *remaining = fu_progressbar_time_remaining_str(self); if (remaining != NULL) g_string_append_printf(str, " %s…", remaining); } /* dump to screen */ g_print("%s", str->str); /* done */ if (is_idle_newline) { g_print("\n"); return; } } /** * fu_progressbar_set_title: * @self: A #FuProgressbar * @title: A string * * Sets progressbar title * * Since: 0.9.7 **/ void fu_progressbar_set_title(FuProgressbar *self, const gchar *title) { fu_progressbar_erase_line(self); g_print("%s\n", title); fu_progressbar_refresh(self, self->status, self->percentage); } /** * fu_progressbar_set_main_context: * @self: A #FuProgressbar * @main_ctx: (nullable): main context * * Sets progressbar main context to use for animations. * * Since: 1.6.2 **/ void fu_progressbar_set_main_context(FuProgressbar *self, GMainContext *main_ctx) { self->main_ctx = g_main_context_ref(main_ctx); } static void fu_progressbar_spin_inc(FuProgressbar *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_progressbar_spin_cb(gpointer user_data) { FuProgressbar *self = FU_PROGRESSBAR(user_data); /* ignore */ if (self->status == FWUPD_STATUS_IDLE || self->status == FWUPD_STATUS_WAITING_FOR_AUTH) return G_SOURCE_CONTINUE; /* move the spinner index up to down */ fu_progressbar_spin_inc(self); /* update the terminal */ fu_progressbar_refresh(self, self->status, self->percentage); return G_SOURCE_CONTINUE; } static void fu_progressbar_spin_end(FuProgressbar *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_progressbar_spin_start(FuProgressbar *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_progressbar_spin_cb, self, NULL); g_source_attach(self->timer_source, self->main_ctx); } /** * fu_progressbar_update: * @self: A #FuProgressbar * @status: A #FwupdStatus * @percentage: unsigned integer * * Refreshes a progressbar * * Since: 0.9.7 **/ void fu_progressbar_update(FuProgressbar *self, FwupdStatus status, guint percentage) { g_return_if_fail(FU_IS_PROGRESSBAR(self)); /* ignore initial client connection */ if (self->status == FWUPD_STATUS_UNKNOWN && status == FWUPD_STATUS_IDLE) { self->status = status; return; } /* use cached value */ if (status == FWUPD_STATUS_UNKNOWN) status = self->status; if (!self->interactive) { g_print("%s: %u%%\n", fu_progressbar_status_to_string(status), percentage); self->status = status; self->percentage = 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 && status != FWUPD_STATUS_WAITING_FOR_AUTH && self->status != FWUPD_STATUS_UNKNOWN) { if ((g_get_monotonic_time() - self->last_animated) / 1000 > 40) { fu_progressbar_spin_inc(self); fu_progressbar_refresh(self, status, percentage); } } /* ignore duplicates */ if (self->status == status && self->percentage == percentage) return; /* enable or disable the spinner timeout */ if (percentage > 0) { fu_progressbar_spin_end(self); } else { fu_progressbar_spin_start(self); } /* update the terminal */ fu_progressbar_refresh(self, status, percentage); /* cache */ self->status = status; self->percentage = percentage; } /** * fu_progressbar_set_interactive: * @self: A #FuProgressbar * @interactive: #gboolean * * Marks the progressbar as interactive or not * * Since: 0.9.7 **/ void fu_progressbar_set_interactive(FuProgressbar *self, gboolean interactive) { g_return_if_fail(FU_IS_PROGRESSBAR(self)); self->interactive = interactive; } /** * fu_progressbar_set_length_status: * @self: A #FuProgressbar * @len: unsigned integer * * Sets the length of the progressbar status * * Since: 0.9.7 **/ void fu_progressbar_set_length_status(FuProgressbar *self, guint len) { g_return_if_fail(FU_IS_PROGRESSBAR(self)); g_return_if_fail(len > 3); self->length_status = len; } /** * fu_progressbar_set_length_percentage: * @self: A #FuProgressbar * @len: unsigned integer * * Sets the length of the progressba percentage * * Since: 0.9.7 **/ void fu_progressbar_set_length_percentage(FuProgressbar *self, guint len) { g_return_if_fail(FU_IS_PROGRESSBAR(self)); g_return_if_fail(len > 3); self->length_percentage = len; } static void fu_progressbar_class_init(FuProgressbarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_progressbar_finalize; } static void fu_progressbar_init(FuProgressbar *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_progressbar_finalize(GObject *obj) { FuProgressbar *self = FU_PROGRESSBAR(obj); 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_progressbar_parent_class)->finalize(obj); } /** * fu_progressbar_new: * * Creates a new #FuProgressbar * * Since: 0.9.7 **/ FuProgressbar * fu_progressbar_new(void) { FuProgressbar *self; self = g_object_new(FU_TYPE_PROGRESSBAR, NULL); return FU_PROGRESSBAR(self); } fwupd-1.7.5/src/fu-progressbar.h000066400000000000000000000014571420024370600165570ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" #define FU_TYPE_PROGRESSBAR (fu_progressbar_get_type()) G_DECLARE_FINAL_TYPE(FuProgressbar, fu_progressbar, FU, PROGRESSBAR, GObject) FuProgressbar * fu_progressbar_new(void); void fu_progressbar_update(FuProgressbar *self, FwupdStatus status, guint percentage); void fu_progressbar_set_length_status(FuProgressbar *self, guint len); void fu_progressbar_set_length_percentage(FuProgressbar *self, guint len); void fu_progressbar_set_title(FuProgressbar *self, const gchar *title); void fu_progressbar_set_interactive(FuProgressbar *self, gboolean interactive); void fu_progressbar_set_main_context(FuProgressbar *self, GMainContext *main_ctx); fwupd-1.7.5/src/fu-remote-list.c000066400000000000000000000407411420024370600164640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuRemoteList" #include "config.h" #include #include #include #include #ifdef HAVE_INOTIFY_H #include #include #endif #include "fwupd-common.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" #include "fu-common.h" #include "fu-remote-list.h" enum { SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; static void fu_remote_list_finalize(GObject *obj); struct _FuRemoteList { GObject parent_instance; GPtrArray *array; /* (element-type FwupdRemote) */ GPtrArray *monitors; /* (element-type GFileMonitor) */ GHashTable *hash_unfound; /* utf8 : NULL */ XbSilo *silo; }; 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_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_debug("%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 gboolean fu_remote_list_add_for_path(FuRemoteList *self, const gchar *path, GError **error) { const gchar *tmp; g_autofree gchar *path_remotes = NULL; g_autoptr(GDir) dir = NULL; g_autoptr(GHashTable) os_release = 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; dir = g_dir_open(path_remotes, 0, error); if (dir == NULL) return FALSE; os_release = fwupd_get_os_release(error); if (os_release == NULL) return FALSE; while ((tmp = g_dir_read_name(dir)) != NULL) { g_autofree gchar *filename = g_build_filename(path_remotes, tmp, NULL); g_autoptr(FwupdRemote) remote = fwupd_remote_new(); g_autofree gchar *remotesdir = NULL; /* skip invalid files */ if (!g_str_has_suffix(tmp, ".conf")) { g_debug("skipping invalid file %s", filename); continue; } /* set directory to store data */ remotesdir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_METADATA); fwupd_remote_set_remotes_dir(remote, remotesdir); /* load from keyfile */ g_debug("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; } if (!fwupd_remote_setup(remote, error)) { g_prefix_error(error, "failed to setup %s: ", filename); 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_autoptr(GString) agreement_markup = NULL; g_autofree gchar *component_id = _fwupd_remote_build_component_id(remote); g_autoptr(XbNode) component = NULL; g_autofree gchar *xpath = NULL; 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"; fu_common_string_replace(agreement_markup, "$OS_RELEASE:NAME$", tmp); tmp = g_hash_table_lookup(os_release, "BUG_REPORT_URL"); if (tmp == NULL) tmp = "https://github.com/fwupd/fwupd/issues"; fu_common_string_replace(agreement_markup, "$OS_RELEASE:BUG_REPORT_URL$", tmp); fwupd_remote_set_agreement(remote, agreement_markup->str); } /* set mtime */ fwupd_remote_set_mtime(remote, _fwupd_remote_get_mtime(remote)); g_ptr_array_add(self->array, g_steal_pointer(&remote)); } 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 *value_old = 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); if (!g_key_file_save_to_file(keyfile, filename, error)) return FALSE; /* reload values */ if (!fwupd_remote_load_from_filename(remote, filename, NULL, error)) { g_prefix_error(error, "failed to load %s: ", filename); 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_with_direction(FuRemoteList *self, gint inc) { guint cnt = 0; for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index(self->array, i); gchar **order = inc < 0 ? fwupd_remote_get_order_after(remote) : 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) { if (g_hash_table_contains(self->hash_unfound, order[j])) continue; g_debug("ignoring unfound remote %s", order[j]); g_hash_table_insert(self->hash_unfound, g_strdup(order[j]), NULL); continue; } if (fwupd_remote_get_priority(remote) > fwupd_remote_get_priority(remote2)) continue; g_debug("ordering %s=%s+%i", fwupd_remote_get_id(remote), fwupd_remote_get_id(remote2), inc); fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote2) + inc); /* increment changes counter */ cnt++; } } return cnt; } gboolean fu_remote_list_reload(FuRemoteList *self, GError **error) { guint depsolve_check; g_autofree gchar *remotesdir = NULL; g_autofree gchar *remotesdir_mut = NULL; /* clear */ g_ptr_array_set_size(self->array, 0); g_ptr_array_set_size(self->monitors, 0); /* use sysremotes, and then fall back to /etc */ remotesdir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR_PKG); if (!fu_remote_list_add_for_path(self, remotesdir, error)) return FALSE; remotesdir_mut = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!fu_remote_list_add_for_path(self, remotesdir_mut, error)) return FALSE; /* depsolve */ for (depsolve_check = 0; depsolve_check < 100; depsolve_check++) { guint cnt = 0; cnt += fu_remote_list_depsolve_with_direction(self, 1); cnt += fu_remote_list_depsolve_with_direction(self, -1); 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); /* 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_common_get_path(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); /* 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_common_get_path(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); } 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); self->hash_unfound = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } 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_hash_table_unref(self->hash_unfound); 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.7.5/src/fu-remote-list.h000066400000000000000000000024711420024370600164670ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-remote.h" #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 * * 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, /*< 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_reload(FuRemoteList *self, 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); fwupd-1.7.5/src/fu-security-attr.c000066400000000000000000000414651420024370600170430ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-security-attr.h" #include #include #include #include "fwupd-security-attr-private.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_ACPI_DMAR) == 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(_("Intel CET Enabled")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE) == 0) { /* TRANSLATORS: Title: CET = Control-flow Enforcement Technology, * active means being used by the OS */ return g_strdup(_("Intel CET Active")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_SMAP) == 0) { /* TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention */ return g_strdup(_("Intel 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_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_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_INTEL_DCI_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_DCI_LOCKED) == 0) { /* TRANSLATORS: Title: Direct Connect Interface (DCI) allows * debugging of Intel processors using the USB3 port */ return g_strdup(_("Intel DCI debugger")); } /* we should not get here */ return g_strdup(fwupd_security_attr_get_name(attr)); } 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; } 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 _("Failed"); } /** * fu_security_attrs_to_json_string: * Convert security attribute to JSON string. * @attrs: a pointer for a FuSecurityAttrs data structure. * @error: return location for an error * * fu_security_attrs_to_json_string() converts FuSecurityAttrs and return the * string pointer. The converted JSON format is shown as follows: * { * "SecurityAttributes": [ * { * "name": "aaa", * "value": "bbb" * } * ] * } * * Returns: A string and NULL on fail. * * Since: 1.7.0 * */ gchar * fu_security_attrs_to_json_string(FuSecurityAttrs *attrs, 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; fu_security_attrs_to_json(attrs, 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); } void fu_security_attrs_to_json(FuSecurityAttrs *attrs, 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(attrs); for (guint i = 0; i < items->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(items, i); guint64 created = fwupd_security_attr_get_created(attr); 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); } gboolean fu_security_attrs_from_json(FuSecurityAttrs *attrs, 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, "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; fu_security_attrs_append(attrs, 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 */ 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); /* 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 */ gboolean fu_security_attrs_equal(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2) { g_autoptr(GPtrArray) compare = fu_security_attrs_compare(attrs1, attrs2); return compare->len == 0; } fwupd-1.7.5/src/fu-security-attr.h000066400000000000000000000015301420024370600170350ustar00rootroot00000000000000/* * 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_result(FwupdSecurityAttr *attr); void fu_security_attrs_to_json(FuSecurityAttrs *attrs, JsonBuilder *builder); gchar * fu_security_attrs_to_json_string(FuSecurityAttrs *attrs, GError **error); gboolean fu_security_attrs_from_json(FuSecurityAttrs *attrs, JsonNode *json_node, GError **error); gboolean fu_security_attrs_equal(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2); GPtrArray * fu_security_attrs_compare(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2); const gchar * fu_security_attr_result_to_string(FwupdSecurityAttrResult result); fwupd-1.7.5/src/fu-self-test.c000066400000000000000000004126551420024370600161350ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include "fwupd-security-attr-private.h" #include "fu-config.h" #include "fu-context-private.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-hash.h" #include "fu-history.h" #include "fu-install-task.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fu-progressbar.h" #include "fu-security-attr.h" #include "fu-smbios-private.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_common_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 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 (fu_common_fnmatch(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_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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); 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_common_get_contents_bytes(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); ret = fu_common_set_contents_bytes("/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, &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_plugin_hash_func(gconstpointer user_data) { GError *error = NULL; g_autofree gchar *pluginfn = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuPlugin) plugin = fu_plugin_new(NULL); gboolean ret = FALSE; ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure not tainted */ ret = fu_engine_get_tainted(engine); g_assert_false(ret); /* create a tainted plugin */ pluginfn = g_test_build_filename(G_TEST_BUILT, "..", "plugins", "test", "libfu_plugin_invalid." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(plugin, pluginfn, &error); g_assert_true(ret); g_assert_no_error(error); /* make sure it tainted now */ g_test_expect_message("FuEngine", G_LOG_LEVEL_WARNING, "* has incorrect built version*"); fu_engine_add_plugin(engine, plugin); ret = fu_engine_get_tainted(engine); g_assert_true(ret); } static void fu_engine_requirements_missing_func(gconstpointer user_data) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; const gchar *xml = "" " " " hello-dave" " " ""; /* 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; const gchar *xml = "" " " " detach-action" " " ""; /* 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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_unsupported_func(gconstpointer user_data) { gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuDevice) child = fu_device_new(); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " " " " " 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuDevice) child = fu_device_new(); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " " " " " 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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) { gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " org.test.dummy" " " ""; /* 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 */ task = fu_install_task_new(NULL, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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 " " " " " 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, FWUPD_INSTALL_FLAG_ALLOW_OLDER, &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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuDevice) unrelated_device3 = fu_device_new_with_context(self->ctx); g_autoptr(FuDevice) parent = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task1 = NULL; g_autoptr(FuInstallTask) task2 = NULL; 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" " " " " " 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_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_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_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 */ task1 = fu_install_task_new(device1, component); ret = fu_engine_check_requirements(engine, request, task1, 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_guid(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); fu_device_add_child(parent, device2); fu_engine_add_device(engine, device2); /* check this passes */ task2 = fu_install_task_new(device1, component); ret = fu_engine_check_requirements(engine, request, task2, 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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" " " " " " 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_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 */ task = fu_install_task_new(device1, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(GPtrArray) devices = NULL; g_autoptr(FuInstallTask) task1 = NULL; g_autoptr(FuInstallTask) task2 = NULL; 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_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_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 */ task1 = fu_install_task_new(device1, component); ret = fu_engine_check_requirements(engine, request, task1, 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 */ task2 = fu_install_task_new(device2, component); ret = fu_engine_check_requirements(engine, request, task2, 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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" " " " " " 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_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 */ task = fu_install_task_new(device2, component); ret = fu_engine_check_requirements(engine, request, task, 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuDevice) device3 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuDevice) device3 = fu_device_new_with_context(self->ctx); g_autoptr(FuDevice) device4 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); 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_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); /* 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(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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); 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; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); 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, &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 */ g_assert_nonnull(fwupd_device_get_release_default(FWUPD_DEVICE(device))); } 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(FuInstallTask) task = NULL; 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; #if defined(__s390x__) /* See https://github.com/fwupd/fwupd/issues/318 for more information */ g_test_skip("Skipping HWID test on s390x due to known problem with gcab"); return; #endif /* 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, &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_common_get_contents_bytes(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_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 */ task = fu_install_task_new(device, component); ret = fu_engine_check_requirements(engine, request, task, 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_downgrade_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autoptr(FuDevice) device = fu_device_new_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); 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) 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" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " 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" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " 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, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); 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, ==, 4); /* 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); #ifndef HAVE_POLKIT g_test_expect_message("FuEngine", G_LOG_LEVEL_WARNING, "*archive signature missing or not trusted"); #endif 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); #ifdef HAVE_POLKIT g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); #endif 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, "deadbeefdeadbeefdeadbeefdeadbeef"); 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"); } 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); 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, &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); #ifndef HAVE_POLKIT g_test_expect_message("FuEngine", G_LOG_LEVEL_WARNING, "*archive signature missing or not trusted"); #endif 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); #ifdef HAVE_POLKIT g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); #endif /* 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_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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(FuInstallTask) task = NULL; 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 */ fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); /* 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_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_common_get_contents_bytes(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 */ g_setenv("FWUPD_PLUGIN_TEST", "another-write-required", TRUE); fu_device_set_metadata_integer(device, "nr-update", 0); /* install it */ task = fu_install_task_new(device, component); ret = fu_engine_install(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_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: updatable|historical\n" " Version: 1.2.2\n" " Created: 2018-01-07\n" " Modified: 2017-12-27\n" " UpdateState: success\n" " \n" " [Release]\n" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\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_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_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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; 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; /* ensure empty tree */ fu_self_test_mkroot(); /* 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, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); /* 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_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_common_get_contents_bytes(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); /* install it */ task = fu_install_task_new(device, component); ret = fu_engine_install(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_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"); } 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; 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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); history_db = g_build_filename(localstatedir, "pending.db", NULL); g_unlink(history_db); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ g_setenv("FWUPD_PLUGIN_TEST", "fail", TRUE); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); /* 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_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_common_get_contents_bytes(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 */ g_setenv("FWUPD_PLUGIN_TEST", "requires-activation", TRUE); task = fu_install_task_new(device, component); ret = fu_engine_install(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_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(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_object_unref(engine); g_object_unref(device); engine = fu_engine_new(FU_APP_FLAGS_NONE); fu_engine_set_silo(engine, silo_empty); fu_engine_add_plugin(engine, self->plugin); device = fu_device_new_with_context(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(FU_APP_FLAGS_NONE); fu_engine_set_silo(engine, silo_empty); fu_engine_add_plugin(engine, self->plugin); device = fu_device_new_with_context(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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuInstallTask) task = NULL; 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 */ g_setenv("FWUPD_PLUGIN_TEST", "fail", TRUE); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); /* 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_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_common_get_contents_bytes(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 */ g_setenv("FWUPD_PLUGIN_TEST", "requires-reboot", TRUE); task = fu_install_task_new(device, component); ret = fu_engine_install(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_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"); } 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_with_context(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(FuInstallTask) task = NULL; 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 */ g_setenv("FWUPD_PLUGIN_TEST", "fail", TRUE); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_engine_get_status(engine), ==, FWUPD_STATUS_IDLE); /* 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_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_common_get_contents_bytes(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); task = fu_install_task_new(device, component); ret = fu_engine_install(engine, task, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, FWUPD_FEATURE_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\n" " Version: 1.2.2\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" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\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(); g_autoptr(FuDevice) parent = fu_device_new(); 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(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(); g_autoptr(FuDevice) device2 = fu_device_new(); g_autoptr(FuDevice) parent = fu_device_new(); 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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(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_with_context(self->ctx); g_autoptr(FuDevice) device_parent = fu_device_new_with_context(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_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_with_context(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new_with_context(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_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 _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); } static void fu_plugin_module_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; GError *error = NULL; FuDevice *device_tmp; FwupdRelease *release; 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(FU_APP_FLAGS_NONE); g_autoptr(FuHistory) history = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); 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 */ g_setenv("FWUPD_PLUGIN_TEST", "registration", TRUE); ret = fu_plugin_runner_startup(self->plugin, &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, &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", "colorhug", "firmware.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); release = fu_device_get_release_default(device); 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 = fu_device_get_release_default(device2); g_assert_nonnull(release); g_assert_cmpstr(fwupd_release_get_filename(release), !=, NULL); g_assert_cmpstr(fwupd_release_get_version(release), ==, "1.2.3"); /* save this; we'll need to delete it later */ pending_cap = g_strdup(fwupd_release_get_filename(release)); /* 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_NONE, 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(); 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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); history_db = g_build_filename(localstatedir, "pending.db", NULL); g_unlink(history_db); 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_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) return; filename = g_build_filename(dirname, "pending.db", NULL); g_unlink(filename); /* add a device */ device = fu_device_new_with_context(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(GCabCompression compression, ...) { gboolean ret; va_list args; g_autoptr(GCabCabinet) cabinet = NULL; g_autoptr(GCabFolder) cabfolder = NULL; g_autoptr(GError) error = NULL; g_autoptr(GOutputStream) op = NULL; /* create a new archive */ cabinet = gcab_cabinet_new(); cabfolder = gcab_folder_new(compression); ret = gcab_cabinet_add_folder(cabinet, cabfolder, &error); g_assert_no_error(error); g_assert_true(ret); /* add each file */ va_start(args, compression); do { const gchar *fn; const gchar *text; g_autoptr(GCabFile) cabfile = NULL; 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)); cabfile = gcab_file_new_with_bytes(fn, blob); ret = gcab_folder_add_file(cabfolder, cabfile, FALSE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); } while (TRUE); va_end(args); /* write the archive to a blob */ op = g_memory_output_stream_new_resizable(); ret = gcab_cabinet_write_simple(cabinet, op, NULL, NULL, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = g_output_stream_close(op, NULL, &error); g_assert_no_error(error); g_assert_true(ret); return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(op)); } 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 void fu_plugin_composite_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; GError *error = NULL; gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new(FU_APP_FLAGS_NONE); g_autoptr(FuEngineRequest) request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) install_tasks = 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( GCAB_COMPRESSION_NONE, "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_common_cab_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 */ g_setenv("FWUPD_PLUGIN_TEST", "composite", TRUE); fu_engine_add_plugin(engine, self->plugin); ret = fu_plugin_runner_startup(self->plugin, &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, &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(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new(device, component); if (!fu_engine_check_requirements(engine, request, task, 0, &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(install_tasks, g_steal_pointer(&task)); } } g_assert_cmpint(install_tasks->len, ==, 3); /* install the cab */ ret = fu_engine_install_tasks(engine, request, install_tasks, blob, progress, FWUPD_DEVICE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* 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_attrs_func(gconstpointer user_data) { 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_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_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 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_progressbar_func(gconstpointer user_data) { g_autoptr(FuProgressbar) progressbar = fu_progressbar_new(); fu_progressbar_set_length_status(progressbar, 20); fu_progressbar_set_length_percentage(progressbar, 50); g_print("\n"); for (guint i = 0; i < 100; i++) { fu_progressbar_update(progressbar, FWUPD_STATUS_DECOMPRESSING, i); g_usleep(10000); } fu_progressbar_update(progressbar, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 100; i++) { guint pc = (i > 25 && i < 75) ? 0 : i; fu_progressbar_update(progressbar, FWUPD_STATUS_LOADING, pc); g_usleep(10000); } fu_progressbar_update(progressbar, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 5000; i++) { fu_progressbar_update(progressbar, FWUPD_STATUS_LOADING, 0); g_usleep(1000); } fu_progressbar_update(progressbar, FWUPD_STATUS_IDLE, 0); } static gint fu_install_task_compare_func_cb(gconstpointer a, gconstpointer b) { FuInstallTask *task_a = *((FuInstallTask **)a); FuInstallTask *task_b = *((FuInstallTask **)b); return fu_install_task_compare(task_a, task_b); } static void fu_install_task_compare_func(gconstpointer user_data) { FuDevice *device_tmp; g_autoptr(GPtrArray) install_tasks = NULL; g_autoptr(FuDevice) device1 = fu_device_new(); g_autoptr(FuDevice) device2 = fu_device_new(); g_autoptr(FuDevice) device3 = fu_device_new(); install_tasks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_device_set_order(device1, 99); g_ptr_array_add(install_tasks, fu_install_task_new(device1, NULL)); fu_device_set_order(device2, 11); g_ptr_array_add(install_tasks, fu_install_task_new(device2, NULL)); fu_device_set_order(device3, 33); g_ptr_array_add(install_tasks, fu_install_task_new(device3, NULL)); /* order the install tasks */ g_ptr_array_sort(install_tasks, fu_install_task_compare_func_cb); g_assert_cmpint(install_tasks->len, ==, 3); device_tmp = fu_install_task_get_device(g_ptr_array_index(install_tasks, 0)); g_assert_cmpint(fu_device_get_order(device_tmp), ==, 11); device_tmp = fu_install_task_get_device(g_ptr_array_index(install_tasks, 1)); g_assert_cmpint(fu_device_get_order(device_tmp), ==, 33); device_tmp = fu_install_task_get_device(g_ptr_array_index(install_tasks, 2)); g_assert_cmpint(fu_device_get_order(device_tmp), ==, 99); } int main(int argc, char **argv) { gboolean ret; g_autofree gchar *pluginfn = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuTest) self = g_new0(FuTest, 1); 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); g_setenv("G_MESSAGES_DEBUG", "all", TRUE); g_setenv("FWUPD_DEVICE_LIST_VERBOSE", "1", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); g_setenv("FWUPD_DATADIR", testdatadir, TRUE); g_setenv("FWUPD_PLUGINDIR", testdatadir, TRUE); g_setenv("FWUPD_SYSCONFDIR", testdatadir, TRUE); g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE); g_setenv("FWUPD_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", 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 the test plugin */ self->plugin = fu_plugin_new(self->ctx); pluginfn = g_test_build_filename(G_TEST_BUILT, "..", "plugins", "test", "libfu_plugin_test." G_MODULE_SUFFIX, NULL); ret = fu_plugin_open(self->plugin, pluginfn, &error); g_assert_no_error(error); g_assert_true(ret); /* tests go here */ if (g_test_slow()) { g_test_add_data_func("/fwupd/progressbar", self, fu_progressbar_func); } g_test_add_data_func("/fwupd/plugin{build-hash}", self, fu_plugin_hash_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_data_func("/fwupd/security-attr", self, fu_security_attr_func); g_test_add_data_func("/fwupd/security-attrs", self, fu_security_attrs_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{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/install-task{compare}", self, fu_install_task_compare_func); g_test_add_data_func("/fwupd/engine{device-unlock}", self, fu_engine_device_unlock_func); g_test_add_data_func("/fwupd/engine{multiple-releases}", self, fu_engine_multiple_rels_func); g_test_add_data_func("/fwupd/engine{history-success}", self, fu_engine_history_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{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-version-require}", self, fu_engine_requirements_version_require_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_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{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}", self, fu_history_migrate_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); return g_test_run(); } fwupd-1.7.5/src/fu-systemd.c000066400000000000000000000115751420024370600157130ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "fu-systemd.h" #include #include #include #include #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; } gboolean fu_systemd_unit_enable(const gchar *unit, GError **error) { const gchar *units[] = {unit, NULL}; g_autoptr(GDBusProxy) proxy_manager = 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; val = g_dbus_proxy_call_sync(proxy_manager, "EnableUnitFiles", g_variant_new("(^asbb)", units, TRUE, TRUE), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } gboolean fu_systemd_unit_disable(const gchar *unit, GError **error) { const gchar *units[] = {unit, NULL}; g_autoptr(GDBusProxy) proxy_manager = 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; val = g_dbus_proxy_call_sync(proxy_manager, "DisableUnitFiles", g_variant_new("(^asb)", units, TRUE), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } gboolean fu_systemd_unit_check_exists(const gchar *unit, GError **error) { g_autoptr(GDBusProxy) proxy_manager = NULL; g_autofree gchar *path = NULL; g_return_val_if_fail(unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager(error); if (proxy_manager == NULL) return FALSE; path = fu_systemd_unit_get_path(proxy_manager, unit, error); return path != NULL; } fwupd-1.7.5/src/fu-systemd.h000066400000000000000000000007311420024370600157100ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_systemd_unit_check_exists(const gchar *unit, GError **error); gboolean fu_systemd_unit_stop(const gchar *unit, GError **error); gboolean fu_systemd_unit_enable(const gchar *unit, GError **error); gboolean fu_systemd_unit_disable(const gchar *unit, GError **error); gchar * fu_systemd_get_default_target(GError **error); fwupd-1.7.5/src/fu-tool.c000066400000000000000000003260421420024370600151760ustar00rootroot00000000000000/* * 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-common-private.h" #include "fwupd-device-private.h" #include "fwupd-plugin-private.h" #include "fu-cabinet.h" #include "fu-context-private.h" #include "fu-debug.h" #include "fu-device-private.h" #include "fu-engine.h" #include "fu-history.h" #include "fu-hwids.h" #include "fu-plugin-private.h" #include "fu-progressbar.h" #include "fu-security-attr.h" #include "fu-security-attrs-private.h" #include "fu-smbios-private.h" #include "fu-util-common.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif /* custom return code */ #define EXIT_NOTHING_TO_DO 2 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; FuProgressbar *progressbar; gboolean as_json; gboolean no_reboot_check; gboolean no_safety_check; 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; gchar *current_message; FwupdDeviceFlags completion_flags; FwupdDeviceFlags filter_include; FwupdDeviceFlags filter_exclude; }; static gboolean fu_util_save_current_state(FuUtilPrivate *priv, GError **error) { g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; g_autoptr(GPtrArray) devices = NULL; g_autofree gchar *state = NULL; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; if (!priv->enable_json_state) return TRUE; devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; fwupd_device_array_ensure_parents(devices); /* create header */ builder = json_builder_new(); json_builder_begin_object(builder); /* add each device */ 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); json_builder_begin_object(builder); fwupd_device_to_json(dev, 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); state = json_generator_to_data(json_generator, NULL); if (state == NULL) return FALSE; dirname = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG); filename = g_build_filename(dirname, "state.json", NULL); return g_file_set_contents(filename, state, -1, error); } 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; /* print */ for (guint i = 0; i < 64; i++) { FwupdPluginFlags flag = (guint64)1 << i; const gchar *tmp; g_autofree gchar *fmt = NULL; 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((guint64)1 << i); if (tmp == NULL) continue; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_string_append_printf(str, "%s %s\n", fmt, tmp); url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s", fwupd_plugin_flag_to_string(flag)); g_string_append(str, " "); /* TRANSLATORS: %s is a link to a website */ g_string_append_printf(str, _("See %s for more information."), url); g_string_append(str, "\n"); g_printerr("%s", str->str); } } 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_common_get_path(FU_PATH_KIND_LOCKDIR); lockfn = g_build_filename(lockdir, "fwupdtool", NULL); } if (!fu_common_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; } static gboolean fu_util_start_engine(FuUtilPrivate *priv, FuEngineLoadFlags flags, 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_debug("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_debug("Failed to stop daemon: %s", error_local->message); } #endif if (!fu_engine_load(priv->engine, flags, error)) return FALSE; if (fu_engine_get_tainted(priv->engine)) { g_autofree gchar *fmt = NULL; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_printerr("%s This tool has loaded 3rd party code and " "is no longer supported by the upstream developers!\n", fmt); } fu_util_show_plugin_warnings(priv); fu_util_show_unsupported_warn(); 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 */ g_print("%s\n", _("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_smbios_to_string(smbios); g_print("%s\n", tmp); return TRUE; } #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->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->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->progressbar != NULL) g_object_unref(priv->progressbar); 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_free(priv->current_message); 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_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_progressbar_update(priv->progressbar, status, 0); } static void fu_util_progress_percentage_changed_cb(FuProgress *progress, guint percentage, FuUtilPrivate *priv) { if (priv->as_json) return; fu_progressbar_update(priv->progressbar, 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_progressbar_update(priv->progressbar, 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, 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(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_HWINFO, 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); g_print("%s\n", str); } if (plugins->len == 0) { /* TRANSLATORS: nothing found */ g_print("%s\n", _("No plugins found")); } return TRUE; } static gboolean fu_util_filter_device(FuUtilPrivate *priv, FwupdDevice *dev) { if (priv->filter_include != FWUPD_DEVICE_FLAG_NONE) { if (!fwupd_device_has_flag(dev, priv->filter_include)) return FALSE; } if (priv->filter_exclude != FWUPD_DEVICE_FLAG_NONE) { if (fwupd_device_has_flag(dev, priv->filter_exclude)) return FALSE; } return TRUE; } static gchar * fu_util_get_tree_title(FuUtilPrivate *priv) { return g_strdup(fu_engine_get_host_product(priv->engine)); } 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 = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index(devices, i); if (!fu_util_filter_device(priv, FWUPD_DEVICE(dev))) continue; g_ptr_array_add(devices_filtered, g_object_ref(dev)); } /* nothing */ if (devices_filtered->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No supported devices"); return NULL; } /* exactly one */ if (devices_filtered->len == 1) { dev = g_ptr_array_index(devices_filtered, 0); /* TRANSLATORS: Device has been chosen by the daemon for the user */ g_print("%s: %s\n", _("Selected device"), fu_device_get_name(dev)); return g_object_ref(dev); } /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose a device:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < devices_filtered->len; i++) { dev = g_ptr_array_index(devices_filtered, i); g_print("%u.\t%s (%s)\n", i + 1, fu_device_get_id(dev), fu_device_get_name(dev)); } idx = fu_util_prompt_for_number(devices_filtered->len); 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_autofree gchar *title = NULL; gboolean no_updates_header = FALSE; gboolean latest_header = FALSE; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; title = fu_util_get_tree_title(priv); /* 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)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!no_updates_header) { g_printerr("%s\n", /* 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; } g_printerr(" • %s\n", fwupd_device_get_name(dev)); continue; } if (!fu_util_filter_device(priv, 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) { if (!latest_header) { g_printerr( "%s\n", /* TRANSLATORS: message letting the user know no device upgrade * available */ _("Devices with the latest available firmware version:")); latest_header = TRUE; } g_printerr(" • %s\n", fwupd_device_get_name(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); g_node_append_data(child, g_object_ref(rel)); } } /* save the device state for other applications to see */ if (!fu_util_save_current_state(priv, error)) return FALSE; /* 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(root, title); 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_autofree gchar *title = NULL; gint fd; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; title = fu_util_get_tree_title(priv); /* 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 */ fd = open(values[0], O_RDONLY); if (fd < 0) { fu_util_maybe_prefix_sandbox_error(values[0], error); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", values[0]); return FALSE; } array = fu_engine_get_details(priv->engine, priv->request, fd, error); close(fd); 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 (!fu_util_filter_device(priv, dev)) 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(root, title); 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); } g_print("%s\n", 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 (!fu_util_filter_device(priv, FWUPD_DEVICE(dev_tmp))) 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(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new(NULL); g_autofree gchar *title = NULL; g_autoptr(GPtrArray) devs = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; title = fu_util_get_tree_title(priv); /* get devices and build tree */ devs = fu_engine_get_devices(priv->engine, error); if (devs == NULL) return FALSE; 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) { /* TRANSLATORS: nothing attached that can be upgraded */ g_print("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } fu_util_print_tree(root, title); /* save the device state for other applications to see */ return fu_util_save_current_state(priv, error); } 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 progressbar */ 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_progressbar_set_title(priv->progressbar, 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_progressbar_set_title(priv->progressbar, 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_progressbar_set_title(priv->progressbar, str); } else { g_warning("no FuUtilOperation set"); } g_set_object(&priv->current_device, device); if (priv->current_message == NULL) { const gchar *tmp = fwupd_device_get_update_message(priv->current_device); if (tmp != NULL) priv->current_message = g_strdup(tmp); } } static void fu_util_display_current_message(FuUtilPrivate *priv) { if (priv->current_message == NULL) return; g_print("%s\n", priv->current_message); g_clear_pointer(&priv->current_message, g_free); } static gboolean fu_util_install_blob(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GBytes) blob_fw = 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_common_get_contents_bytes(values[0], error); if (blob_fw == NULL) { fu_util_maybe_prefix_sandbox_error(values[0], error); return FALSE; } /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* get device */ 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, priv->progress, priv->flags, fu_engine_request_get_feature_flags(priv->request), error)) return FALSE; 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->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_common_get_contents_bytes(values[0], error); if (archive_blob_old == NULL) return FALSE; cert = fu_common_get_contents_bytes(values[1], error); if (cert == NULL) return FALSE; privkey = fu_common_get_contents_bytes(values[2], error); if (privkey == NULL) return FALSE; /* load, sign, export */ if (!fu_cabinet_parse(cabinet, archive_blob_old, FU_CABINET_PARSE_FLAG_NONE, error)) return FALSE; if (!fu_cabinet_sign(cabinet, cert, privkey, FU_CABINET_SIGN_FLAG_NONE, error)) return FALSE; archive_blob_new = fu_cabinet_export(cabinet, FU_CABINET_EXPORT_FLAG_NONE, error); if (archive_blob_new == NULL) return FALSE; return fu_common_set_contents_bytes(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; /* 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_common_set_contents_bytes(values[0], blob_empty, error)) return FALSE; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO, error)) return FALSE; /* get device */ 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, priv->progress, priv->flags, error); if (blob_fw == NULL) return FALSE; return fu_common_set_contents_bytes(values[0], blob_fw, error); } static gint fu_util_install_task_sort_cb(gconstpointer a, gconstpointer b) { FuInstallTask *task1 = *((FuInstallTask **)a); FuInstallTask *task2 = *((FuInstallTask **)b); return fu_install_task_compare(task1, task2); } static void fu_util_stdout_cb(const gchar *line, gpointer user_data) { if (g_getenv("FWUPD_DOWNLOAD_VERBOSE") != NULL) g_debug("'%s'", line); } static gboolean fu_util_download_out_of_process(const gchar *uri, const gchar *fn, GError **error) { #ifdef _WIN32 g_autofree gchar *basedir = fu_common_get_path(FU_PATH_KIND_WIN32_BASEDIR); g_autofree gchar *cert = g_build_filename(basedir, "bin", "ca-bundle.crt", NULL); const gchar *argv[][7] = {{"curl.exe", uri, "--output", fn, "--cacert", cert, NULL}, {NULL}}; #else const gchar *argv[][5] = {{"wget", uri, "-O", fn, NULL}, {"curl", uri, "--output", fn, NULL}, {NULL}}; #endif for (guint i = 0; argv[i][0] != NULL; i++) { g_autoptr(GError) error_local = NULL; g_autofree gchar *fn_tmp = NULL; fn_tmp = fu_common_find_program_in_path(argv[i][0], &error_local); if (fn_tmp == NULL) { g_debug("%s", error_local->message); continue; } return fu_common_spawn_sync(argv[i], fu_util_stdout_cb, NULL, 0, NULL, error); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no supported out-of-process downloaders found"); return FALSE; } static gchar * fu_util_download_if_required(FuUtilPrivate *priv, const gchar *perhapsfn, GError **error) { g_autofree gchar *filename = 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_common_mkdir_parent(filename, error)) return NULL; if (!fu_util_download_out_of_process(perhapsfn, filename, 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) install_tasks = NULL; g_autoptr(XbSilo) silo = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* 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(FWUPD_DEVICE(device), error)) return FALSE; } devices_possible = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); 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_common_get_contents_bytes(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); install_tasks = 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(FuInstallTask) task = NULL; g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ task = fu_install_task_new(device, component); if (!fu_engine_check_requirements(priv->engine, priv->request, task, priv->flags | FWUPD_INSTALL_FLAG_FORCE, &error_local)) { 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(errors, g_steal_pointer(&error_local)); continue; } /* make a second pass using possibly updated version format now */ fu_engine_md_refresh_device_from_component(priv->engine, device, component); if (!fu_engine_check_requirements(priv->engine, priv->request, task, priv->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(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(install_tasks, g_steal_pointer(&task)); } } /* order the install tasks by the device priority */ g_ptr_array_sort(install_tasks, fu_util_install_task_sort_cb); /* nothing suitable */ if (install_tasks->len == 0) { GError *error_tmp = fu_common_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_tasks(priv->engine, priv->request, install_tasks, blob_cab, priv->progress, priv->flags, 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; } /* save the device state for other applications to see */ if (!fu_util_save_current_state(priv, error)) return FALSE; /* success */ return fu_util_prompt_complete(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); } 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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, 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) { g_printerr("%s\n", /* 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; } g_printerr(" • %s\n", fwupd_device_get_name(dev)); continue; } if (!fu_util_filter_device(priv, dev)) continue; rels = fu_engine_get_upgrades(priv->engine, priv->request, device_id, &error_local); if (rels == NULL) { if (!latest_header) { g_printerr( "%s\n", /* TRANSLATORS: message letting the user know no device upgrade * available */ _("Devices with the latest available firmware version:")); latest_header = TRUE; } g_printerr(" • %s\n", 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) { if (!fu_util_prompt_warning(dev, rel, fu_util_get_tree_title(priv), error)) return FALSE; if (!fu_util_prompt_warning_fde(dev, error)) return FALSE; } if (!fu_util_install_release(priv, rel, &error_local)) { g_printerr("%s\n", 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; } /* save the device state for other applications to see */ if (!fu_util_save_current_state(priv, error)) return FALSE; return fu_util_prompt_complete(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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, 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 (fu_common_vercmp_full(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; } /* save the device state for other applications to see */ if (!fu_util_save_current_state(priv, error)) return FALSE; return fu_util_prompt_complete(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; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* get device */ 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; return fu_device_detach_full(device, priv->progress, error); } 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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, 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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, 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; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* get device */ 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; return fu_device_attach_full(device, priv->progress, 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; /* 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_HWINFO, 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; } /* activate anything with _NEEDS_ACTIVATION */ /* order by device priority */ g_ptr_array_sort(devices, fu_util_device_order_sort_cb); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (!fu_util_filter_device(priv, FWUPD_DEVICE(device))) continue; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; has_pending = TRUE; /* TRANSLATORS: shown when shutting down to switch to the new version */ g_print("%s %s…\n", _("Activating firmware update"), fu_device_get_name(device)); if (!fu_engine_activate(priv->engine, fu_device_get_id(device), priv->progress, error)) return FALSE; } 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) { g_autoptr(FuHwids) hwids = fu_hwids_new(); g_autoptr(FuSmbios) smbios = fu_smbios_new(); 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_smbios_setup(smbios, error)) return FALSE; if (!fu_hwids_setup(hwids, smbios, 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); 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) { g_autoptr(FuSmbios) smbios = NULL; g_autoptr(FuHwids) hwids = fu_hwids_new(); g_autoptr(GPtrArray) hwid_keys = fu_hwids_get_keys(hwids); /* read DMI data */ if (g_strv_length(values) == 0) { smbios = fu_smbios_new(); if (!fu_smbios_setup(smbios, error)) return FALSE; } else if (g_strv_length(values) == 1) { /* a keyfile with overrides */ g_autoptr(GKeyFile) kf = g_key_file_new(); if (g_key_file_load_from_file(kf, values[0], G_KEY_FILE_NONE, NULL)) { 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_smbios_override(hwids, hwid_key, tmp); } /* a DMI blob */ } else { smbios = fu_smbios_new(); if (!fu_smbios_setup_from_file(smbios, values[0], error)) return FALSE; } } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_hwids_setup(hwids, smbios, error)) return FALSE; /* show debug output */ g_print("Computer Information\n"); g_print("--------------------\n"); 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); g_print("%s: %" G_GUINT64_FORMAT "\n", hwid_key, val); } else { g_print("%s: %s\n", hwid_key, value); } } /* show GUIDs */ g_print("\nHardware IDs\n"); g_print("------------\n"); 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) { g_print("%s\n", error_local->message); continue; } /* show what makes up the GUID */ keysv = g_strsplit(keys, "&", -1); keys_str = g_strjoinv(" + ", keysv); g_print("{%s} <- %s\n", guid, keys_str); } return TRUE; } static gboolean fu_util_firmware_builder(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *script_fn = "startup.sh"; const gchar *output_fn = "firmware.bin"; g_autoptr(GBytes) archive_blob = NULL; g_autoptr(GBytes) firmware_blob = NULL; if (g_strv_length(values) < 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } archive_blob = fu_common_get_contents_bytes(values[0], error); if (archive_blob == NULL) return FALSE; if (g_strv_length(values) > 2) script_fn = values[2]; if (g_strv_length(values) > 3) output_fn = values[3]; firmware_blob = fu_common_firmware_builder(archive_blob, script_fn, output_fn, error); if (firmware_blob == NULL) return FALSE; return fu_common_set_contents_bytes(values[1], firmware_blob, error); } 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, 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; g_print("%s\n", sig); return TRUE; } static void fu_util_device_added_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fu_util_device_to_string(device, 0); /* TRANSLATORS: this is when a device is hotplugged */ g_print("%s\n%s", _("Device added:"), tmp); } static void fu_util_device_removed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fu_util_device_to_string(device, 0); /* TRANSLATORS: this is when a device is hotplugged */ g_print("%s\n%s", _("Device removed:"), tmp); } static void fu_util_device_changed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { g_autofree gchar *tmp = fu_util_device_to_string(device, 0); /* TRANSLATORS: this is when a device has been updated */ g_print("%s\n%s", _("Device changed:"), tmp); } static void fu_util_changed_cb(FwupdClient *client, gpointer user_data) { /* TRANSLATORS: this is when the daemon state changes */ g_print("%s\n", _("Changed")); } static gboolean fu_util_monitor(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdClient) client = fwupd_client_new(); fwupd_client_set_main_context(client, priv->main_ctx); /* get all the devices */ if (!fwupd_client_connect(client, priv->cancellable, error)) return FALSE; /* watch for any hotplugged device */ g_signal_connect(FWUPD_CLIENT(client), "changed", G_CALLBACK(fu_util_changed_cb), priv); g_signal_connect(FWUPD_CLIENT(client), "device-added", G_CALLBACK(fu_util_device_added_cb), priv); g_signal_connect(FWUPD_CLIENT(client), "device-removed", G_CALLBACK(fu_util_device_removed_cb), priv); g_signal_connect(FWUPD_CLIENT(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, 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); g_print("%s\n", id); } if (firmware_types->len == 0) { /* TRANSLATORS: nothing found */ g_print("%s\n", _("No firmware IDs found")); return TRUE; } return TRUE; } static gchar * fu_util_prompt_for_firmware_type(FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) firmware_types = NULL; guint idx; firmware_types = fu_context_get_firmware_gtype_ids(fu_engine_get_context(priv->engine)); /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose a firmware type:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < firmware_types->len; i++) { const gchar *id = g_ptr_array_index(firmware_types, i); g_print("%u.\t%s\n", i + 1, id); } idx = fu_util_prompt_for_number(firmware_types->len); 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) { 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_common_get_contents_bytes(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) firmware_type = fu_util_prompt_for_firmware_type(priv, error); if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), 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); g_print("%s", str); return TRUE; } static gboolean fu_util_firmware_export(FuUtilPrivate *priv, gchar **values, GError **error) { 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_common_get_contents_bytes(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) firmware_type = fu_util_prompt_for_firmware_type(priv, error); if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), 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; g_print("%s", str); return TRUE; } static gboolean fu_util_firmware_extract(FuUtilPrivate *priv, gchar **values, GError **error) { 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_common_get_contents_bytes(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) firmware_type = fu_util_prompt_for_firmware_type(priv, error); if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), 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); g_print("%s", 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 */ g_print("%s : %s\n", _("Writing file:"), fn); if (!fu_common_set_contents_bytes(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_common_get_contents_bytes(values[0], error); if (blob_src == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, 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_common_set_contents_bytes(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); g_print("%s", 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_common_get_contents_bytes(values[0], error); if (blob_src == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY, error)) return FALSE; /* find the GType to use */ if (firmware_type_src == NULL) firmware_type_src = fu_util_prompt_for_firmware_type(priv, error); if (firmware_type_src == NULL) return FALSE; if (firmware_type_dst == NULL) firmware_type_dst = fu_util_prompt_for_firmware_type(priv, error); if (firmware_type_dst == NULL) return FALSE; gtype_src = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), 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); g_print("%s", 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); } /* write new file */ blob_dst = fu_firmware_write(firmware_dst, error); if (blob_dst == NULL) return FALSE; if (!fu_common_set_contents_bytes(values[1], blob_dst, error)) return FALSE; str_dst = fu_firmware_to_string(firmware_dst); g_print("%s", 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_byte_array_free_to_bytes(g_steal_pointer(&buf)); } 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_common_get_contents_bytes(values[0], error); if (blob_src == NULL) return FALSE; /* parse offset */ if (!fu_common_strtoull_full(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, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) firmware_type = fu_util_prompt_for_firmware_type(priv, 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_common_set_contents_bytes(values[0], blob_dst, error)) return FALSE; str = fu_firmware_to_string(firmware); g_print("%s", 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; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* get device */ 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), priv->progress, error)) return FALSE; /* show checksums */ str = fu_device_to_string(dev); g_print("%s\n", 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); g_autofree gchar *title = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; title = fu_util_get_tree_title(priv); /* 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; if (!fu_util_filter_device(priv, dev)) 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 = fu_engine_get_releases(priv->engine, priv->request, fwupd_device_get_id(dev), error); if (rels == NULL) 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 (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(root, title); return TRUE; } static gboolean fu_util_refresh_remote(FuUtilPrivate *priv, FwupdRemote *remote, GError **error) { const gchar *metadata_uri = NULL; g_autofree gchar *fn_raw = NULL; g_autofree gchar *fn_sig = NULL; g_autoptr(GBytes) bytes_raw = NULL; g_autoptr(GBytes) bytes_sig = NULL; /* signature */ metadata_uri = fwupd_remote_get_metadata_uri_sig(remote); if (metadata_uri == 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; } fn_sig = fu_util_get_user_cache_path(metadata_uri); if (!fu_common_mkdir_parent(fn_sig, error)) return FALSE; if (!fu_util_download_out_of_process(metadata_uri, fn_sig, error)) return FALSE; bytes_sig = fu_common_get_contents_bytes(fn_sig, error); if (bytes_sig == NULL) return FALSE; if (!fwupd_remote_load_signature_bytes(remote, bytes_sig, error)) return FALSE; /* payload */ metadata_uri = fwupd_remote_get_metadata_uri(remote); if (metadata_uri == 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; } fn_raw = fu_util_get_user_cache_path(metadata_uri); if (!fu_util_download_out_of_process(metadata_uri, fn_raw, error)) return FALSE; bytes_raw = fu_common_get_contents_bytes(fn_raw, error); if (bytes_raw == NULL) return FALSE; /* send to daemon */ g_debug("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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, 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_get_enabled(remote)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) 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; g_autofree gchar *title = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; title = fu_util_get_tree_title(priv); /* 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(root, title); 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) items = NULL; g_autoptr(GPtrArray) events_array = NULL; g_autofree gchar *str = NULL; /* not ready yet */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "The HSI specification is not yet complete. " "To ignore this warning, use --force"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; g_print("%s \033[1m%s\033[0m\n", /* TRANSLATORS: this is a string like 'HSI:2-U' */ _("Host Security ID:"), fu_engine_get_host_security_id(priv->engine)); /* 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; } /* print the "why" */ attrs = fu_engine_get_host_security_attrs(priv->engine); if (priv->as_json) { str = fu_security_attrs_to_json_string(attrs, error); if (str == NULL) return FALSE; g_print("%s\n", str); return TRUE; } items = fu_security_attrs_get_all(attrs); str = fu_util_security_attrs_to_string(items, flags); g_print("%s\n", 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) g_print("%s\n", estr); } /* success */ return TRUE; } static FuVolume * fu_util_prompt_for_volume(GError **error) { FuVolume *volume; guint idx; gboolean is_fallback = FALSE; g_autoptr(GPtrArray) volumes = NULL; g_autoptr(GPtrArray) volumes_vfat = g_ptr_array_new(); g_autoptr(GError) error_local = NULL; /* exactly one */ volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_ESP, &error_local); if (volumes == NULL) { is_fallback = TRUE; g_debug("%s, falling back to %s", error_local->message, FU_VOLUME_KIND_BDP); volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_BDP, error); if (volumes == NULL) { g_prefix_error(error, "%s: ", error_local->message); return NULL; } } /* on fallback: only add internal vfat partitions */ for (guint i = 0; i < volumes->len; i++) { FuVolume *vol = g_ptr_array_index(volumes, i); g_autofree gchar *type = fu_volume_get_id_type(vol); if (type == NULL) continue; if (is_fallback && !fu_volume_is_internal(vol)) continue; if (g_strcmp0(type, "vfat") == 0) g_ptr_array_add(volumes_vfat, vol); } if (volumes_vfat->len == 1) { volume = g_ptr_array_index(volumes_vfat, 0); /* TRANSLATORS: Volume has been chosen by the user */ g_print("%s: %s\n", _("Selected volume"), fu_volume_get_id(volume)); return g_object_ref(volume); } /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose a volume:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < volumes_vfat->len; i++) { volume = g_ptr_array_index(volumes_vfat, i); g_print("%u.\t%s\n", i + 1, fu_volume_get_id(volume)); } idx = fu_util_prompt_for_number(volumes_vfat->len); 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_vfat, 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(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(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; volume = fu_util_prompt_for_volume(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); files = fu_common_get_files_recursive(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_print("%s\n", 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_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES, error)) return FALSE; /* find the device and check it has multiple branches */ priv->filter_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; priv->filter_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 GLIB_CHECK_VERSION(2, 54, 3) if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL)) continue; #endif 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: get interactive prompt, where branch is the * supplier of the firmware, e.g. "non-free" or "free" */ g_print("%s\n", _("Choose a branch:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < branches->len; i++) { const gchar *branch_tmp = g_ptr_array_index(branches, i); g_print("%u.\t%s\n", i + 1, fu_util_branch_for_display(branch_tmp)); } idx = fu_util_prompt_for_number(branches->len); 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(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->completion_flags, TRUE, error); } static gboolean fu_util_version(FuUtilPrivate *priv, GError **error) { g_autoptr(GHashTable) metadata = NULL; g_autofree gchar *str = NULL; /* 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(metadata, error); str = fu_util_project_versions_to_string(metadata); g_print("%s", 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); } int main(int argc, char *argv[]) { gboolean allow_branch_switch = FALSE; gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean force = FALSE; gboolean ret; gboolean version = FALSE; gboolean ignore_checksum = FALSE; gboolean ignore_vid_pid = FALSE; g_auto(GStrv) plugin_glob = NULL; 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 = NULL; const GOptionEntry options[] = { {"version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ _("Show client and daemon versions"), NULL}, {"allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ _("Allow reinstalling existing firmware versions"), NULL}, {"allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ _("Allow downgrading firmware versions"), NULL}, {"allow-branch-switch", '\0', 0, G_OPTION_ARG_NONE, &allow_branch_switch, /* TRANSLATORS: command line option */ _("Allow switching firmware branch"), NULL}, {"force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ _("Force the action by relaxing some runtime checks"), NULL}, {"ignore-checksum", '\0', 0, G_OPTION_ARG_NONE, &ignore_checksum, /* TRANSLATORS: command line option */ _("Ignore firmware checksum failures"), NULL}, {"ignore-vid-pid", '\0', 0, G_OPTION_ARG_NONE, &ignore_vid_pid, /* TRANSLATORS: command line option */ _("Ignore firmware hardware mismatch failures"), NULL}, {"no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check, /* TRANSLATORS: command line option */ _("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 */ _("Do not perform device safety checks"), NULL}, {"show-all", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ _("Show all results"), NULL}, {"show-all-devices", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ _("Show devices that are not updatable"), NULL}, {"plugins", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob, /* TRANSLATORS: command line option */ _("Manually enable specific plugins"), NULL}, {"plugin-whitelist", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &plugin_glob, /* TRANSLATORS: command line option */ _("Manually enable specific plugins"), NULL}, {"prepare", '\0', 0, G_OPTION_ARG_NONE, &priv->prepare_blob, /* TRANSLATORS: command line option */ _("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 */ _("Run the plugin composite cleanup routine when using install-blob"), NULL}, {"enable-json-state", '\0', 0, G_OPTION_ARG_NONE, &priv->enable_json_state, /* TRANSLATORS: command line option */ _("Save device state into a JSON file between executions"), NULL}, {"disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ _("Ignore SSL strict checks when downloading files"), NULL}, {"filter", '\0', 0, G_OPTION_ARG_STRING, &filter, /* TRANSLATORS: command line option */ _("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL}, {"json", '\0', 0, G_OPTION_ARG_NONE, &priv->as_json, /* TRANSLATORS: command line option */ _("Output in JSON format"), NULL}, {NULL}}; #ifdef _WIN32 /* workaround Windows setting the codepage to 1252 */ 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); /* create helper object */ priv->main_ctx = g_main_context_new(); priv->loop = g_main_loop_new(priv->main_ctx, FALSE); priv->progressbar = fu_progressbar_new(); fu_progressbar_set_main_context(priv->progressbar, priv->main_ctx); priv->request = fu_engine_request_new(FU_ENGINE_REQUEST_KIND_ACTIVE); /* when not using the engine */ priv->progress = fu_progress_new(G_STRLOC); fu_progress_set_profile(priv->progress, g_getenv("FWUPD_VERBOSE") != NULL); 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, "build-firmware", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE-IN FILE-OUT [SCRIPT] [OUTPUT]"), /* TRANSLATORS: command description */ _("Build firmware using a sandbox"), fu_util_firmware_builder); 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 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 firmware file on this hardware"), 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, "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-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-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); /* 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_console(&error_console)) { g_debug("failed to initialize interactive console: %s", error_console->message); priv->no_reboot_check = TRUE; priv->no_safety_check = 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); } fu_progressbar_set_interactive(priv->progressbar, 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) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { g_autofree gchar *fmt = NULL; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_printerr("%s %s\n", fmt, /* TRANSLATORS: try to help */ _("Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); g_setenv("DISABLE_SSL_STRICT", "1", TRUE); } /* parse filter flags */ if (filter != NULL) { if (!fu_util_parse_filter_flags(filter, &priv->filter_include, &priv->filter_exclude, &error)) { g_print("%s: %s\n", /* TRANSLATORS: the user didn't read the man page */ _("Failed to parse flags for --filter"), error->message); 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 (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(FU_APP_FLAGS_NO_IDLE_SOURCES); 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)) { g_printerr("%s\n", error->message); 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) { g_printerr("%s\n", error->message); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { /* TRANSLATORS: error message explaining command on how to get help */ g_printerr("\n%s\n", _("Use fwupdtool --help for help")); } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("%s\n", error->message); return EXIT_NOTHING_TO_DO; } #ifdef HAVE_GETUID /* if not root, then notify users on the error path */ if (priv->interactive && (getuid() != 0 || geteuid() != 0)) { /* TRANSLATORS: we're poking around as a power user */ g_printerr("%s\n", _("NOTE: This program may only work correctly as root")); } #endif return EXIT_FAILURE; } /* success */ return EXIT_SUCCESS; } fwupd-1.7.5/src/fu-udev-backend.c000066400000000000000000000134771420024370600165560ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include #include "fu-udev-backend.h" #include "fu-udev-device.h" struct _FuUdevBackend { FuBackend parent_instance; GUdevClient *gudev_client; GHashTable *changed_idle_ids; /* sysfs:FuUdevBackendHelper */ GPtrArray *subsystems; }; G_DEFINE_TYPE(FuUdevBackend, fu_udev_backend, FU_TYPE_BACKEND) static void fu_udev_backend_device_add(FuUdevBackend *self, GUdevDevice *udev_device) { g_autoptr(FuUdevDevice) device = NULL; /* success */ device = fu_udev_device_new_with_context(fu_backend_get_context(FU_BACKEND(self)), udev_device); 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) { if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { g_debug("UDEV %s removed", g_udev_device_get_sysfs_path(udev_device)); } fu_backend_device_removed(FU_BACKEND(self), device_tmp); } } 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); 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 gboolean fu_udev_backend_coldplug(FuBackend *backend, GError **error) { FuUdevBackend *self = FU_UDEV_BACKEND(backend); /* udev watches can only be set up in _init() so set up client now */ if (self->subsystems->len > 0) { g_auto(GStrv) subsystems = g_new0(gchar *, self->subsystems->len + 1); for (guint i = 0; i < self->subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index(self->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 */ for (guint i = 0; i < self->subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index(self->subsystems, i); GList *devices = g_udev_client_query_by_subsystem(self->gudev_client, subsystem); if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) { g_debug("%u devices with subsystem %s", g_list_length(devices), subsystem); } for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *udev_device = l->data; fu_udev_backend_device_add(self, udev_device); } g_list_foreach(devices, (GFunc)g_object_unref, NULL); g_list_free(devices); } return TRUE; } static void fu_udev_backend_finalize(GObject *object) { FuUdevBackend *self = FU_UDEV_BACKEND(object); if (self->gudev_client != NULL) g_object_unref(self->gudev_client); if (self->subsystems != NULL) g_ptr_array_unref(self->subsystems); g_hash_table_unref(self->changed_idle_ids); G_OBJECT_CLASS(fu_udev_backend_parent_class)->finalize(object); } static void fu_udev_backend_init(FuUdevBackend *self) { 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; } FuBackend * fu_udev_backend_new(GPtrArray *subsystems) { FuUdevBackend *self; self = FU_UDEV_BACKEND(g_object_new(FU_TYPE_UDEV_BACKEND, "name", "udev", NULL)); if (subsystems != NULL) self->subsystems = g_ptr_array_ref(subsystems); return FU_BACKEND(self); } fwupd-1.7.5/src/fu-udev-backend.h000066400000000000000000000005301420024370600165450ustar00rootroot00000000000000/* * 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(GPtrArray *subsystems); fwupd-1.7.5/src/fu-usb-backend.c000066400000000000000000000103261420024370600163720ustar00rootroot00000000000000/* * 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.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 = NULL; /* success */ device = fu_usb_device_new_with_context(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) { FuUsbBackend *self = FU_USB_BACKEND(backend); FuDevice *device_tmp; /* find the device we enumerated */ device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), g_usb_device_get_platform_id(usb_device)); if (device_tmp != NULL) fu_backend_device_removed(backend, device_tmp); } 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 gboolean fu_usb_backend_setup(FuBackend *backend, 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(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_coldplug(FuBackend *backend, GError **error) { FuUsbBackend *self = FU_USB_BACKEND(backend); g_usb_context_enumerate(self->usb_ctx); return TRUE; } 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_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->registered = fu_usb_backend_registered; } FuBackend * fu_usb_backend_new(void) { return FU_BACKEND(g_object_new(FU_TYPE_USB_BACKEND, "name", "usb", NULL)); } fwupd-1.7.5/src/fu-usb-backend.h000066400000000000000000000005011420024370600163710ustar00rootroot00000000000000/* * 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(void); fwupd-1.7.5/src/fu-util-common.c000066400000000000000000002514161420024370600164660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include #include #include #include #ifdef HAVE_GUSB #include #endif #include #include #ifdef HAVE_LIBCURL #include #endif #ifdef _WIN32 #include #include #endif #include "fu-common.h" #include "fu-device-private.h" #include "fu-device.h" #include "fu-security-attr.h" #include "fu-security-attrs.h" #include "fu-util-common.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif #define SYSTEMD_FWUPD_UNIT "fwupd.service" #define SYSTEMD_SNAP_FWUPD_UNIT "snap.fwupd.fwupd.service" const gchar * fu_util_get_systemd_unit(void) { if (g_getenv("SNAP") != NULL) return SYSTEMD_SNAP_FWUPD_UNIT; return SYSTEMD_FWUPD_UNIT; } gchar * fu_util_term_format(const gchar *text, FuUtilTermColor fg_color) { return g_strdup_printf("\033[%um\033[1m%s\033[0m", fg_color, text); } #ifdef HAVE_SYSTEMD static const gchar * fu_util_get_expected_command(const gchar *target) { if (g_strcmp0(target, SYSTEMD_SNAP_FWUPD_UNIT) == 0) return "fwupd.fwupdmgr"; return "fwupdmgr"; } #endif gboolean fu_util_using_correct_daemon(GError **error) { #ifdef HAVE_SYSTEMD g_autofree gchar *default_target = NULL; g_autoptr(GError) error_local = NULL; const gchar *target = fu_util_get_systemd_unit(); default_target = fu_systemd_get_default_target(&error_local); if (default_target == NULL) { g_debug("Systemd isn't accessible: %s\n", error_local->message); return TRUE; } if (!fu_systemd_unit_check_exists(target, &error_local)) { g_debug("wrong target: %s\n", error_local->message); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Mismatched daemon and client, use %s instead"), fu_util_get_expected_command(target)); return FALSE; } #endif return TRUE; } void fu_util_print_data(const gchar *title, const gchar *msg) { gsize title_len; g_auto(GStrv) lines = NULL; if (msg == NULL) return; g_print("%s:", title); /* pad */ title_len = fu_common_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_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; } gboolean fu_util_prompt_for_boolean(gboolean def) { 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; } while (TRUE); return FALSE; } static gboolean fu_util_traverse_tree(GNode *n, gpointer 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(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_debug("%s", tmp); } /* root node */ if (n->data == NULL && g_getenv("FWUPD_VERBOSE") == NULL) { const gchar *str = data; g_print("%s\n│\n", str != NULL ? str : "○"); 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->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') { g_print("%s\n", str->str); continue; } /* dump to the console */ g_string_append(str, split[i] + (idx * 2)); g_print("%s\n", str->str); } return FALSE; } void fu_util_print_tree(GNode *n, gpointer data) { g_node_traverse(n, G_PRE_ORDER, G_TRAVERSE_ALL, -1, fu_util_traverse_tree, data); } 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; /* 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(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_common_vercmp_full(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_util_warning_box(title->str, str->str, 80); /* ask for confirmation */ g_print("\n%s [Y|n]: ", /* TRANSLATORS: prompt to apply the update */ _("Perform operation?")); if (!fu_util_prompt_for_boolean(TRUE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } /* success */ return TRUE; } gboolean fu_util_prompt_complete(FwupdDeviceFlags flags, gboolean prompt, GError **error) { if (flags & FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { if (prompt) { g_print("\n%s %s [y|N]: ", /* TRANSLATORS: explain why we want to shutdown */ _("An update requires the system to shutdown to complete."), /* TRANSLATORS: shutdown to apply the update */ _("Shutdown now?")); if (!fu_util_prompt_for_boolean(FALSE)) return TRUE; } return fu_util_update_shutdown(error); } if (flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { if (prompt) { g_print("\n%s %s [y|N]: ", /* TRANSLATORS: explain why we want to reboot */ _("An update requires a reboot to complete."), /* TRANSLATORS: reboot to apply the update */ _("Restart now?")); if (!fu_util_prompt_for_boolean(FALSE)) 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_common_strwidth(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += fu_common_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; } 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); } } /* 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); } static GPtrArray * fu_util_strsplit_words(const gchar *text, guint line_len) { g_auto(GStrv) tokens = NULL; g_autoptr(GPtrArray) lines = g_ptr_array_new(); 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_common_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_util_warning_box_line(const gchar *start, const gchar *text, const gchar *end, const gchar *padding, guint width) { guint offset = 0; if (start != NULL) { offset += fu_common_strwidth(start); g_print("%s", start); } if (text != NULL) { offset += fu_common_strwidth(text); g_print("%s", text); } if (end != NULL) offset += fu_common_strwidth(end); for (guint i = offset; i < width; i++) g_print("%s", padding); if (end != NULL) g_print("%s\n", end); } void fu_util_warning_box(const gchar *title, const gchar *body, guint width) { /* nothing to do */ if (title == NULL && body == NULL) return; /* header */ fu_util_warning_box_line("╔", NULL, "╗", "═", width); /* optional title */ if (title != NULL) { g_autoptr(GPtrArray) lines = fu_util_strsplit_words(title, width - 4); for (guint j = 0; j < lines->len; j++) { const gchar *line = g_ptr_array_index(lines, j); fu_util_warning_box_line("║ ", line, " ║", " ", width); } } /* join */ if (title != NULL && body != NULL) fu_util_warning_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_util_strsplit_words(split[i], width - 4); if (lines == NULL) { if (has_nonempty) { fu_util_warning_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_util_warning_box_line("║ ", line, " ║", " ", width); } has_nonempty = TRUE; } } /* footer */ fu_util_warning_box_line("╚", NULL, "╝", "═", width); } gboolean fu_util_parse_filter_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; } 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; } 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_common_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 * * Since: 1.3.7 **/ 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 in to 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_SKIPS_RESTART) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) { return NULL; } 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; } gchar * fu_util_device_to_string(FwupdDevice *dev, guint idt) { FwupdUpdateState state; GPtrArray *guids = fwupd_device_get_guids(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); 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_debug("%s", debug_str); return NULL; } tmp = fwupd_device_get_name(dev); if (tmp == NULL) { /* TRANSLATORS: Name of hardware */ tmp = _("Unknown Device"); } fu_common_string_append_kv(str, idt, tmp, NULL); tmp = fwupd_device_get_id(dev); if (tmp != NULL) { /* TRANSLATORS: ID for hardware, typically a SHA1 sum */ fu_common_string_append_kv(str, idt + 1, _("Device ID"), tmp); } /* summary */ tmp = fwupd_device_get_summary(dev); if (tmp != NULL) { /* TRANSLATORS: one line summary of device */ fu_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv( str, idt + 1, /* TRANSLATORS: version number of previous firmware */ _("Previous version"), verstr->str); } else { /* TRANSLATORS: version number of current firmware */ fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, _("Minimum Version"), tmp); } tmp = fwupd_device_get_version_bootloader(dev); if (tmp != NULL) { /* TRANSLATORS: firmware version of bootloader */ fu_common_string_append_kv(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_common_strjoin_array(", ", vendor_ids); g_autofree gchar *both = g_strdup_printf("%s (%s)", tmp, strv); /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv(str, idt + 1, _("Vendor"), both); } else if (tmp != NULL) { /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv(str, idt + 1, _("Vendor"), tmp); } else if (vendor_ids->len > 0) { g_autofree gchar *strv = fu_common_strjoin_array("|", vendor_ids); /* TRANSLATORS: manufacturer of hardware */ fu_common_string_append_kv(str, idt + 1, _("Vendor"), strv); } /* branch */ if (fwupd_device_get_branch(dev) != NULL) { fu_common_string_append_kv( 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_common_string_append_kv(str, idt + 1, _("Install Duration"), time); } /* serial # */ tmp = fwupd_device_get_serial(dev); if (tmp != NULL) { /* TRANSLATORS: serial number of hardware */ fu_common_string_append_kv(str, idt + 1, _("Serial Number"), tmp); } /* update state */ state = fwupd_device_get_update_state(dev); if (state != FWUPD_UPDATE_STATE_UNKNOWN) { fu_common_string_append_kv(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_util_term_format(tmp, FU_UTIL_TERM_COLOR_BLUE); fu_common_string_append_kv( str, idt + 1, /* TRANSLATORS: helpful messages from last update */ _("Update Message"), color); } } } tmp = fwupd_device_get_update_error(dev); if (tmp != NULL) { g_autofree gchar *color = fu_util_term_format(tmp, FU_UTIL_TERM_COLOR_RED); /* TRANSLATORS: error message from last update attempt */ fu_common_string_append_kv(str, idt + 1, _("Update Error"), color); } /* 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_common_string_append_kv(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_common_string_append_kv( str, idt + 1, /* TRANSLATORS: global ID common to all similar hardware */ ngettext("GUID", "GUIDs", guids->len), guid_src); } else { fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, tmp, bullet); tmp = NULL; } else { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf("• %s", tmp2); fu_common_string_append_kv(str, idt + 1, "", bullet); } } 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_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_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_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"); } /* 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: return NULL; case FWUPD_PLUGIN_FLAG_NONE: case FWUPD_PLUGIN_FLAG_REQUIRE_HWID: return fu_util_term_format(fu_util_plugin_flag_to_string(plugin_flag), FU_UTIL_TERM_COLOR_GREEN); case FWUPD_PLUGIN_FLAG_DISABLED: case FWUPD_PLUGIN_FLAG_NO_HARDWARE: return fu_util_term_format(fu_util_plugin_flag_to_string(plugin_flag), FU_UTIL_TERM_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_KERNEL_TOO_OLD: 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_common_string_append_kv(str, idt, fwupd_plugin_get_name(plugin), NULL); /* TRANSLATORS: description of plugin state, e.g. disabled */ hdr = _("Flags"); if (flags == 0x0) { const gchar *tmp = fu_util_plugin_flag_to_cli_text(flags); g_autofree gchar *li = g_strdup_printf("• %s", tmp); fu_common_string_append_kv(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_common_string_append_kv(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 meatdata 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"); } /* fall back for unknown types */ return fwupd_release_flag_to_string(release_flag); } gchar * fu_util_release_to_string(FwupdRelease *rel, guint idt) { const gchar *title; const gchar *tmp2; GPtrArray *issues = fwupd_release_get_issues(rel); GPtrArray *tags = fwupd_release_get_tags(rel); GString *str = g_string_new(NULL); guint64 flags = fwupd_release_get_flags(rel); g_autofree gchar *desc_fb = NULL; g_return_val_if_fail(FWUPD_IS_RELEASE(rel), NULL); fu_common_string_append_kv(str, idt, fwupd_release_get_name(rel), NULL); /* TRANSLATORS: version number of new firmware */ fu_common_string_append_kv(str, idt + 1, _("New version"), fwupd_release_get_version(rel)); if (fwupd_release_get_remote_id(rel) != NULL) { fu_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv( 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_common_string_append_kv(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_common_string_append_kv( str, idt + 1, /* TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') */ _("Variant"), fwupd_release_get_name_variant_suffix(rel)); } fu_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv(str, idt + 1, _("Created"), tmp); } if (fwupd_release_get_urgency(rel) != FWUPD_RELEASE_URGENCY_UNKNOWN) { FwupdReleaseUrgency tmp = fwupd_release_get_urgency(rel); fu_common_string_append_kv(str, idt + 1, /* TRANSLATORS: how important the release is */ _("Urgency"), fu_util_release_urgency_to_string(tmp)); } if (fwupd_release_get_details_url(rel) != NULL) { fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, /* TRANSLATORS: source (as in code) link */ _("Source"), fwupd_release_get_source_url(rel)); } if (fwupd_release_get_vendor(rel) != NULL) { fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, _("Duration"), tmp); } if (fwupd_release_get_update_message(rel) != NULL) { fu_common_string_append_kv(str, idt + 1, /* TRANSLATORS: helpful messages for the update */ _("Update Message"), fwupd_release_get_update_message(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_common_string_append_kv(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_common_string_append_kv(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_common_string_append_kv( str, idt + 1, /* TRANSLATORS: issue fixed with the release, e.g. CVE */ ngettext("Issue", "Issues", issues->len), issue); } else { fu_common_string_append_kv(str, idt + 1, "", issue); } } if (tags->len > 0) { g_autofree gchar *tag_strs = fu_common_strjoin_array(", ", tags); fu_common_string_append_kv( str, idt + 1, /* TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 */ ngettext("Tag", "Tags", tags->len), tag_strs); } return g_string_free(str, FALSE); } gchar * fu_util_remote_to_string(FwupdRemote *remote, guint idt) { GString *str = g_string_new(NULL); FwupdRemoteKind kind = fwupd_remote_get_kind(remote); FwupdKeyringKind keyring_kind = fwupd_remote_get_keyring_kind(remote); const gchar *tmp; gint priority; g_return_val_if_fail(FWUPD_IS_REMOTE(remote), NULL); fu_common_string_append_kv(str, idt, fwupd_remote_get_title(remote), NULL); /* TRANSLATORS: remote identifier, e.g. lvfs-testing */ fu_common_string_append_kv(str, idt + 1, _("Remote ID"), fwupd_remote_get_id(remote)); /* TRANSLATORS: remote type, e.g. remote or local */ fu_common_string_append_kv(str, idt + 1, _("Type"), fwupd_remote_kind_to_string(kind)); if (keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { fu_common_string_append_kv(str, idt + 1, /* TRANSLATORS: keyring type, e.g. GPG or PKCS7 */ _("Keyring"), fwupd_keyring_kind_to_string(keyring_kind)); } fu_common_string_append_kv(str, idt + 1, /* TRANSLATORS: if the remote is enabled */ _("Enabled"), fwupd_remote_get_enabled(remote) ? "true" : "false"); tmp = fwupd_remote_get_checksum(remote); if (tmp != NULL) { /* TRANSLATORS: remote checksum */ fu_common_string_append_kv(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) { const gchar *unit = "s"; gdouble age = fwupd_remote_get_age(remote); g_autofree gchar *age_str = NULL; if (age > 60) { age /= 60.f; unit = "m"; } if (age > 60) { age /= 60.f; unit = "h"; } if (age > 24) { age /= 24.f; unit = "d"; } if (age > 7) { age /= 7.f; unit = "w"; } age_str = g_strdup_printf("%.2f%s", age, unit); /* TRANSLATORS: the age of the metadata */ fu_common_string_append_kv(str, idt + 1, _("Age"), 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_common_string_append_kv(str, idt + 1, _("Priority"), priority_str); } tmp = fwupd_remote_get_username(remote); if (tmp != NULL) { /* TRANSLATORS: remote filename base */ fu_common_string_append_kv(str, idt + 1, _("Username"), tmp); } tmp = fwupd_remote_get_password(remote); if (tmp != NULL) { g_autofree gchar *hidden = g_strnfill(fu_common_strwidth(tmp), '*'); /* TRANSLATORS: remote filename base */ fu_common_string_append_kv(str, idt + 1, _("Password"), hidden); } tmp = fwupd_remote_get_filename_cache(remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_common_string_append_kv(str, idt + 1, _("Filename"), tmp); } tmp = fwupd_remote_get_filename_cache_sig(remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, _("Filename Source"), tmp); } tmp = fwupd_remote_get_metadata_uri(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv(str, idt + 1, _("Metadata URI"), tmp); } tmp = fwupd_remote_get_metadata_uri_sig(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv(str, idt + 1, _("Metadata Signature"), tmp); } tmp = fwupd_remote_get_firmware_base_uri(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_common_string_append_kv(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_common_string_append_kv(str, idt + 1, _("Report URI"), tmp); fu_common_string_append_kv( str, idt + 1, /* TRANSLATORS: Boolean value to automatically send reports */ _("Automatic Reporting"), fwupd_remote_get_automatic_reports(remote) ? "true" : "false"); } return g_string_free(str, FALSE); } static void fu_security_attr_append_str(FwupdSecurityAttr *attr, GString *str, FuSecurityAttrToStringFlags flags) { g_autofree gchar *name = NULL; /* 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 = fu_security_attr_get_name(attr); if (name == NULL) name = g_strdup(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_common_strwidth(name); i < 30; i++) g_string_append(str, " "); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) { g_string_append_printf(str, "\033[37m\033[1m%s\033[0m", fu_security_attr_get_result(attr)); } else if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_string_append_printf(str, "\033[32m\033[1m%s\033[0m", fu_security_attr_get_result(attr)); } else { g_string_append_printf(str, "\033[31m\033[1m%s\033[0m", fu_security_attr_get_result(attr)); } 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) { struct { const gchar *appstream_id; FwupdSecurityAttrResult result_old; FwupdSecurityAttrResult result_new; const gchar *text; } items[] = {{"org.fwupd.hsi.Iommu", FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("IOMMU device protection enabled")}, {"org.fwupd.hsi.Iommu", FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, /* TRANSLATORS: HSI event title */ _("IOMMU device protection disabled")}, /* ------------------------------------------*/ {"org.fwupd.hsi.Fwupd.Plugins", FWUPD_SECURITY_ATTR_RESULT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, NULL}, {"org.fwupd.hsi.Fwupd.Plugins", FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_TAINTED, NULL}, {"org.fwupd.hsi.Fwupd.Plugins", FWUPD_SECURITY_ATTR_RESULT_UNKNOWN, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, NULL}, /* ------------------------------------------*/ {"org.fwupd.hsi.Kernel.Tainted", FWUPD_SECURITY_ATTR_RESULT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, /* TRANSLATORS: HSI event title */ _("Kernel is tainted")}, {"org.fwupd.hsi.Kernel.Tainted", FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_TAINTED, /* TRANSLATORS: HSI event title */ _("Kernel is no longer tainted")}, /* ------------------------------------------*/ {"org.fwupd.hsi.Kernel.Lockdown", FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Kernel lockdown disabled")}, {"org.fwupd.hsi.Kernel.Lockdown", FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Kernel lockdown enabled")}, /* ------------------------------------------*/ {"org.fwupd.hsi.AcpiDmar", FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Pre-boot DMA protection is disabled")}, {"org.fwupd.hsi.AcpiDmar", FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Pre-boot DMA protection is enabled")}, /* ------------------------------------------*/ {"org.fwupd.hsi.Uefi.SecureBoot", FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Secure Boot disabled")}, {"org.fwupd.hsi.Uefi.SecureBoot", FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Secure Boot enabled")}, /* ------------------------------------------*/ {"org.fwupd.hsi.Tpm.EmptyPcr", FWUPD_SECURITY_ATTR_RESULT_UNKNOWN, FWUPD_SECURITY_ATTR_RESULT_VALID, /* TRANSLATORS: HSI event title */ _("All TPM PCRs are valid")}, {"org.fwupd.hsi.Tpm.EmptyPcr", FWUPD_SECURITY_ATTR_RESULT_VALID, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, /* TRANSLATORS: HSI event title */ _("All TPM PCRs are now valid")}, {"org.fwupd.hsi.Uefi.SecureBoot", FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, FWUPD_SECURITY_ATTR_RESULT_VALID, /* TRANSLATORS: HSI event title */ _("A TPM PCR is now an invalid value")}, /* ------------------------------------------*/ {"org.fwupd.hsi.Tpm.ReconstructionPcr0", FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, /* TRANSLATORS: HSI event title */ _("TPM PCR0 reconstruction is invalid")}, {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) { 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"), fu_security_attr_get_name(attr), 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) { 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"), fu_security_attr_get_name(attr), fu_security_attr_result_to_string(fwupd_security_attr_get_result(attr))); } /* fall back to something sensible */ 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"), fu_security_attr_get_name(attr), 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_debug("%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; 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_util_term_format("✔", FU_UTIL_TERM_COLOR_GREEN); } else { check = fu_util_term_format("✘", FU_UTIL_TERM_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_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_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://github.com/fwupd/fwupd/wiki/Low-host-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://github.com/fwupd/fwupd/wiki/Host-security-ID-runtime-issues"); } 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://github.com/fwupd/fwupd/wiki/TPM-PCR0-differs-from-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_debug("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; } static gint fu_util_device_order_compare(FuDevice *device1, FuDevice *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; return 0; } gint fu_util_device_order_sort_cb(gconstpointer a, gconstpointer b) { FuDevice *device_a = *((FuDevice **)a); FuDevice *device_b = *((FuDevice **)b); return fu_util_device_order_compare(device_a, device_b); } gboolean fu_util_switch_branch_warning(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_util_warning_box(title, desc_full->str, 80); if (!assume_yes) { /* ask for permission */ g_print("\n%s [y|N]: ", /* TRANSLATORS: should the branch be changed */ _("Do you understand the consequences of changing the firmware branch?")); if (!fu_util_prompt_for_boolean(FALSE)) { 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(FwupdDevice *dev, GError **error) { 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.")); /* TRANSLATORS: title text, shown as a warning */ fu_util_warning_box(_("Full Disk Encryption Detected"), str->str, 80); /* ask for confirmation */ g_print("\n%s [Y|n]: ", /* TRANSLATORS: prompt to apply the update */ _("Perform operation?")); if (!fu_util_prompt_for_boolean(TRUE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } return TRUE; } void fu_util_show_unsupported_warn(void) { #ifndef SUPPORTED_BUILD g_autofree gchar *fmt = NULL; if (g_getenv("FWUPD_SUPPORTED") != NULL) return; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_YELLOW); g_printerr("%s %s\n", fmt, /* TRANSLATORS: unsupported build of the package */ _("This package has not been validated, it may not work properly.")); #endif } #ifdef HAVE_LIBCURL_7_62_0 G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) #endif gboolean fu_util_is_url(const gchar *perhaps_url) { #ifdef HAVE_LIBCURL_7_62_0 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_setup_interactive_console(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; } gboolean fu_util_print_builder(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 */ g_print("%s\n", data); return TRUE; } 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(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(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.7.5/src/fu-util-common.h000066400000000000000000000102341420024370600164620ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include #include "fwupd-security-attr-private.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; typedef enum { FU_UTIL_TERM_COLOR_BLACK = 30, FU_UTIL_TERM_COLOR_RED = 31, FU_UTIL_TERM_COLOR_GREEN = 32, FU_UTIL_TERM_COLOR_YELLOW = 33, FU_UTIL_TERM_COLOR_BLUE = 34, FU_UTIL_TERM_COLOR_MAGENTA = 35, FU_UTIL_TERM_COLOR_CYAN = 36, FU_UTIL_TERM_COLOR_WHITE = 37, } FuUtilTermColor; void fu_util_print_data(const gchar *title, const gchar *msg); gchar * fu_util_term_format(const gchar *text, FuUtilTermColor fg_color); guint fu_util_prompt_for_number(guint maxnum); gboolean fu_util_prompt_for_boolean(gboolean def); void fu_util_print_tree(GNode *n, gpointer data); gboolean fu_util_is_interesting_device(FwupdDevice *dev); gchar * fu_util_get_user_cache_path(const gchar *fn); void fu_util_warning_box(const gchar *title, const gchar *body, guint width); gboolean fu_util_prompt_warning(FwupdDevice *device, FwupdRelease *release, const gchar *machine, GError **error); gboolean fu_util_prompt_warning_fde(FwupdDevice *dev, GError **error); gboolean fu_util_prompt_complete(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); gchar * fu_util_release_get_name(FwupdRelease *release); const gchar * fu_util_branch_for_display(const gchar *branch); const gchar * fu_util_get_systemd_unit(void); gboolean fu_util_using_correct_daemon(GError **error); gboolean fu_util_parse_filter_flags(const gchar *filter, FwupdDeviceFlags *include, FwupdDeviceFlags *exclude, GError **error); gchar * fu_util_convert_description(const gchar *xml, GError **error); gchar * fu_util_time_to_str(guint64 tmp); gchar * fu_util_device_to_string(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_release_to_string(FwupdRelease *rel, guint idt); gchar * fu_util_remote_to_string(FwupdRemote *remote, guint idt); gchar * fu_util_security_attrs_to_string(GPtrArray *attrs, FuSecurityAttrToStringFlags flags); gchar * fu_util_security_events_to_string(GPtrArray *events, FuSecurityAttrToStringFlags flags); 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); gint fu_util_device_order_sort_cb(gconstpointer a, gconstpointer b); gboolean fu_util_switch_branch_warning(FwupdDevice *dev, FwupdRelease *rel, gboolean assume_yes, GError **error); void fu_util_show_unsupported_warn(void); gboolean fu_util_is_url(const gchar *perhaps_url); gboolean fu_util_setup_interactive_console(GError **error); gboolean fu_util_print_builder(JsonBuilder *builder, GError **error); gchar * fu_util_project_versions_to_string(GHashTable *metadata); gboolean fu_util_project_versions_as_json(GHashTable *metadata, GError **error); fwupd-1.7.5/src/fu-util.c000066400000000000000000004015141420024370600151740ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_GIO_UNIX #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-plugin-private.h" #include "fu-polkit-agent.h" #include "fu-progressbar.h" #include "fu-security-attrs.h" #include "fu-util-common.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif /* custom return code */ #define EXIT_NOTHING_TO_DO 2 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; GOptionContext *context; FwupdInstallFlags flags; FwupdClientDownloadFlags download_flags; FwupdClient *client; FuProgressbar *progressbar; gboolean no_remote_check; gboolean no_metadata_check; gboolean no_reboot_check; gboolean no_unreported_check; gboolean no_safety_check; 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_include; FwupdDeviceFlags filter_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_progressbar_update(priv->progressbar, 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_util_term_format(_("Action Required:"), FU_UTIL_TERM_COLOR_RED); tmp = g_strdup_printf("%s %s", fmt, fwupd_request_get_message(request)); fu_progressbar_set_title(priv->progressbar, tmp); } /* 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; /* 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 progressbar */ 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_progressbar_set_title(priv->progressbar, 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_progressbar_set_title(priv->progressbar, 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_progressbar_set_title(priv->progressbar, str); } else { g_warning("no FuUtilOperation set"); } g_set_object(&priv->current_device, device); } static gboolean fu_util_filter_device(FuUtilPrivate *priv, FwupdDevice *dev) { for (guint i = 0; i < 64; i++) { FwupdDeviceFlags flag = 1LLU << i; if (priv->filter_include & flag) { if (!fwupd_device_has_flag(dev, flag)) return FALSE; } if (priv->filter_exclude & flag) { if (fwupd_device_has_flag(dev, flag)) return FALSE; } } return TRUE; } 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 = g_ptr_array_new(); for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index(devices, i); if (!fu_util_filter_device(priv, dev)) continue; g_ptr_array_add(devices_filtered, dev); } /* nothing */ if (devices_filtered->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No supported devices"); return NULL; } /* exactly one */ if (devices_filtered->len == 1) { dev = g_ptr_array_index(devices_filtered, 0); /* TRANSLATORS: Device has been chosen by the daemon for the user */ g_print("%s: %s\n", _("Selected device"), fwupd_device_get_name(dev)); return g_object_ref(dev); } /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose a device:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < devices_filtered->len; i++) { dev = g_ptr_array_index(devices_filtered, i); g_print("%u.\t%s (%s)\n", i + 1, fwupd_device_get_id(dev), fwupd_device_get_name(dev)); } idx = fu_util_prompt_for_number(devices_filtered->len); 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_get_automatic_reports(remote); 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 (!fu_util_filter_device(priv, dev)) 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 */ g_print("________________________________________________\n"); /* failures */ if (devices_failed->len > 0) { /* TRANSLATORS: a list of failed updates */ g_print("\n%s\n\n", _("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); g_print(" • %s (%s → %s)\n", fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } } /* success */ if (devices_success->len > 0) { /* TRANSLATORS: a list of successful updates */ g_print("\n%s\n\n", _("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); g_print(" • %s (%s → %s)\n", fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } } /* ask for permission */ g_print("\n%s\n%s (%s) [Y|n]:\n", /* TRANSLATORS: explain why we want to upload */ _("Uploading firmware reports helps hardware vendors" " to quickly identify failing and successful updates" " on real devices."), /* TRANSLATORS: ask the user to upload */ _("Upload report now?"), /* TRANSLATORS: metadata is downloaded from the Internet */ _("Requires internet connection")); if (!fu_util_prompt_for_boolean(TRUE)) { g_print("\n%s [y|N]:\n", /* TRANSLATORS: offer to disable this nag */ _("Do you want to disable this feature for future updates?")); if (fu_util_prompt_for_boolean(FALSE)) { 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) { g_print("\n%s [y|N]:\n", /* TRANSLATORS: offer to stop asking the question */ _("Do you want to upload reports automatically for future updates?")); if (fu_util_prompt_for_boolean(FALSE)) { 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_get_automatic_reports(remote)) continue; if (!fwupd_client_modify_remote(priv->client, remote_id, "AutomaticReports", "true", priv->cancellable, error)) return FALSE; } } } /* success */ return TRUE; } static gboolean fu_util_modify_remote_warning(FuUtilPrivate *priv, FwupdRemote *remote, 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_util_warning_box(_("Enable new remote?"), warning_plain, 80); if (!priv->assume_yes) { /* ask for permission */ g_print("\n%s [Y|n]: ", /* TRANSLATORS: should the remote still be enabled */ _("Agree and enable the remote?")); if (!fu_util_prompt_for_boolean(TRUE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Declined agreement"); return FALSE; } } return TRUE; } static void fu_util_build_device_tree(FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FwupdDevice *dev) { for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index(devs, i); if (!fu_util_filter_device(priv, dev_tmp)) continue; if (!priv->show_all && !fu_util_is_interesting_device(dev_tmp)) continue; if (fwupd_device_get_parent(dev_tmp) == dev) { FwupdRelease *rel = fwupd_device_get_release_default(dev_tmp); GNode *child = g_node_append_data(root, dev_tmp); if (rel != NULL) g_node_append_data(child, rel); fu_util_build_device_tree(priv, child, devs, dev_tmp); } } } static gchar * fu_util_get_tree_title(FuUtilPrivate *priv) { return g_strdup(fwupd_client_get_host_product(priv->client)); } 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); 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(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); fwupd_device_add_release(dev, rel); } } /* add to builder */ json_builder_begin_object(builder); fwupd_device_to_json(dev, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(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; g_autofree gchar *title = fu_util_get_tree_title(priv); /* get results from daemon */ 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, NULL); if (g_node_n_children(root) == 0) { /* TRANSLATORS: nothing attached that can be upgraded */ g_print("%s\n", _("No hardware detected with firmware update capability")); return TRUE; } fu_util_print_tree(root, title); /* 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(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); g_print("%s\n", str); } if (plugins->len == 0) { /* TRANSLATORS: nothing found */ g_print("%s\n", _("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_common_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_common_set_contents_bytes(filename, blob, error)) return NULL; return g_steal_pointer(&filename); } static void fu_util_display_current_message(FuUtilPrivate *priv) { /* TRANSLATORS: success message */ g_print("%s\n", _("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); g_print("%s\n", fwupd_request_get_message(request)); } } typedef struct { guint nr_success; guint nr_failed; JsonBuilder *builder; const gchar *name; } 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 && !fu_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; /* TRANSLATORS: this is for the device tests */ msg = fu_util_term_format(_("Did not find any devices with matching GUIDs"), FU_UTIL_TERM_COLOR_RED); g_print("%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, fu_device_get_version(device)) != 0) { g_autofree gchar *str = NULL; str = g_strdup_printf("version did not match: got %s, expected %s", fu_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"), fu_device_get_version(device), version); msg = fu_util_term_format(str2, FU_UTIL_TERM_COLOR_RED); g_print("%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_util_term_format(_("OK!"), FU_UTIL_TERM_COLOR_GREEN); g_print("%s: %s\n", helper->name, msg); } helper->nr_success++; return TRUE; } static gboolean fu_util_device_test_step(FuUtilPrivate *priv, FuUtilDeviceTestHelper *helper, JsonObject *json_obj, GError **error) { JsonArray *json_array; const gchar *url; const gchar *baseuri = g_getenv("FWUPD_DEVICE_TESTS_BASE_URI"); g_autofree gchar *filename = NULL; g_autofree gchar *url_safe = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; /* 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"); if (baseuri != NULL) { g_autofree gchar *basename = g_path_get_basename(url); url_safe = g_build_filename(baseuri, basename, NULL); } else { url_safe = g_strdup(url); } filename = fu_util_download_if_required(priv, url_safe, error); if (filename == NULL) { g_prefix_error(error, "failed to download %s: ", url_safe); return FALSE; } /* log */ json_builder_set_member_name(helper->builder, "url"); json_builder_add_string_value(helper->builder, url_safe); /* 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); return TRUE; } if (!priv->as_json) { g_autofree gchar *msg = NULL; msg = fu_util_term_format(error_local->message, FU_UTIL_TERM_COLOR_RED); g_print("%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_device_test(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); FuUtilDeviceTestHelper helper = {.nr_failed = 0, .nr_success = 0, .builder = builder, .name = "Unknown"}; /* required for interactive devices */ g_signal_connect(FWUPD_CLIENT(priv->client), "device-request", G_CALLBACK(fu_util_update_device_request_cb), priv); /* 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(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_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_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; } blob = fwupd_client_download_bytes(priv->client, values[0], priv->download_flags, priv->cancellable, error); if (blob == NULL) return FALSE; basename = g_path_get_basename(values[0]); return g_file_set_contents(basename, g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), error); } static gboolean fu_util_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) { id = values[1]; dev = fu_util_get_device_by_id(priv, id, error); if (dev == NULL) return FALSE; } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; 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); /* 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(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->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(dev, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(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); g_autofree gchar *title = fu_util_get_tree_title(priv); /* 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, NULL); fu_util_print_tree(root, title); 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 *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 */ if (!priv->assume_yes && !fwupd_remote_get_automatic_reports(remote)) { fu_util_print_data(_("Target"), fwupd_remote_get_report_uri(remote)); fu_util_print_data(_("Payload"), data); if (sig != NULL) fu_util_print_data(_("Signature"), sig); g_print("%s [Y|n]: ", _("Proceed with upload?")); if (!fu_util_prompt_for_boolean(TRUE)) { 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, fwupd_remote_get_report_uri(remote), data, sig, &uri, error)) return FALSE; /* server wanted us to see a message */ if (uri != NULL) { g_print("%s %s\n", /* 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(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; 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 (!fu_util_filter_device(priv, dev)) continue; if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) 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 RemoteURI", fwupd_remote_get_report_uri(remote)); 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 */ if (g_hash_table_size(report_map) == 0) { 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)); g_print("%s\n", str->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); g_autofree gchar *title = fu_util_get_tree_title(priv); /* 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_devices_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 (!fu_util_filter_device(priv, dev)) 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 (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(root, title); 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 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_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: ", fu_device_get_name(dev)); return FALSE; } /* TRANSLATORS: success message when user refreshes device checksums */ g_print("%s\n", _("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; g_print("%s\n%s\n%s [Y|n]: ", /* TRANSLATORS: explain why no metadata available */ _("No remotes are currently enabled so no metadata is available."), /* TRANSLATORS: explain why no metadata available */ _("Metadata can be obtained from the Linux Vendor Firmware Service."), /* TRANSLATORS: Turn on the remote */ _("Enable this remote?")); if (!fu_util_prompt_for_boolean(TRUE)) 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, remote, error)) return FALSE; /* refresh the newly-enabled remote */ return fwupd_client_refresh_remote(priv->client, remote, 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_get_enabled(remote)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; checked = TRUE; 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; g_autoptr(GPtrArray) devs = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GString) str = g_string_new(NULL); /* metadata refreshed recently */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint64 age_oldest = 0; const guint64 age_limit_hours = 24; if (!fu_util_check_oldest_remote(priv, &age_oldest, error)) return FALSE; if (age_oldest < 60 * 60 * age_limit_hours) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message for a user who ran fwupdmgr refresh recently %1 is an already translated timestamp such as 6 hours or 15 seconds */ _("Firmware metadata last refresh: %s ago. " "Use --force to refresh again."), fu_util_time_to_str(age_oldest)); return FALSE; } } 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_get_enabled(remote)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; download_remote_enabled = TRUE; g_print("%s %s\n", _("Updating"), fwupd_remote_get_id(remote)); if (!fwupd_client_refresh_remote(priv->client, remote, priv->cancellable, error)) return FALSE; } /* 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; } /* 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); g_print("%s\n", 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 */ g_print("%s\n", _("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(res, builder); json_builder_end_object(builder); return fu_util_print_builder(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(rel, 0); g_print("%s", 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_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 */ g_print("%s\n", _("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 = fwupd_release_to_string(rel); g_print("%s\n", tmp); } } else { g_autoptr(GNode) root = g_node_new(NULL); g_autofree gchar *title = fu_util_get_tree_title(priv); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); g_node_append_data(root, rel); } fu_util_print_tree(root, title); } return TRUE; } static FwupdRelease * fu_util_prompt_for_release(FuUtilPrivate *priv, GPtrArray *rels, GError **error) { FwupdRelease *rel; guint idx; /* nothing */ if (rels->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No supported releases"); return NULL; } /* exactly one */ if (rels->len == 1) { rel = g_ptr_array_index(rels, 0); return g_object_ref(rel); } /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose a release:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, i); g_print("%u.\t%s\n", i + 1, fwupd_release_get_version(rel_tmp)); } idx = fu_util_prompt_for_number(rels->len); 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_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: ", fu_device_get_name(dev)); return FALSE; } /* TRANSLATORS: success message when user verified device checksums */ g_print("%s\n", _("Successfully verified device checksums")); return TRUE; } static gboolean fu_util_unlock(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_include |= FWUPD_DEVICE_FLAG_LOCKED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; 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; if (!fwupd_client_unlock(priv->client, fwupd_device_get_id(dev), priv->cancellable, error)) return FALSE; return fu_util_prompt_complete(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) { /* TRANSLATORS: the metadata is very out of date; %u is a number > 1 */ g_print(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); g_print("\n\n"); g_print("%s (%s) [y|N]: ", /* TRANSLATORS: ask the user if we can update the metadata */ _("Update now?"), /* TRANSLATORS: metadata is downloaded from the Internet */ _("Requires internet connection")); if (!fu_util_prompt_for_boolean(FALSE)) 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); fwupd_device_add_release(dev, rel); } /* add to builder */ json_builder_begin_object(builder); fwupd_device_to_json(dev, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(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_autofree gchar *title = fu_util_get_tree_title(priv); gboolean no_updates_header = FALSE; gboolean latest_header = FALSE; /* 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)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!no_updates_header) { /* TRANSLATORS: message letting the user know no device upgrade * available due to missing on LVFS */ g_printerr("%s\n", _("Devices with no available firmware updates: ")); no_updates_header = TRUE; } g_printerr(" • %s\n", fwupd_device_get_name(dev)); continue; } if (!fu_util_filter_device(priv, 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) { if (!latest_header) { /* TRANSLATORS: message letting the user know no device upgrade * available */ g_printerr( "%s\n", _("Devices with the latest available firmware version:")); latest_header = TRUE; } g_printerr(" • %s\n", fwupd_device_get_name(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); g_node_append_data(child, g_object_ref(rel)); } } /* 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, "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, _("No updates available for remaining devices")); return FALSE; } fu_util_print_tree(root, title); /* 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(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; g_autofree gchar *title = fu_util_get_tree_title(priv); 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 */ g_print("%s\n", _("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(root, title); return TRUE; } static FwupdRelease * fu_util_get_release_with_tag(FuUtilPrivate *priv, FwupdDevice *dev, const gchar *tag, 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_has_tag(rel, tag)) 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_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-bkc` */ _("This device will be reverted back to %s when the %s command is performed."), fwupd_release_get_version(rel), "fwupdmgr sync-bkc"); /* 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 */ fu_util_warning_box(_("Deviate from the best known configuration?"), str->str, 80); /* ask for confirmation */ g_print("\n%s [Y|n]: ", /* TRANSLATORS: prompt to apply the update */ _("Perform operation?")); if (!fu_util_prompt_for_boolean(TRUE)) { 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 = fu_util_get_tree_title(priv); if (!fu_util_prompt_warning(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 (!priv->no_safety_check && !priv->assume_yes) { g_autofree gchar *title = fu_util_get_tree_title(priv); if (!fu_util_prompt_warning(dev, rel, title, error)) return FALSE; if (!fu_util_prompt_warning_fde(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, const gchar *remote_id, GError **error) { g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error_local = NULL; if (remote_id == NULL) { g_debug("not sending reports, no remote"); return TRUE; } remote = fwupd_client_get_remote_by_id(priv->client, remote_id, priv->cancellable, error); if (remote == NULL) return FALSE; if (fwupd_remote_get_automatic_reports(remote)) { 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_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); 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); const gchar *remote_id; 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)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!no_updates_header) { g_printerr("%s\n", /* 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; } g_printerr(" • %s\n", 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 (!fu_util_filter_device(priv, 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) { if (!latest_header) { g_printerr( "%s\n", /* TRANSLATORS: message letting the user know no device upgrade * available */ _("Devices with the latest available firmware version:")); latest_header = TRUE; } g_printerr(" • %s\n", 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 (!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 */ remote_id = fwupd_release_get_remote_id(rel); if (!fu_util_maybe_send_reports(priv, remote_id, 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->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 */ g_print("%s\n", _("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, remote, 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 */ g_print("%s\n", _("Successfully enabled remote")); return TRUE; } if (!priv->assume_yes) { g_print("%s (%s) [Y|n]: ", /* TRANSLATORS: ask the user if we can update the metadata */ _("Do you want to refresh this remote now?"), /* TRANSLATORS: metadata is downloaded from the Internet */ _("Requires internet connection")); if (!fu_util_prompt_for_boolean(TRUE)) { /* TRANSLATORS: success message */ g_print("%s\n", _("Successfully enabled remote")); return TRUE; } } if (!fwupd_client_refresh_remote(priv->client, remote, priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message */ g_print("\n%s\n", _("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 */ g_print("%s\n", _("Successfully disabled remote")); return TRUE; } static gboolean fu_util_downgrade(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *remote_id; 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_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; 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); 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 */ remote_id = fwupd_release_get_remote_id(rel); if (!fu_util_maybe_send_reports(priv, remote_id, 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->completion_flags, TRUE, error); } static gboolean fu_util_reinstall(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *remote_id; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(FwupdDevice) dev = NULL; priv->filter_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 */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return FALSE; for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (fu_common_vercmp_full(fwupd_release_get_version(rel_tmp), fu_device_get_version(dev), fwupd_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(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); 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 */ remote_id = fwupd_release_get_remote_id(rel); if (!fu_util_maybe_send_reports(priv, remote_id, 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->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 *remote_id; 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_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; priv->filter_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 GLIB_CHECK_VERSION(2, 54, 3) if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL)) continue; #endif 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: get interactive prompt, where branch is the * supplier of the firmware, e.g. "non-free" or "free" */ g_print("%s\n", _("Choose a branch:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < branches->len; i++) { const gchar *branch_tmp = g_ptr_array_index(branches, i); g_print("%u.\t%s\n", i + 1, fu_util_branch_for_display(branch_tmp)); } idx = fu_util_prompt_for_number(branches->len); 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(dev, rel, priv->assume_yes, error)) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; 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); 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 */ remote_id = fwupd_release_get_remote_id(rel); if (!fu_util_maybe_send_reports(priv, remote_id, 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->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++) { FuDevice *device = g_ptr_array_index(devices, i); if (fu_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 */ /* order by device priority */ g_ptr_array_sort(devices, fu_util_device_order_sort_cb); for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); if (!fu_util_filter_device(priv, device)) continue; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; g_print("%s %s…\n", /* 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 */ g_print("%s\n", _("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(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 */ g_print("%s\n", _("There is no approved firmware.")); } else { g_print( "%s\n", /* 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++) g_print(" * %s\n", 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) { g_print("%s [Y|n]: ", /* TRANSLATORS: configuration changes only take effect on restart */ _("Restart the daemon to make the change effective?")); if (!fu_util_prompt_for_boolean(FALSE)) return TRUE; } #ifdef HAVE_SYSTEMD if (!fu_systemd_unit_stop(fu_util_get_systemd_unit(), error)) return FALSE; #endif /* TRANSLATORS: success message -- a per-system setting value */ g_print("%s\n", _("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_get_enabled(remote)) 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_get_automatic_security_reports(remote)) { g_autofree gchar *tmp = NULL; /* TRANSLATORS: ask the user to share, %s is something like: * "Linux Vendor Firmware Service" */ tmp = g_strdup_printf("Upload these anonymous results to the %s to help other users?", fwupd_remote_get_title(remote)); g_print("\n%s [y|N]: ", tmp); if (!fu_util_prompt_for_boolean(FALSE)) { g_print("%s [Y|n]: ", /* TRANSLATORS: stop nagging the user */ _("Ask again next time?")); if (!fu_util_prompt_for_boolean(TRUE)) { 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_get_automatic_security_reports(remote)) { fu_util_print_data(_("Target"), fwupd_remote_get_security_report_uri(remote)); fu_util_print_data(_("Payload"), data); if (sig != NULL) fu_util_print_data(_("Signature"), sig); g_print("%s [Y|n]: ", _("Proceed with upload?")); if (!fu_util_prompt_for_boolean(TRUE)) { 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; /* TRANSLATORS: success, so say thank you to the user */ g_print("%s\n", "Host Security ID attributes uploaded successfully, thanks!"); /* as this worked, ask if the user want to do this every time */ if (!fwupd_remote_get_automatic_security_reports(remote)) { g_print("%s [y|N]: ", /* TRANSLATORS: can we JFDI? */ _("Automatically upload every time?")); if (fu_util_prompt_for_boolean(FALSE)) { 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, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); /* attrs */ json_builder_set_member_name(builder, "HostSecurityAttributes"); 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, "HostSecurityEvents"); 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); return fu_util_print_builder(builder, error); } static gboolean fu_util_sync_bkc(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; 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); priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; /* for each device, find the release that matches the tag */ if (host_bkc == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No HostBkc set in daemon.conf"); return FALSE; } 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; rel = fu_util_get_release_with_tag(priv, dev, host_bkc, &error_local); 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)) 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 require modifications for target %s", host_bkc); 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->completion_flags, TRUE, error); } 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) events = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *str = NULL; /* not ready yet */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "The HSI specification is not yet complete. " "To ignore this warning, use --force"); return FALSE; } /* 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; } } /* not for human consumption */ if (priv->as_json) return fu_util_security_as_json(priv, attrs, events, error); g_print("%s \033[1m%s\033[0m\n", /* 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); g_print("%s\n", str); /* events */ if (events != NULL && events->len > 0) { g_autofree gchar *estr = fu_util_security_events_to_string(events, flags); if (estr != NULL) g_print("%s\n", estr); } /* opted-out */ if (priv->no_unreported_check) return TRUE; /* upload, with confirmation */ return fu_util_upload_security(priv, attrs, error); } 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_context_unref(priv->main_ctx); g_object_unref(priv->cancellable); g_object_unref(priv->progressbar); 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 = fu_common_get_path(FU_PATH_KIND_POLKIT_ACTIONS); g_autofree gchar *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 */ g_print("%s %s\n", _("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 */ g_print("%s %s\n", _("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 */ g_print("%s\n", _("There are no blocked firmware files")); return TRUE; } /* TRANSLATORS: there follows a list of hashes */ g_print("%s\n", _("Blocked firmware files:")); for (guint i = 0; csums[i] != NULL; i++) { g_print("%u.\t%s\n", 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; /* print */ for (guint i = 0; i < 64; i++) { FwupdPluginFlags flag = (guint64)1 << i; const gchar *tmp; g_autofree gchar *fmt = NULL; 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; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_string_append_printf(str, "%s %s\n", fmt, tmp); url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s", fwupd_plugin_flag_to_string(flag)); g_string_append(str, " "); /* TRANSLATORS: %s is a link to a website */ g_string_append_printf(str, _("See %s for more information."), url); g_string_append(str, "\n"); g_printerr("%s", str->str); } } 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(metadata, error); str = fu_util_project_versions_to_string(metadata); g_print("%s", str); return TRUE; } int main(int argc, char *argv[]) { gboolean force = FALSE; gboolean allow_branch_switch = FALSE; gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean enable_ipfs = FALSE; gboolean is_interactive = FALSE; gboolean no_history = 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 = NULL; const GOptionEntry options[] = {{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ _("Show extra debugging information"), NULL}, {"version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ _("Show client and daemon versions"), NULL}, {"offline", '\0', 0, G_OPTION_ARG_NONE, &offline, /* TRANSLATORS: command line option */ _("Schedule installation for next reboot when possible"), NULL}, {"allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ _("Allow reinstalling existing firmware versions"), NULL}, {"allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ _("Allow downgrading firmware versions"), NULL}, {"allow-branch-switch", '\0', 0, G_OPTION_ARG_NONE, &allow_branch_switch, /* TRANSLATORS: command line option */ _("Allow switching firmware branch"), NULL}, {"force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ _("Force the action by relaxing some runtime checks"), NULL}, {"assume-yes", 'y', 0, G_OPTION_ARG_NONE, &priv->assume_yes, /* TRANSLATORS: command line option */ _("Answer yes to all questions"), NULL}, {"sign", '\0', 0, G_OPTION_ARG_NONE, &priv->sign, /* TRANSLATORS: command line option */ _("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 */ _("Do not check for unreported history"), NULL}, {"no-metadata-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_metadata_check, /* TRANSLATORS: command line option */ _("Do not check for old metadata"), NULL}, {"no-remote-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_remote_check, /* TRANSLATORS: command line option */ _("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 */ _("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 */ _("Do not perform device safety checks"), NULL}, {"no-history", '\0', 0, G_OPTION_ARG_NONE, &no_history, /* TRANSLATORS: command line option */ _("Do not write to the history database"), NULL}, {"show-all", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ _("Show all results"), NULL}, {"show-all-devices", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ _("Show devices that are not updatable"), NULL}, {"disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ _("Ignore SSL strict checks when downloading files"), NULL}, {"ipfs", '\0', 0, G_OPTION_ARG_NONE, &enable_ipfs, /* TRANSLATORS: command line option */ _("Only use IPFS when downloading files"), NULL}, {"filter", '\0', 0, G_OPTION_ARG_STRING, &filter, /* TRANSLATORS: command line option */ _("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL}, {"json", '\0', 0, G_OPTION_ARG_NONE, &priv->as_json, /* TRANSLATORS: command line option */ _("Output in JSON format"), NULL}, {NULL}}; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); /* ensure D-Bus errors are registered */ fwupd_error_quark(); /* this is an old command which is possibly a symlink */ if (g_str_has_suffix(argv[0], "fwupdagent")) { 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->progressbar = fu_progressbar_new(); priv->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_progressbar_set_main_context(priv->progressbar, 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, "install", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Install a firmware file on this hardware"), fu_util_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 daemon.conf */ _("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-bkc", NULL, /* TRANSLATORS: command description */ _("Sync firmware versions to the host best known configuration"), fu_util_sync_bkc); 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); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new(); fu_util_setup_signal_handlers(priv); /* sort by command name */ fu_util_cmd_array_sort(cmd_array); /* 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) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { g_autofree gchar *fmt = NULL; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_printerr("%s %s\n", fmt, /* TRANSLATORS: try to help */ _("Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); 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) { g_autofree gchar *fmt = NULL; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_printerr("%s %s\n", fmt, /* TRANSLATORS: try to help */ _("The system clock has not been set " "correctly and downloading files may fail.")); } /* non-TTY consoles cannot answer questions */ if (!fu_util_setup_interactive_console(&error_console)) { g_debug("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; } else { is_interactive = TRUE; } fu_progressbar_set_interactive(priv->progressbar, is_interactive); /* parse filter flags */ if (filter != NULL) { if (!fu_util_parse_filter_flags(filter, &priv->filter_include, &priv->filter_exclude, &error)) { g_print("%s: %s\n", /* TRANSLATORS: the user didn't read the man page */ _("Failed to parse flags for --filter"), error->message); return EXIT_FAILURE; } } /* set verbose? */ if (verbose) { g_setenv("G_MESSAGES_DEBUG", "all", FALSE); 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 IPFS for metadata and firmware *only* if specified */ if (enable_ipfs) priv->download_flags |= FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_IPFS; #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)) { g_printerr("Failed to open polkit agent: %s\n", 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); /* show a warning if the daemon is tainted */ if (!fwupd_client_connect(priv->client, priv->cancellable, &error)) { g_printerr("Failed to connect to daemon: %s\n", error->message); return EXIT_FAILURE; } if (fwupd_client_get_tainted(priv->client)) { g_autofree gchar *fmt = NULL; /* TRANSLATORS: this is a prefix on the console */ fmt = fu_util_term_format(_("WARNING:"), FU_UTIL_TERM_COLOR_RED); g_printerr("%s %s\n", fmt, /* 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)) { g_printerr("%s\n", error->message); 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_warn(); /* we know the runtime daemon version now */ fwupd_client_set_user_agent_for_package(priv->client, "fwupdmgr", 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)) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } #ifdef HAVE_SYSTEMD /* make sure the correct daemon is in use */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fwupd_client_get_daemon_interactive(priv->client) && !fu_util_using_correct_daemon(&error)) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } #endif /* make sure polkit actions were installed */ if (!fu_util_check_polkit_actions(&error)) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } /* send our implemented feature set */ if (is_interactive) { if (!fwupd_client_set_feature_flags( priv->client, FWUPD_FEATURE_FLAG_CAN_REPORT | FWUPD_FEATURE_FLAG_SWITCH_BRANCH | FWUPD_FEATURE_FLAG_REQUESTS | FWUPD_FEATURE_FLAG_UPDATE_ACTION | FWUPD_FEATURE_FLAG_FDE_WARNING | FWUPD_FEATURE_FLAG_DETACH_ACTION | FWUPD_FEATURE_FLAG_COMMUNITY_TEXT, priv->cancellable, &error)) { g_printerr("Failed to set front-end features: %s\n", error->message); return EXIT_FAILURE; } } /* run the specified command */ ret = fu_util_cmd_array_run(cmd_array, priv, argv[1], (gchar **)&argv[2], &error); if (!ret) { g_printerr("%s\n", error->message); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { /* TRANSLATORS: error message explaining command on how to get help */ g_printerr("\n%s\n", _("Use fwupdmgr --help for help")); } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("%s\n", error->message); return EXIT_NOTHING_TO_DO; } return EXIT_FAILURE; } #ifdef HAVE_POLKIT /* stop listening for polkit questions */ fu_polkit_agent_close(); #endif /* success */ return EXIT_SUCCESS; } fwupd-1.7.5/src/fwupd.gresource.xml000066400000000000000000000003251420024370600173020ustar00rootroot00000000000000 org.freedesktop.fwupd.xml fwupd-1.7.5/src/fwupdagent.1000066400000000000000000000011301420024370600156570ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdagent man page" .SH NAME fwupdagent \- firmware updating agent .SH SYNOPSIS fwupdagent [CMD] .SH DESCRIPTION fwupdagent used to be a command line fwupd client intended to be used by scripts. You should now use the 100% compatible \fBfwupdmgr --json\fR command instead. The output is JSON and guaranteed to be stable. .SH EXIT STATUS Commands that successfully execute will return "0". .SH SEE ALSO fwupdmgr(1), fwupdtool(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/src/fwupdmgr.1000066400000000000000000000013541420024370600153560ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdmgr man page" .SH NAME fwupdmgr \- firmware update manager client utility .SH SYNOPSIS fwupdmgr [CMD] .SH DESCRIPTION fwupdmgr is a command line fwupd client intended to be used interactively. The output between versions of fwupd is not guaranteed to be stable. .SH OPTIONS The fwupdmgr command takes various options depending on the action. Run \fBfwupdmgr --help\fR for the full list. .SH EXIT STATUS Commands that successfully execute will return "0", but commands that have no actions but successfully execute will return "2". .SH SEE ALSO fwupdagent(1), fwupdtool(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/src/fwupdtool.1000066400000000000000000000017011420024370600155420ustar00rootroot00000000000000.\" Report problems in https://github.com/fwupd/fwupd .TH man 1 "11 April 2021" @PACKAGE_VERSION@ "fwupdtool man page" .SH NAME fwupdtool \- standalone firmware update utility .SH SYNOPSIS fwupdtool [CMD] .SH DESCRIPTION This tool allows an administrator to use the fwupd plugins without being installed on the host system. .PP Additionally \fBdfu-tool\fR 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. .SH OPTIONS The fwupdtool command takes various options depending on the action. Run \fBfwupdtool --help\fR for the full list. .SH EXIT STATUS Commands that successfully execute will return "0", but commands that have no actions but successfully execute will return "2". .SH SEE ALSO fwupdagent(1), fwupdmgr(1) .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) fwupd-1.7.5/src/meson.build000066400000000000000000000116601420024370600156040ustar00rootroot00000000000000if get_option('tests') subdir('tests') endif if build_daemon and get_option('introspection') install_data(['org.freedesktop.fwupd.xml'], install_dir : join_paths(datadir, 'dbus-1', 'interfaces') ) endif client_src = [] systemd_src = [] daemon_dep = [ libjcat, libxmlb, libgcab, giounix, gmodule, gudev, libjsonglib, ] client_dep = [ gudev, ] if get_option('gusb') daemon_dep += gusb client_dep += gusb endif if get_option('libarchive') daemon_dep += libarchive endif if get_option('sqlite') daemon_dep += sqlite client_dep += sqlite endif if get_option('systemd') systemd_src += 'fu-systemd.c' endif if build_daemon and get_option('polkit') client_src += 'fu-polkit-agent.c' daemon_dep += polkit endif daemon_src = [ 'fu-config.c', 'fu-debug.c', 'fu-device-list.c', 'fu-engine.c', 'fu-engine-helper.c', 'fu-engine-request.c', 'fu-history.c', 'fu-idle.c', 'fu-install-task.c', 'fu-keyring-utils.c', 'fu-plugin-list.c', 'fu-remote-list.c', 'fu-security-attr.c', ] + systemd_src if get_option('gudev') daemon_src += 'fu-udev-backend.c' endif if get_option('gusb') daemon_src += 'fu-usb-backend.c' endif if get_option('bluez') daemon_src += 'fu-bluez-backend.c' endif if build_daemon fwupdmgr = executable( 'fwupdmgr', fu_hash, sources : [ 'fu-util.c', 'fu-history.c', 'fu-progressbar.c', 'fu-security-attr.c', 'fu-util-common.c', client_src, systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libfwupd_deps, libxmlb, client_dep, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : bindir ) # for compatibility if get_option('compat_cli') meson.add_install_script( 'sh', '-c', 'ln -fs fwupdmgr @0@@1@'.format( '${DESTDIR}', join_paths(get_option('prefix'), get_option('bindir'), 'fwupdagent'))) endif endif if get_option('systemd') and get_option('offline') fwupdoffline = executable( 'fwupdoffline', fu_hash, sources : [ 'fu-history.c', 'fu-offline.c', 'fu-security-attr.c', 'fu-util-common.c', systemd_src ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libfwupd_deps, client_dep, libxmlb, ], link_with : [ fwupd, fwupdplugin, ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif resources_src = gnome.compile_resources( 'fwupd-resources', 'fwupd.gresource.xml', source_dir : '.', c_name : 'fu' ) fwupdtool = executable( 'fwupdtool', resources_src, fu_hash, export_dynamic : true, sources : [ 'fu-tool.c', 'fu-progressbar.c', 'fu-util-common.c', daemon_src, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ libfwupd_deps, libxmlb, libgcab, client_dep, valgrind, ], link_with : [ fwupd, fwupdplugin ], install : true, install_dir : bindir ) if get_option('man') if build_daemon configure_file( input : 'fwupdmgr.1', output : 'fwupdmgr.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) configure_file( input : 'fwupdagent.1', output : 'fwupdagent.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_standalone configure_file( input : 'fwupdtool.1', output : 'fwupdtool.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif endif if build_daemon executable( 'fwupd', resources_src, fu_hash, sources : [ 'fu-main.c', daemon_src, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ valgrind, libsystemd, daemon_dep, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ '-DFU_OFFLINE_DESTDIR=""', ], install : true, install_dir : join_paths(libexecdir, 'fwupd') ) endif if get_option('tests') test_deps += colorhug_test_firmware test_deps += hwid_test_firmware test_deps += multiple_rels_test_firmware test_deps += noreqs_test_firmware test_deps += builder_test_firmware 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', resources_src, test_deps, fu_hash, sources : [ 'fu-progressbar.c', 'fu-self-test.c', daemon_src, ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies : [ daemon_dep, ], link_with : [ fwupd, fwupdplugin ], c_args : [ ], ) test('fu-self-test', e, is_parallel:false, timeout:180, env : env) endif fwupd-1.7.5/src/org.freedesktop.fwupd.xml000066400000000000000000000655071420024370600204220ustar00rootroot00000000000000 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. 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. 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. 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. Ask the daemon to quit. This can only be called by the root user. 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.7.5/src/tests/000077500000000000000000000000001420024370600146005ustar00rootroot00000000000000fwupd-1.7.5/src/tests/colorhug/000077500000000000000000000000001420024370600164225ustar00rootroot00000000000000fwupd-1.7.5/src/tests/colorhug/colorhug-als-3.0.2.cab000066400000000000000000000232171420024370600221330ustar00rootroot00000000000000MSCF&,@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.7.5/src/tests/colorhug/firmware.bin000077700000000000000000000000001420024370600321112../../../libfwupdplugin/tests/colorhug/firmware.binustar00rootroot00000000000000fwupd-1.7.5/src/tests/daemon.conf000077700000000000000000000000001420024370600223562../../data/daemon.confustar00rootroot00000000000000fwupd-1.7.5/src/tests/history_v1.db000066400000000000000000000300001420024370600172070ustar00rootroot00000000000000SQLite 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.7.5/src/tests/meson.build000066400000000000000000000000571420024370600167440ustar00rootroot00000000000000subdir('missing-hwid') subdir('multiple-rels') fwupd-1.7.5/src/tests/metadata.xml000066400000000000000000000007731420024370600171110ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    fwupd-1.7.5/src/tests/missing-hwid/000077500000000000000000000000001420024370600172025ustar00rootroot00000000000000fwupd-1.7.5/src/tests/missing-hwid/firmware.bin000066400000000000000000000175001420024370600215130ustar00rootroot000000000000001 (~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.7.5/src/tests/missing-hwid/firmware.metainfo.xml000066400000000000000000000007041420024370600233420ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 9342d47a-1bab-5709-9869-c840b2eac501 fwupd-1.7.5/src/tests/missing-hwid/firmware2.metainfo.xml000066400000000000000000000005531420024370600234260ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.7.5/src/tests/missing-hwid/meson.build000066400000000000000000000007231420024370600213460ustar00rootroot00000000000000hwid_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.7.5/src/tests/multiple-rels/000077500000000000000000000000001420024370600173765ustar00rootroot00000000000000fwupd-1.7.5/src/tests/multiple-rels/firmware-123.bin000066400000000000000000000000121420024370600222000ustar00rootroot000000000000000x1020003 fwupd-1.7.5/src/tests/multiple-rels/firmware-124.bin000066400000000000000000000000121420024370600222010ustar00rootroot000000000000000x1020004 fwupd-1.7.5/src/tests/multiple-rels/firmware.metainfo.xml000066400000000000000000000010731420024370600235360ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.7.5/src/tests/multiple-rels/meson.build000066400000000000000000000004351420024370600215420ustar00rootroot00000000000000multiple_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.7.5/src/tests/remotes.d/000077500000000000000000000000001420024370600165005ustar00rootroot00000000000000fwupd-1.7.5/src/tests/remotes.d/broken.conf000066400000000000000000000001221420024370600206220ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/broken.xml.gz fwupd-1.7.5/src/tests/remotes.d/directory.conf000066400000000000000000000001411420024370600213470ustar00rootroot00000000000000[fwupd Remote] Enabled=true Keyring=none MetadataURI=file:///tmp/fwupd-self-test/var/cache/fwupd fwupd-1.7.5/src/tests/remotes.d/stable.conf000066400000000000000000000001171420024370600206200ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/stable.xml fwupd-1.7.5/src/tests/remotes.d/testing.conf000066400000000000000000000001461420024370600210250ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/testing.xml ApprovalRequired=true fwupd-1.7.5/subprojects/000077500000000000000000000000001420024370600152125ustar00rootroot00000000000000fwupd-1.7.5/subprojects/.gitignore000066400000000000000000000000671420024370600172050ustar00rootroot00000000000000gusb gcab gi-docgen flashrom libjcat libxmlb fwupd-efi fwupd-1.7.5/subprojects/flashrom.wrap000066400000000000000000000001331420024370600177150ustar00rootroot00000000000000[wrap-git] directory = flashrom url = https://github.com/flashrom/flashrom revision = v1.2 fwupd-1.7.5/subprojects/fwupd-efi.wrap000066400000000000000000000001311420024370600177660ustar00rootroot00000000000000[wrap-git] directory = fwupd-efi url = https://github.com/fwupd/fwupd-efi revision = 1.2 fwupd-1.7.5/subprojects/gcab.wrap000066400000000000000000000001761420024370600170050ustar00rootroot00000000000000[wrap-git] directory = gcab url = https://gitlab.gnome.org/GNOME/gcab.git revision = b55268ac1020cd6c033acb52f2e6ae984bf5c9fd fwupd-1.7.5/subprojects/gi-docgen.wrap000066400000000000000000000001501420024370600177350ustar00rootroot00000000000000[wrap-git] directory=gi-docgen url=https://gitlab.gnome.org/GNOME/gi-docgen.git revision=2021.8 depth=1 fwupd-1.7.5/subprojects/gusb.wrap000066400000000000000000000001331420024370600170420ustar00rootroot00000000000000[wrap-git] directory = gusb url = https://github.com/hughsie/libgusb.git revision = 0.3.10 fwupd-1.7.5/subprojects/libjcat.wrap000066400000000000000000000001351420024370600175140ustar00rootroot00000000000000[wrap-git] directory = libjcat url = https://github.com/hughsie/libjcat.git revision = 0.1.9 fwupd-1.7.5/subprojects/libxmlb.wrap000066400000000000000000000001351420024370600175350ustar00rootroot00000000000000[wrap-git] directory = libxmlb url = https://github.com/hughsie/libxmlb.git revision = 0.3.6