├── .cargo └── config.toml ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── Cargo.toml ├── DESIGN.md ├── LICENSE.txt ├── README.md ├── TODO.md ├── examples └── fromyaml │ └── main.rs ├── rust-toolchain.toml ├── rustfmt.toml ├── src ├── apcb.rs ├── entry.rs ├── group.rs ├── lib.rs ├── naples.rs ├── ondisk.rs ├── serializers.rs ├── struct_accessors.rs ├── struct_variants_enum.rs ├── tests.rs ├── token_accessors.rs ├── tokens_entry.rs └── types.rs ├── tests ├── compat.rs └── headers.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./xtask/Cargo.toml --" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: amd-apcb 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - "**" 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | test: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Run tests 20 | run: cargo xtask tests 21 | 22 | format: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: mbrobbel/rustfmt-check@master 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | lint: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - run: rustup component add clippy 35 | - uses: actions-rs/clippy-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | args: --all-features 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /xtask/target 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = [ "xtask" ] } 2 | 3 | [package] 4 | name = "amd-apcb" 5 | version = "0.5.2" 6 | authors = ["Oxide Computer"] 7 | edition = "2024" 8 | license = "MPL-2.0" 9 | 10 | [dependencies] 11 | # newer one than the one in zerocopy--required for WriteBytesExt (used by Parameters). 12 | byteorder = { version = "1.4", default-features = false } 13 | four-cc = { version = "0.4", default-features = false } 14 | memoffset = "0.9" 15 | modular-bitfield = { version = "0.11", default-features = false } 16 | num-derive = { version = "0.4", features = [ ] } 17 | num-traits = { version = "0.2", default-features = false } 18 | paste = "1.0" 19 | static_assertions = "1.1" 20 | zerocopy = { version = "0.8", features = ["derive"] } 21 | serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } 22 | serde-hex = { version = "0.1", optional = true } 23 | schemars = { version = "0.8", optional = true } 24 | parse_int = { version = "0.9", optional = true } 25 | thiserror = { version = "2.0", optional = true } 26 | 27 | [features] 28 | default = ["std"] 29 | std = ["byteorder/std", "four-cc/std", "thiserror"] 30 | schemars = ["std", "dep:schemars", "four-cc/schemars"] 31 | serde = ["std", "dep:serde", "dep:parse_int", "four-cc/serde"] 32 | serde-hex = ["std", "dep:serde", "dep:serde-hex"] 33 | 34 | [dev-dependencies] 35 | serde_yaml = "0.9" # for the example 36 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Implementation design decisions 2 | 3 | * It's meant to run in a no_std, no_alloc environment. That means that almost all serialization crates are disqualifi 4 | ed, leaving only `ssmarshal` and maybe `packed_struct`. `binread` would be otherwise nice, but it is `alloc`. 5 | 6 | # Observations 7 | 8 | APCB V3 TOKEN type_id are NOT unique per group. Maybe unique (board_mask, type_id)--maybe not. 9 | The other APCB elements have unique type_id. 10 | 11 | # Modification actions that are supported 12 | 13 | * Creating a new group 14 | * Deleting a group from APCB 15 | * Resizing a group 16 | * Inserting entry into group 17 | * Deleting entry from group 18 | * Querying/modifying existing tokens in entry (by token_id and entry type (Bool, DWord etc)) 19 | * Hardcode and check: unit_size = 8 20 | 21 | # Modification actions that need to be supported 22 | 23 | * Creating a new group; order doesn't matter--although it's usually ascending by group_id (TODO) 24 | * Growing an existing entry; especially for adding tokens (which have to be sorted) (TODO) 25 | * Adding/REMOVEing tokens in entry (by token_id and entry type (Bool, DWord etc)) (TODO) 26 | * Hardcode and check: unit_size = 8, key_size = 4, key_pos = 0 27 | 28 | # Limitations 29 | 30 | In order to keep the sort order the same, the key of an existing entry cannot be changed. That includes: 31 | 32 | * group_id of existing groups (TODO: Make group header read-only) 33 | * (group_id, type_id, instance_id, board_instance_mask) of existing entries (TODO: Make entry header read-only) 34 | * token_id of existing tokens 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | This crate allows you to manipulate APCB (AGESA PSP Configuration Blob), 4 | directly in an image (u8 slice). 5 | 6 | # Usage 7 | 8 | ## Full-featured usage 9 | 10 | Add 11 | 12 | amd-apcb = { path = "../amd-apcb", features = ["serde", "schemars"] } 13 | 14 | to the `[dependencies]` block in your `Cargo.toml`. 15 | 16 | This way, you get JSON serialization and JSON schema support. 17 | That means when you have an `Apcb` instance in variable `apcb`, you can 18 | do `serde_json::to_string_pretty(&apcb)` to get matching JSON out. 19 | Likewise, you can also deserialize from JSON into a new Apcb instance 20 | (using `serde_json::from_str`, for example). 21 | 22 | Enabling these features slightly changes the signature of some functions (like 23 | `Apcb::load`) to take copy-on-write buffers (in order to allow 24 | deserialization). 25 | 26 | In order to load an existing blob, do this: 27 | 28 | let mut apcb = Apcb::load(std::borrow::Cow::Borrowed(&mut buffer[..]), 29 | &ApcbIoOptions::default())? 30 | 31 | For details, see the generated documentation. 32 | 33 | ## Minimal usage 34 | 35 | Add 36 | 37 | amd-apcb = { path = "../amd-apcb", default_features = false, features = [] } 38 | 39 | to the `[dependencies]` block in your `Cargo.toml`. 40 | 41 | This gives you a minimal interface without serialization support. It's 42 | intended for embedded use. 43 | 44 | In order to load an existing blob, do this: 45 | 46 | let mut apcb = Apcb::load(&mut buffer[..], &ApcbIoOptions::default())? 47 | 48 | There are (about four) groups in the blob. Inside each group there is a 49 | variable number of entries. There are a few different entry types for 50 | different purposes. 51 | 52 | You can use 53 | 54 | apcb.groups()? // or apcb.groups_mut()? 55 | 56 | to iterate over the groups. 57 | 58 | Alternatively, you can use 59 | 60 | apcb.group(GroupId::xxx)? // or apcb.group_mut(GroupId::xxx)? 61 | 62 | to immediately get a specific group (a useful example for `xxx` is `Memory`). 63 | 64 | When you have a group in variable `group`, you can use 65 | 66 | group.entries() // or group.entries_mut() 67 | 68 | to iterate over the entries of that group. Alternatively, you can use 69 | 70 | group.entry_compatible(EntryId::xxx, instance_id, BoardInstances::yyy) 71 | 72 | in order to get an entry compatible with the given instance and boards. 73 | 74 | Alternatively, you can use 75 | 76 | group.entry_exact(EntryId::xxx, instance_id, BoardInstances::yyy) 77 | 78 | in order to get an entry that is exactly specified for the given instance and 79 | boards. 80 | 81 | When you have an entry in variable `entry`, you can use 82 | 83 | entry.body_as_struct::() 84 | 85 | in order to interpret the entry as the given struct `X`. A useful example for 86 | `X` is `ErrorOutControl116`. 87 | 88 | In order to interpret the entry as an array of a given struct `X`, you can do: 89 | 90 | entry.body_as_struct_array::() 91 | 92 | In order to interpret the entry as an array of differently-sized records 93 | (collected into the enum `X`), you can do: 94 | 95 | entry.body_as_struct_sequence:() 96 | 97 | But on a modern AMD platform, the most useful entries are token entries: 98 | 99 | In order to get an interface to the the token entries in a user-friendly form, 100 | you can use: 101 | 102 | let tokens = apcb.tokens(instance_id, BoardInstances::new())? 103 | 104 | It's then possible to use a lot of different getters and setters (one each 105 | per token) in order to access the respective token. For example, you can do 106 | `tokens.abl_serial_baud_rate()` to get the baud rate for the serial port 107 | and/or `tokens.set_abl_serial_baud_rate(BaudRate::_9600Baud)` in order to 108 | set the baud rate for the serial port. 109 | 110 | For more dynamic access, you can do `tokens.get(entry_id, token_id)` directly, 111 | where `entry_id: u16` and `token_id: u32`. 112 | 113 | For details, see the generated documentation. 114 | 115 | # Testing 116 | 117 | Run 118 | 119 | cargo xtask test 120 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # CLEAN UP 2 | 3 | * https://github.com/Robbepop/modular-bitfield/issues/31 4 | * Says that modular-bitfield now allows deserializing unknown invalid bitpatterns without failure! 5 | * Getters protect, though. 6 | * Compile-time assertions 7 | * `const _: () = assert!(std::mem::size_of::() == 8);` 8 | * Tokens 9 | * u0x96176308 = is_capsule_mode: bool (fch) 10 | * u0x6c4ccf38 = mem_ps(platform specific)_error_handling: 0~0xff (usually 0); obsolete 11 | * u0xae7f0df4 = bmc_rcb_wrong_attr_dis: 0=en, 1=dis, skip=0xff 12 | * FchSmbusSpeed::Auto missing? 13 | * Sanity-check MaxFreqElement::new argument 1 14 | * define_ErrorOutControl: CHECK DEFAULT VALUES! 15 | * RdimmDdr4CadBusElement::new: has address_command_control as one conglomerate--should be split. 16 | 17 | # Features 18 | 19 | * Error log should also be readable maybe. 20 | * GroupMutIter: Also validate() 21 | * IdRevApcbMapping 22 | * id_and_feature_mask: bit 7: 1=user controlled; 0=normal 23 | * OdtPatElement: Availability of dimm0_rank, dimm1_rank should be conditional. 24 | * Enums for stuff with a "|" comment. 25 | * a lot of platform_specific_override::* enums 26 | * Cs 27 | 28 | # Tests 29 | 30 | * Add unit test for token entries!! mutation 31 | * bitfield out-of-range 32 | * bitfield in-range 33 | 34 | # (Future) Compatibility 35 | 36 | * Maybe remove PartialEq from structs 37 | * Remove `pub type CadBusAddressCommandDriveStrength = CadBusClkDriveStrength` 38 | * Remove `pub type CadBusCkeDriveStrength = CadBusClkDriveStrength` 39 | * Remove `pub type CadBusCsOdtDriveStrength = CadBusClkDriveStrength` 40 | 41 | # Security 42 | 43 | * Sanity-check non-clone in body_as_struct_mut 44 | * Check for unique key on load 45 | * Check globally unique (group_id) on load. 46 | * Check globally unique (group_id, entry_id, instance_id, board_instance_mask) on load. 47 | * Check globally-unique (group_id, instance_id, token_id) on load. 48 | * insert_token: group_id==Token. entry_id should be datatype-dependent, so only one makes sense per token_id anyway--so entry_id is not part of the unique key. 49 | * Fuzzing! 50 | * https://rust-fuzz.github.io/book/cargo-fuzz.html 51 | * Fuzz after: 52 | * apcb header 53 | * group header 54 | * entry header 55 | * token header 56 | 57 | # Unimportant/later 58 | 59 | * apcb::insert_entry: Replace by shifts and masks (if not compile time) 60 | * insert_token: "&" instead of "%" 61 | * Check error handling crate "failure" or "anyhow". `#[source]` 62 | * Entry alignment relative to containing group instead?? 63 | * AMD# 55483 4.1.3 SPD_Info DIMM_INFO_ARRAY does not seem to exist 64 | * TX EQ struct; bitfield for sockets; bitfield for dies; bitfield for lanes; lane data (variable-length body!) 65 | * Apcb: Dirty-type, original-type; automate calling update_checksum 66 | * My own idea: Just implement Drop and have a flag you refer to. 67 | * Give a reference to the flag to all the iterators that would need to change it 68 | * If there are &mut to struct that doesn't work, now does it? 69 | * insert_struct_sequence_entry(EntryId::Quux).with(A {p : 1}).with(B {q: 42}).finish() (You can do pretty complex construction of structures using that pattern, debug_struct in std is a good example) 70 | * https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.debug_struct 71 | * Move skip_step from EntryCompatible to SequenceElementFromBytes (right now, the latter is only implemented by the enum macro; but skip_step would also be implemented by the enum--but outside the macro. That's too complicated) 72 | * Maybe remove "EventControl" id 73 | 74 | # Alternate Bitfield implementations 75 | 76 | * Most important would be to partially evaluate at compile time! 77 | * bitstruct 78 | -------------------------------------------------------------------------------- /examples/fromyaml/main.rs: -------------------------------------------------------------------------------- 1 | //! serde_yaml has a limitation such that from_str cannot borrow parts of the original string in the returned struct. That means that all the structures in question of amd-apcb have to implement DeserializeOwned, which means none of them are allowed to have lifetime parameters. If they had, the Rust compiler would emit a borrow checker error. 2 | //! We did modify amd-apcb in this way recently, so it works now. However, it can potentially be silently broken by future changes to amd-apcb since the property is only ensured by convention. 3 | //! Therefore, add an example that is built by the Makefile. 4 | 5 | fn main() { 6 | let _foo: amd_apcb::Apcb = serde_yaml::from_str("").unwrap(); 7 | } 8 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | components = [ "rustfmt", "rust-src", "rust-analyzer" ] 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | max_width = 80 3 | use_small_heuristics = "Max" 4 | newline_style = "Unix" 5 | -------------------------------------------------------------------------------- /src/group.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use crate::types::{ApcbContext, Error, FileSystemError, Result}; 6 | 7 | use crate::entry::{EntryItem, EntryItemBody, EntryMutItem}; 8 | use crate::ondisk::ENTRY_ALIGNMENT; 9 | use crate::ondisk::ENTRY_HEADER; 10 | use crate::ondisk::GROUP_HEADER; 11 | use crate::ondisk::GroupId; 12 | use crate::ondisk::TOKEN_ENTRY; 13 | pub use crate::ondisk::{ 14 | BoardInstances, ContextFormat, ContextType, EntryId, PriorityLevels, 15 | }; 16 | use crate::ondisk::{ 17 | take_body_from_collection, take_body_from_collection_mut, 18 | take_header_from_collection, take_header_from_collection_mut, 19 | }; 20 | use core::convert::TryInto; 21 | use core::mem::size_of; 22 | use num_traits::FromPrimitive; 23 | use num_traits::ToPrimitive; 24 | 25 | #[cfg_attr(feature = "serde", derive(serde::Serialize))] 26 | pub struct GroupItem<'a> { 27 | pub(crate) context: ApcbContext, 28 | pub(crate) header: &'a GROUP_HEADER, 29 | #[cfg_attr(feature = "serde", serde(skip))] 30 | pub(crate) buf: &'a [u8], 31 | #[cfg_attr(feature = "serde", serde(skip))] 32 | pub(crate) used_size: usize, 33 | } 34 | 35 | #[cfg(feature = "serde")] 36 | #[cfg_attr( 37 | feature = "serde", 38 | derive(Default, serde::Serialize, serde::Deserialize) 39 | )] 40 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 41 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 42 | pub struct SerdeGroupItem { 43 | pub header: GROUP_HEADER, 44 | } 45 | 46 | #[cfg(feature = "schemars")] 47 | impl schemars::JsonSchema for GroupItem<'_> { 48 | fn schema_name() -> std::string::String { 49 | SerdeGroupItem::schema_name() 50 | } 51 | fn json_schema( 52 | generator: &mut schemars::r#gen::SchemaGenerator, 53 | ) -> schemars::schema::Schema { 54 | SerdeGroupItem::json_schema(generator) 55 | } 56 | fn is_referenceable() -> bool { 57 | SerdeGroupItem::is_referenceable() 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct GroupIter<'a> { 63 | pub(crate) context: ApcbContext, 64 | pub(crate) header: &'a GROUP_HEADER, 65 | buf: &'a [u8], 66 | remaining_used_size: usize, 67 | } 68 | 69 | impl<'a> Iterator for GroupIter<'a> { 70 | type Item = EntryItem<'a>; 71 | 72 | fn next(&mut self) -> Option { 73 | if self.remaining_used_size == 0 { 74 | return None; 75 | } 76 | self.next1().ok() 77 | } 78 | } 79 | impl<'a> GroupIter<'a> { 80 | /// It's useful to have some way of NOT mutating self.buf. This is what 81 | /// this function does. Note: The caller needs to manually decrease 82 | /// remaining_used_size for each call if desired. 83 | fn next_item<'b>( 84 | context: ApcbContext, 85 | buf: &mut &'b [u8], 86 | ) -> Result> { 87 | if buf.is_empty() { 88 | return Err(Error::FileSystem( 89 | FileSystemError::InconsistentHeader, 90 | "ENTRY_HEADER", 91 | )); 92 | } 93 | let header = 94 | match take_header_from_collection::(&mut *buf) { 95 | Some(item) => item, 96 | None => { 97 | return Err(Error::FileSystem( 98 | FileSystemError::InconsistentHeader, 99 | "ENTRY_HEADER", 100 | )); 101 | } 102 | }; 103 | GroupId::from_u16(header.group_id.get()).ok_or(Error::FileSystem( 104 | FileSystemError::InconsistentHeader, 105 | "ENTRY_HEADER::group_id", 106 | ))?; 107 | let entry_size = header.entry_size.get() as usize; 108 | 109 | let payload_size = entry_size 110 | .checked_sub(size_of::()) 111 | .ok_or(Error::FileSystem( 112 | FileSystemError::InconsistentHeader, 113 | "ENTRY_HEADER", 114 | ))?; 115 | let body = match take_body_from_collection( 116 | &mut *buf, 117 | payload_size, 118 | ENTRY_ALIGNMENT, 119 | ) { 120 | Some(item) => item, 121 | None => { 122 | return Err(Error::FileSystem( 123 | FileSystemError::InconsistentHeader, 124 | "ENTRY_HEADER", 125 | )); 126 | } 127 | }; 128 | 129 | let body = EntryItemBody::<&[u8]>::from_slice(header, body, context)?; 130 | 131 | Ok(EntryItem { context, header, body }) 132 | } 133 | 134 | pub(crate) fn next1(&mut self) -> Result> { 135 | if self.remaining_used_size == 0 { 136 | panic!("Internal error"); 137 | } 138 | match Self::next_item(self.context, &mut self.buf) { 139 | Ok(e) => { 140 | if e.header.group_id.get() == self.header.group_id.get() { 141 | } else { 142 | return Err(Error::FileSystem( 143 | FileSystemError::InconsistentHeader, 144 | "ENTRY_HEADER::group_id", 145 | )); 146 | } 147 | let entry_size = e.header.entry_size.get() as usize; 148 | if self.remaining_used_size >= entry_size { 149 | } else { 150 | return Err(Error::FileSystem( 151 | FileSystemError::InconsistentHeader, 152 | "ENTRY_HEADER::entry_size", 153 | )); 154 | } 155 | self.remaining_used_size -= entry_size; 156 | Ok(e) 157 | } 158 | Err(e) => Err(e), 159 | } 160 | } 161 | 162 | /// Validates the entries (recursively). Also consumes iterator. 163 | pub(crate) fn validate(mut self) -> Result<()> { 164 | while self.remaining_used_size > 0 { 165 | match self.next1() { 166 | Ok(item) => { 167 | item.validate()?; 168 | } 169 | Err(e) => { 170 | return Err(e); 171 | } 172 | } 173 | } 174 | Ok(()) 175 | } 176 | } 177 | 178 | impl GroupItem<'_> { 179 | /// Note: ASCII 180 | pub fn signature(&self) -> [u8; 4] { 181 | self.header.signature 182 | } 183 | /// Note: See ondisk::GroupId 184 | pub fn id(&self) -> GroupId { 185 | GroupId::from_u16(self.header.group_id.get()).unwrap() 186 | } 187 | 188 | /// This finds the entry with the given ID, INSTANCE_ID and compatible 189 | /// BOARD_INSTANCE_MASK, if any. If you have a board_id, 190 | /// BOARD_INSTANCE_MASK = 1 << board_id 191 | pub fn entry_compatible( 192 | &self, 193 | id: EntryId, 194 | instance_id: u16, 195 | board_instance_mask: BoardInstances, 196 | ) -> Option> { 197 | self.entries().find(|entry| { 198 | entry.id() == id 199 | && entry.instance_id() == instance_id 200 | && u16::from(entry.board_instance_mask()) 201 | & u16::from(board_instance_mask) 202 | != 0 203 | }) 204 | } 205 | 206 | /// This finds the entry with the given ID, INSTANCE_ID and exact 207 | /// BOARD_INSTANCE_MASK, if any. 208 | pub fn entry_exact( 209 | &self, 210 | id: EntryId, 211 | instance_id: u16, 212 | board_instance_mask: BoardInstances, 213 | ) -> Option> { 214 | self.entries().find(|entry| { 215 | entry.id() == id 216 | && entry.instance_id() == instance_id 217 | && entry.board_instance_mask() == board_instance_mask 218 | }) 219 | } 220 | 221 | pub fn entries(&self) -> GroupIter<'_> { 222 | GroupIter { 223 | context: self.context, 224 | header: self.header, 225 | buf: self.buf, 226 | remaining_used_size: self.used_size, 227 | } 228 | } 229 | } 230 | 231 | impl core::fmt::Debug for GroupItem<'_> { 232 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 233 | // Note: Elides BODY--so, technically, it's not a 1:1 representation 234 | let id = self.id(); 235 | let signature = self.signature(); 236 | fmt.debug_struct("GroupItem") 237 | .field("signature", &signature) 238 | .field("id", &id) 239 | .field("group_size", &self.header.group_size) 240 | .finish() 241 | } 242 | } 243 | 244 | #[derive(Debug)] 245 | pub struct GroupMutIter<'a> { 246 | pub(crate) context: ApcbContext, 247 | pub(crate) header: &'a mut GROUP_HEADER, 248 | buf: &'a mut [u8], 249 | remaining_used_size: usize, 250 | } 251 | 252 | impl GroupMutIter<'_> { 253 | /// It's useful to have some way of NOT mutating self.buf. This is what 254 | /// this function does. Note: The caller needs to manually decrease 255 | /// remaining_used_size for each call if desired. 256 | fn next_item<'b>( 257 | context: ApcbContext, 258 | buf: &mut &'b mut [u8], 259 | ) -> Result> { 260 | if buf.is_empty() { 261 | return Err(Error::FileSystem( 262 | FileSystemError::InconsistentHeader, 263 | "ENTRY_HEADER", 264 | )); 265 | } 266 | let header = 267 | match take_header_from_collection_mut::(&mut *buf) { 268 | Some(item) => item, 269 | None => { 270 | return Err(Error::FileSystem( 271 | FileSystemError::InconsistentHeader, 272 | "ENTRY_HEADER", 273 | )); 274 | } 275 | }; 276 | let entry_size = header.entry_size.get() as usize; 277 | 278 | let payload_size = entry_size 279 | .checked_sub(size_of::()) 280 | .ok_or(Error::FileSystem( 281 | FileSystemError::InconsistentHeader, 282 | "ENTRY_HEADER", 283 | ))?; 284 | let body = match take_body_from_collection_mut( 285 | &mut *buf, 286 | payload_size, 287 | ENTRY_ALIGNMENT, 288 | ) { 289 | Some(item) => item, 290 | None => { 291 | return Err(Error::FileSystem( 292 | FileSystemError::InconsistentHeader, 293 | "ENTRY_HEADER", 294 | )); 295 | } 296 | }; 297 | 298 | let body = 299 | EntryItemBody::<&mut [u8]>::from_slice(header, body, context)?; 300 | Ok(EntryMutItem { context, header, body }) 301 | } 302 | 303 | /// Find the place BEFORE which the entry (GROUP_ID, ENTRY_ID, INSTANCE_ID, 304 | /// BOARD_INSTANCE_MASK) is supposed to go. 305 | pub(crate) fn move_insertion_point_before( 306 | &mut self, 307 | group_id: u16, 308 | type_id: u16, 309 | instance_id: u16, 310 | board_instance_mask: BoardInstances, 311 | ) -> Result<()> { 312 | loop { 313 | let mut buf = &mut self.buf[..self.remaining_used_size]; 314 | if buf.is_empty() { 315 | break; 316 | } 317 | match Self::next_item(self.context, &mut buf) { 318 | Ok(e) => { 319 | if ( 320 | e.group_id(), 321 | e.type_id(), 322 | e.instance_id(), 323 | u16::from(e.board_instance_mask()), 324 | ) < ( 325 | group_id, 326 | type_id, 327 | instance_id, 328 | u16::from(board_instance_mask), 329 | ) { 330 | self.next().unwrap(); 331 | } else { 332 | break; 333 | } 334 | } 335 | Err(e) => { 336 | return Err(e); 337 | } 338 | } 339 | } 340 | Ok(()) 341 | } 342 | /// Find the place BEFORE which the entry (GROUP_ID, ENTRY_ID, INSTANCE_ID, 343 | /// BOARD_INSTANCE_MASK) is. Returns how much it moved the point, and 344 | /// the size of the entry. 345 | pub(crate) fn move_point_to( 346 | &mut self, 347 | entry_id: EntryId, 348 | instance_id: u16, 349 | board_instance_mask: BoardInstances, 350 | size_diff: i64, 351 | ) -> Result<(usize, usize)> { 352 | let mut offset = 0usize; 353 | loop { 354 | let remaining_used_size = self.remaining_used_size 355 | - if size_diff > 0 { 356 | // Make it not see the new, uninitialized, entry. 357 | size_diff as usize 358 | } else { 359 | 0 360 | }; 361 | let mut buf = &mut self.buf[..remaining_used_size]; 362 | if buf.is_empty() { 363 | return Err(Error::EntryNotFound { 364 | entry_id, 365 | instance_id, 366 | board_instance_mask, 367 | }); 368 | } 369 | match Self::next_item(self.context, &mut buf) { 370 | Ok(e) => { 371 | let entry_size = e.header.entry_size.get(); 372 | if (e.id(), e.instance_id(), e.board_instance_mask()) 373 | != (entry_id, instance_id, board_instance_mask) 374 | { 375 | self.next().unwrap(); 376 | offset = offset 377 | .checked_add(entry_size.into()) 378 | .ok_or(Error::ArithmeticOverflow)?; 379 | while offset % ENTRY_ALIGNMENT != 0 { 380 | offset = offset 381 | .checked_add(1) 382 | .ok_or(Error::ArithmeticOverflow)?; 383 | } 384 | } else { 385 | return Ok((offset, entry_size.into())); 386 | } 387 | } 388 | Err(e) => { 389 | return Err(e); 390 | } 391 | } 392 | } 393 | } 394 | /// Inserts the given entry data at the right spot. 395 | /// Precondition: The caller already grew the group by 396 | /// `payload_size + size_of::()` 397 | #[allow(clippy::too_many_arguments)] 398 | pub(crate) fn insert_entry( 399 | &mut self, 400 | entry_id: EntryId, 401 | instance_id: u16, 402 | board_instance_mask: BoardInstances, 403 | entry_allocation: u16, 404 | context_type: ContextType, 405 | payload_size: usize, 406 | payload_initializer: impl Fn(&mut [u8]), 407 | priority_mask: PriorityLevels, 408 | ) -> Result<()> { 409 | let group_id = entry_id.group_id(); 410 | let entry_id = entry_id.type_id(); 411 | 412 | // Make sure that entry_allocation is large enough for the header and 413 | // data 414 | let padding_size = (entry_allocation as usize) 415 | .checked_sub(size_of::()) 416 | .ok_or(Error::FileSystem( 417 | FileSystemError::PayloadTooBig, 418 | "ENTRY_HEADER:entry_size", 419 | ))?; 420 | let padding_size = 421 | padding_size.checked_sub(payload_size).ok_or(Error::FileSystem( 422 | FileSystemError::PayloadTooBig, 423 | "ENTRY_HEADER:entry_size", 424 | ))?; 425 | 426 | // Make sure that move_insertion_point_before does not notice the new 427 | // uninitialized entry 428 | self.remaining_used_size = self 429 | .remaining_used_size 430 | .checked_sub(entry_allocation as usize) 431 | .ok_or(Error::FileSystem( 432 | FileSystemError::InconsistentHeader, 433 | "ENTRY_HEADER::entry_size", 434 | ))?; 435 | self.move_insertion_point_before( 436 | group_id.to_u16().unwrap(), 437 | entry_id, 438 | instance_id, 439 | board_instance_mask, 440 | )?; 441 | 442 | let mut buf = &mut *self.buf; // already done: offset 443 | // Move the entries from after the insertion point to the right (in 444 | // order to make room before for our new entry). 445 | buf.copy_within(0..self.remaining_used_size, entry_allocation as usize); 446 | 447 | //let mut buf = &mut self.buf[..(self.remaining_used_size + 448 | // entry_allocation as usize)]; 449 | let header = take_header_from_collection_mut::(&mut buf) 450 | .ok_or(Error::FileSystem( 451 | FileSystemError::InconsistentHeader, 452 | "ENTRY_HEADER", 453 | ))?; 454 | *header = ENTRY_HEADER::default(); 455 | header.group_id.set(group_id.to_u16().unwrap()); 456 | header.entry_id.set(entry_id); 457 | header.entry_size.set(entry_allocation); 458 | header.instance_id.set(instance_id); 459 | header.context_type = context_type as u8; 460 | header.context_format = ContextFormat::Raw as u8; 461 | header.unit_size = 0; 462 | header.key_size = 0; 463 | header.key_pos = 0; 464 | if context_type == ContextType::Tokens { 465 | header.context_format = ContextFormat::SortAscending as u8; 466 | header.unit_size = 8; 467 | header.key_size = 4; 468 | header.key_pos = 0; 469 | } 470 | header.set_priority_mask(priority_mask); 471 | 472 | // Note: The following is settable by the user via EntryMutItem 473 | // set-accessors: context_type, context_format, unit_size, 474 | // priority_mask, key_size, key_pos 475 | header.board_instance_mask.set(u16::from(board_instance_mask)); 476 | let body = take_body_from_collection_mut(&mut buf, payload_size, 1) 477 | .ok_or(Error::FileSystem( 478 | FileSystemError::InconsistentHeader, 479 | "ENTRY_HEADER", 480 | ))?; 481 | payload_initializer(body); 482 | 483 | let padding = take_body_from_collection_mut(&mut buf, padding_size, 1) 484 | .ok_or(Error::FileSystem( 485 | FileSystemError::InconsistentHeader, 486 | "padding", 487 | ))?; 488 | // We pad this with 0s instead of 0xFFs because that's what AMD does, 489 | // even though the erase polarity of most flash systems nowadays are 490 | // 0xFF. 491 | padding.iter_mut().for_each(|b| *b = 0u8); 492 | self.remaining_used_size = self 493 | .remaining_used_size 494 | .checked_add(entry_allocation as usize) 495 | .ok_or(Error::OutOfSpace)?; 496 | Ok(()) 497 | } 498 | } 499 | 500 | #[derive(Debug)] 501 | pub struct GroupMutItem<'a> { 502 | pub(crate) context: ApcbContext, 503 | pub(crate) header: &'a mut GROUP_HEADER, 504 | pub(crate) buf: &'a mut [u8], 505 | pub(crate) used_size: usize, 506 | } 507 | 508 | impl GroupMutItem<'_> { 509 | /// Note: ASCII 510 | pub fn signature(&self) -> [u8; 4] { 511 | self.header.signature 512 | } 513 | /// Note: See ondisk::GroupId 514 | pub fn id(&self) -> GroupId { 515 | GroupId::from_u16(self.header.group_id.get()).unwrap() 516 | } 517 | 518 | /// This finds the entry with the given ID, INSTANCE_ID and compatible 519 | /// BOARD_INSTANCE_MASK, if any. If you have a board_id, 520 | /// BOARD_INSTANCE_MASK = 1 << board_id 521 | pub fn entry_compatible_mut( 522 | &mut self, 523 | id: EntryId, 524 | instance_id: u16, 525 | board_instance_mask: BoardInstances, 526 | ) -> Option> { 527 | self.entries_mut().find(|entry| { 528 | entry.id() == id 529 | && entry.instance_id() == instance_id 530 | && u16::from(entry.board_instance_mask()) 531 | & u16::from(board_instance_mask) 532 | != 0 533 | }) 534 | } 535 | 536 | /// Note: BOARD_INSTANCE_MASK needs to be exact. 537 | /// This finds the entry with the given ID, INSTANCE_ID and exact 538 | /// BOARD_INSTANCE_MASK, if any. If you have a board_id, 539 | /// BOARD_INSTANCE_MASK = 1 << board_id 540 | pub fn entry_exact_mut( 541 | &mut self, 542 | id: EntryId, 543 | instance_id: u16, 544 | board_instance_mask: BoardInstances, 545 | ) -> Option> { 546 | self.entries_mut().find(|entry| { 547 | entry.id() == id 548 | && entry.instance_id() == instance_id 549 | && entry.board_instance_mask() == board_instance_mask 550 | }) 551 | } 552 | 553 | pub(crate) fn delete_entry( 554 | &mut self, 555 | entry_id: EntryId, 556 | instance_id: u16, 557 | board_instance_mask: BoardInstances, 558 | ) -> Result { 559 | let mut entries = self.entries_mut(); 560 | let (offset, entry_size) = entries.move_point_to( 561 | entry_id, 562 | instance_id, 563 | board_instance_mask, 564 | 0, 565 | )?; 566 | let buf = &mut self.buf[offset..]; 567 | buf.copy_within(entry_size..self.used_size, offset); 568 | Ok(entry_size as u32) 569 | } 570 | /// Resizes the given entry by SIZE_DIFF. 571 | /// Precondition: If `size_diff > 0`, the caller must have already 572 | /// expanded the group `size_diff`. If `size_diff < 0`, the caller 573 | /// must call `resize_entry_by` before resizing the group. 574 | pub(crate) fn resize_entry_by( 575 | &mut self, 576 | entry_id: EntryId, 577 | instance_id: u16, 578 | board_instance_mask: BoardInstances, 579 | size_diff: i64, 580 | ) -> Result> { 581 | let mut old_used_size = self.used_size; 582 | let mut entries = self.entries_mut(); 583 | let (offset, entry_size) = entries.move_point_to( 584 | entry_id, 585 | instance_id, 586 | board_instance_mask, 587 | size_diff, 588 | )?; 589 | let entry_size: u16 = 590 | entry_size.try_into().map_err(|_| Error::ArithmeticOverflow)?; 591 | let entry = entries.next().ok_or(Error::EntryNotFound { 592 | entry_id, 593 | instance_id, 594 | board_instance_mask, 595 | })?; 596 | 597 | if size_diff > 0 { 598 | let size_diff: usize = 599 | size_diff.try_into().map_err(|_| Error::ArithmeticOverflow)?; 600 | old_used_size = old_used_size 601 | .checked_sub(size_diff) 602 | .ok_or(Error::ArithmeticOverflow)? 603 | } 604 | let old_entry_size = entry_size; 605 | let new_entry_size: u16 = if size_diff > 0 { 606 | let size_diff = size_diff as u64; 607 | old_entry_size 608 | .checked_add( 609 | size_diff 610 | .try_into() 611 | .map_err(|_| Error::ArithmeticOverflow)?, 612 | ) 613 | .ok_or(Error::OutOfSpace)? 614 | } else { 615 | let size_diff = (-size_diff) as u64; 616 | old_entry_size 617 | .checked_sub( 618 | size_diff 619 | .try_into() 620 | .map_err(|_| Error::ArithmeticOverflow)?, 621 | ) 622 | .ok_or(Error::OutOfSpace)? 623 | }; 624 | 625 | entry.header.entry_size.set(new_entry_size); 626 | let buf = &mut self.buf[offset..]; 627 | buf.copy_within( 628 | old_entry_size as usize..(old_used_size - offset), 629 | new_entry_size as usize, 630 | ); 631 | match self.entry_exact_mut(entry_id, instance_id, board_instance_mask) { 632 | Some(e) => Ok(e), 633 | None => { 634 | panic!( 635 | "Entry (entry_id = {:?}, instance_id = {:?}, board_instance_mask = {:?}) was found by move_point to, but not by manual iteration after resizing.", 636 | entry_id, instance_id, board_instance_mask 637 | ); 638 | } 639 | } 640 | } 641 | /// Inserts the given token. 642 | /// Precondition: Caller already increased the group size by 643 | /// `size_of::()`. 644 | pub(crate) fn insert_token( 645 | &mut self, 646 | entry_id: EntryId, 647 | instance_id: u16, 648 | board_instance_mask: BoardInstances, 649 | token_id: u32, 650 | token_value: u32, 651 | ) -> Result<()> { 652 | let token_size = size_of::(); 653 | // Note: Now, GroupMutItem.buf includes space for the token, claimed by 654 | // no entry so far. This is bad when iterating over the group members 655 | // until the end--it will iterate over garbage. Therefore, mask 656 | // the new area out for the iterator--and reinstate it only after 657 | // resize_entry_by (which has been adapted specially) is finished with 658 | // Ok. 659 | let mut entry = self.resize_entry_by( 660 | entry_id, 661 | instance_id, 662 | board_instance_mask, 663 | token_size as i64, 664 | )?; 665 | entry.insert_token(token_id, token_value) 666 | } 667 | 668 | /// Deletes the given token. 669 | /// Returns the number of bytes that were deleted. 670 | /// Postcondition: Caller will resize the given group 671 | pub(crate) fn delete_token( 672 | &mut self, 673 | entry_id: EntryId, 674 | instance_id: u16, 675 | board_instance_mask: BoardInstances, 676 | token_id: u32, 677 | ) -> Result { 678 | let token_size = size_of::(); 679 | // Note: Now, GroupMutItem.buf includes space for the token, claimed by 680 | // no entry so far. This is bad when iterating over the group members 681 | // until the end--it will iterate over garbage. Therefore, mask 682 | // the new area out for the iterator--and reinstate it only after 683 | // resize_entry_by (which has been adapted specially) is finished with 684 | // Ok. 685 | let mut entry = self 686 | .entry_exact_mut(entry_id, instance_id, board_instance_mask) 687 | .ok_or(Error::EntryNotFound { 688 | entry_id, 689 | instance_id, 690 | board_instance_mask, 691 | })?; 692 | entry.delete_token(token_id)?; 693 | let mut token_size_diff: i64 = 694 | token_size.try_into().map_err(|_| Error::ArithmeticOverflow)?; 695 | token_size_diff = -token_size_diff; 696 | self.resize_entry_by( 697 | entry_id, 698 | instance_id, 699 | board_instance_mask, 700 | token_size_diff, 701 | )?; 702 | Ok(token_size_diff) 703 | } 704 | 705 | pub fn entries(&self) -> GroupIter<'_> { 706 | GroupIter { 707 | context: self.context, 708 | header: self.header, 709 | buf: self.buf, 710 | remaining_used_size: self.used_size, 711 | } 712 | } 713 | 714 | pub fn entries_mut(&mut self) -> GroupMutIter<'_> { 715 | GroupMutIter { 716 | context: self.context, 717 | header: self.header, 718 | buf: self.buf, 719 | remaining_used_size: self.used_size, 720 | } 721 | } 722 | } 723 | 724 | impl<'a> Iterator for GroupMutIter<'a> { 725 | type Item = EntryMutItem<'a>; 726 | 727 | fn next(&mut self) -> Option { 728 | if self.remaining_used_size == 0 { 729 | return None; 730 | } 731 | match Self::next_item(self.context, &mut self.buf) { 732 | Ok(e) => { 733 | assert!(e.header.group_id.get() == self.header.group_id.get()); 734 | let entry_size = e.header.entry_size.get() as usize; 735 | assert!(self.remaining_used_size >= entry_size); 736 | self.remaining_used_size -= entry_size; 737 | Some(e) 738 | } 739 | Err(_) => None, 740 | } 741 | } 742 | } 743 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | /*! 6 | 7 | This crate allows you to manipulate APCB (AGESA PSP Configuration Blob), 8 | directly in an image (u8 slice). 9 | 10 | # Usage 11 | 12 | Add 13 | 14 | amd-apcb = { path = "../amd-apcb", default_features = false, features = [] } 15 | 16 | to the `[dependencies]` block in your `Cargo.toml`. 17 | 18 | To iterate, you can do: 19 | 20 | let mut buffer: [u8; 8*1024] = ... load from file; 21 | let apcb = Apcb::load(&mut buffer[0..]).unwrap(); 22 | for group in apcb.groups() { 23 | for entry in group.entries() { 24 | ... 25 | } 26 | } 27 | 28 | To insert a new group: 29 | 30 | apcb.insert_group(GroupId::Psp, *b"PSPG")?; 31 | 32 | To delete a group: 33 | 34 | apcb.delete_group(GroupId::Psp)?; 35 | 36 | To insert a new entry: 37 | 38 | apcb.insert_entry(EntryId::Psp(PspEntryId::...), 0, 0xFFFF, 0)?; 39 | 40 | (Note: There are convenience functions that you can use to insert Struct data: 41 | `insert_struct_entry`, `insert_struct_sequence_as_entry`) 42 | 43 | To delete an entry: 44 | 45 | apcb.delete_entry(EntryId::Psp(PspEntryId::...), 0, 0xFFFF)?; 46 | 47 | To insert a new token: 48 | 49 | apcb.insert_token(EntryId::Token(TokenEntryId::Byte), 0, 0xFFFF, 0x42, 1)?; 50 | 51 | To delete a token: 52 | 53 | apcb.delete_token(EntryId::Token(TokenEntryId::Byte), 0, 0xFFFF, 0x42)?; 54 | 55 | If the entry is a struct entry, you can use something like 56 | 57 | let mut (entry, _tail) = entry.body_as_struct_mut::()?; 58 | entry.dimm_slot_present 59 | 60 | to have the entry represented as a Rust struct (the `_tail` would be the 61 | variable-length array at the end of the struct, if applicable). 62 | This is only useful for structs whose name doesn't contain "Element" (since 63 | those are the ones without a variable-length payload). 64 | 65 | If the entry is a struct array entry (variable-length array), then you can 66 | use something like 67 | 68 | let mut entry = entry.body_as_struct_array_mut::()?; 69 | for element in entry { 70 | ... 71 | } 72 | 73 | to iterate over it. This is only useful for structs whose name contains "Element". 74 | 75 | In order to update the checksum (you should do that once after any insertion/deletion/mutation): 76 | 77 | apcb.save()?; 78 | 79 | Note that this also changes unique_apcb_instance. 80 | 81 | # Implementation notes 82 | 83 | A library such as this, since we want to have type-safe fields (for example 84 | values as enum variants etc), has to have some layer on top of zerocopy 85 | (the latter of which would just have "this is a little-endian u32"--well, 86 | that's not very expressive). 87 | 88 | That is what the macros `make_accessors` and (or rather, xor) `make_bitfield_serde` 89 | add. 90 | 91 | We want the APCB serde interface to be as close as possible to the actual 92 | ondisk structs (but using high-level types)--because that makes understanding 93 | and debugging it much easier for the user. That meant that `make_accessors` 94 | and `make_bitfield_serde` grew serde-specific automatically generated accessors, 95 | serde structs (used for serialization and deserialization by serde--all the 96 | ones we generate have `Serde` in the name) and impls Deserialize and Serialize 97 | on the `Serde` (!) struct. 98 | 99 | In general you can think of the entire thing to do basically the following 100 | (pub chosen with great care in the following): 101 | 102 | ```rust 103 | make_accessors! { 104 | struct Foo { 105 | a || Quux : Bar | pub get G : pub set S, 106 | } 107 | } 108 | ``` 109 | 110 | => 111 | 112 | ```rust 113 | struct Foo { 114 | a: Bar 115 | } 116 | impl Foo { 117 | pub fn a(&self) -> Result { 118 | self.a.get1() 119 | } 120 | pub fn set_a(&mut self, value: S) { 121 | self.a.set1(value) 122 | } 123 | pub fn builder() -> Self { 124 | Self::default() 125 | } 126 | pub fn build(&self) -> Self { 127 | self.clone() 128 | } 129 | pub fn with_a(self: &mut Self, value: S) -> &mut Self { 130 | let result = self; 131 | result.a.set1(value); 132 | result 133 | } 134 | fn serde_a(&self) -> Result { 135 | self.a.get1() 136 | } 137 | fn serde_with_a(&mut self, value: Quux) -> &mut Self { 138 | let result = self; 139 | result.a.set1(value); 140 | result 141 | } 142 | } 143 | 144 | #[derive(Serialize, Deserialize)] 145 | struct SerdeFoo { 146 | pub a: Quux 147 | } 148 | ``` 149 | 150 | But it turned out that bitfields already have some of the things that 151 | `make_accessors` generates, hence `make_bitfield_serde` was born--which is 152 | basically `make_accessors` with duplicate things removed. 153 | 154 | `make_bitfield_serde` would do: 155 | 156 | ```rust 157 | make_bitfield_serde! { 158 | #[bitfield] 159 | struct Foo { 160 | // Note: G and S are ignored--but it's there so we have some 161 | // regularity in our DSL. 162 | a || Quux : B3 | pub get G : pub set S, 163 | } 164 | } 165 | ``` 166 | 167 | => 168 | 169 | ```rust 170 | #[bitfield] 171 | struct Foo { 172 | a: B3 173 | } 174 | impl Foo { 175 | // no getters, setters, builder, with_... --since the bitfields have 176 | // those automatically 177 | fn serde_a(&self) -> Result { 178 | self.a() 179 | } 180 | fn serde_with_a(&mut self, value: Quux) -> &mut Self { 181 | self.set_a(value.into()); 182 | self 183 | } 184 | } 185 | 186 | #[derive(Serialize, Deserialize)] 187 | struct SerdeFoo { 188 | // WARNING: 189 | // If the `|| Quux` was left off in the input, then it would default to a 190 | // Rust serde-able type that is at least as big as B3 (the result would be u8). 191 | // This is specific to make_bitfield_serde and it's the result of me giving up 192 | // trying to use refinement types in Rust--see ux_serde crate for what 193 | // could have been. That also means that in the serde config, you can 194 | // specify a value that doesn't fit into a B3. It will then be truncated 195 | // and stored without warning (which is what a lot of Rust crates 196 | // culturally do--see for example register crates, hal crates etc). 197 | // To prevent truncation is why, whenever possible, we use an 198 | // enum that derives BitfieldSpecifier instead of things like B3. 199 | // That way, out of bounds values cannot happen. 200 | // These enums can be used as `TYPE` directly in `#[bitfield]` fields. 201 | pub a: Quux 202 | } 203 | ``` 204 | 205 | The builder, with_ and build fns are implementing the builder pattern 206 | (for a nicer user-level interface) in Rust. 207 | 208 | make_accessors gets a struct definition as parameter, and the thing below is 209 | one of the fields of the input struct definition. The result is that struct 210 | is generated, and another struct is generated with `Serde` in front of the 211 | struct name and different field types): 212 | 213 | `NAME [|| SERDE_TYPE] : TYPE 214 | [| pub get GETTER_RETURN_TYPE [: pub set SETTER_PARAMETER_TYPE]]` 215 | 216 | The brackets denote optional parts. 217 | It defines field `NAME` as `TYPE`. `TYPE` usually has to be the lowest-level 218 | machine type (so it's "Little-endian u32", or "3 bits", or similar). 219 | `SERDE_TYPE`, if given, will be the type used for the serde field. If it is 220 | not given, `TYPE` will be used instead. 221 | The "pub get" will use the given GETTER_RETURN_TYPE as the resulting type 222 | of the generated getter, using `Getter` converters to get there as needed. 223 | The "pub set" will use the given SETTER_PARAMETER_TYPE as the parameter 224 | type of the generated setter, using `Setter` converters to get there as 225 | needed. 226 | The `[|| SERDE_TYPE] : TYPE` is weird because of an unfortunate limitation 227 | of the Rust macro system. I've filed bugs upstream, but apparently they 228 | don't consider those bugs (for example rust-lang/rust#96787 ). 229 | 230 | In any case, in an ideal world, that `[|| SERDE_TYPE] : TYPE` would just be 231 | `: [SERDE_TYPE | ] TYPE`, but that is not currently possible 232 | (because the Rust macro parser apparently only has a lookahead of 1 token). 233 | 234 | In the end, even now, it just means you can leave `|| SERDE_TYPE` off and 235 | it will just use `TYPE` on the serde side as well. 236 | 237 | In order to ensure backward-compatibility of the configuration files there 238 | is NO automatic connection between Foo and SerdeFoo. Instead, the user is 239 | supposed to do the connection (and conversion), possibly to ANOTHER custom 240 | serde struct the user wrote manually. 241 | 242 | If the conversion is 1:1 after all, there's a macro 243 | `impl_struct_serde_conversion` for simplifying that, in `serializers.rs`. 244 | You give it the two (existing!) struct 245 | names and a list of fields and it will generate converters to and fro 246 | (using the fns serde_... that were generated earlier). 247 | Long term (with changing requirements), those conversions will become less 248 | and less straightforward and more manual and so usage of 249 | `impl_struct_serde_conversion` will decrease. 250 | 251 | For tokens, there's a macro make_token_accessors in src/token_accessors.rs. 252 | 253 | You give in an DSL-extended-enum (the DSL is designed to be very similar to 254 | the struct one described in the beginning), and an enum with data (different 255 | data type per variant) comes out--and some pub getters and setters 256 | (one per variant). 257 | 258 | Because it's guaranteed that all token values are at most `u32` by AMD 259 | (and the lowlevel type is `U32`), the token field value 260 | accessors just use `USER_TYPE::from_u32` and `USER_INSTANCE.to_u32`, 261 | respectively, instead of doing all the complicated stuff described above. 262 | 263 | But that means that `from_u32` and `to_u32` need to be implemented for the 264 | bitfield. That is what `impl_bitfield_primitive_conversion` does (with great 265 | care--as opposed to what bitfield itself would do if given a chance). 266 | 267 | */ 268 | 269 | #![forbid(unsafe_code)] 270 | #![recursion_limit = "256"] 271 | #![cfg_attr(not(feature = "std"), no_std)] 272 | #![warn(elided_lifetimes_in_paths)] 273 | #![allow(clippy::collapsible_if)] 274 | 275 | #[cfg(test)] 276 | #[macro_use] 277 | extern crate memoffset; 278 | 279 | mod apcb; 280 | mod entry; 281 | mod group; 282 | mod naples; 283 | mod ondisk; 284 | #[cfg(feature = "serde")] 285 | mod serializers; 286 | mod struct_accessors; 287 | mod struct_variants_enum; 288 | mod tests; 289 | mod token_accessors; 290 | mod tokens_entry; 291 | mod types; 292 | pub use apcb::Apcb; 293 | pub use apcb::ApcbIoOptions; 294 | pub use entry::EntryItemBody; 295 | pub use ondisk::*; 296 | pub use types::ApcbContext; 297 | pub use types::Error; 298 | pub use types::FileSystemError; 299 | pub use types::MemDfeSearchVersion; 300 | pub use types::PriorityLevel; 301 | pub use types::Result; 302 | -------------------------------------------------------------------------------- /src/naples.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! This file mostly contains the Naples backward-compatibility interface. 6 | 7 | use crate::struct_accessors::{Getter, Setter}; 8 | use crate::types::Result; 9 | use modular_bitfield::prelude::*; 10 | 11 | #[derive( 12 | Debug, PartialEq, num_derive::FromPrimitive, Clone, Copy, BitfieldSpecifier, 13 | )] 14 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 16 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 17 | #[non_exhaustive] 18 | #[bits = 8] 19 | pub enum ParameterTimePoint { 20 | Never = 0, 21 | Any = 1, 22 | } 23 | 24 | impl Getter> for ParameterTimePoint { 25 | fn get1(self) -> Result { 26 | Ok(self) 27 | } 28 | } 29 | 30 | impl Setter for ParameterTimePoint { 31 | fn set1(&mut self, value: Self) { 32 | *self = value 33 | } 34 | } 35 | 36 | impl Default for ParameterTimePoint { 37 | fn default() -> Self { 38 | Self::Any 39 | } 40 | } 41 | 42 | #[derive( 43 | Debug, PartialEq, num_derive::FromPrimitive, Clone, Copy, BitfieldSpecifier, 44 | )] 45 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 46 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 47 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 48 | #[non_exhaustive] 49 | #[bits = 13] 50 | pub enum ParameterTokenConfig { 51 | // Cbs 52 | Cbs00 = 0x00, 53 | Cbs01 = 0x01, 54 | Cbs02 = 0x02, 55 | Cbs03 = 0x03, 56 | Cbs04 = 0x04, 57 | Cbs05 = 0x05, 58 | Cbs06 = 0x06, 59 | Cbs07 = 0x07, 60 | Cbs08 = 0x08, 61 | Cbs09 = 0x09, 62 | Cbs0a = 0x0a, 63 | Cbs0b = 0x0b, 64 | Cbs0c = 0x0c, 65 | Cbs0d = 0x0d, 66 | Cbs0e = 0x0e, 67 | Cbs0f = 0x0f, 68 | Cbs10 = 0x10, 69 | Cbs11 = 0x11, 70 | Cbs12 = 0x12, 71 | Cbs13 = 0x13, 72 | Cbs14 = 0x14, 73 | Cbs15 = 0x15, 74 | Cbs16 = 0x16, 75 | Cbs17 = 0x17, 76 | Cbs18 = 0x18, 77 | Cbs19 = 0x19, 78 | Cbs1a = 0x1a, 79 | Cbs1b = 0x1b, 80 | Cbs1c = 0x1c, 81 | Cbs1d = 0x1d, 82 | Cbs1e = 0x1e, 83 | Cbs1f = 0x1f, 84 | Cbs20 = 0x20, 85 | Cbs21 = 0x21, 86 | Cbs22 = 0x22, 87 | Cbs23 = 0x23, 88 | Cbs24 = 0x24, 89 | Cbs25 = 0x25, 90 | Cbs26 = 0x26, 91 | Cbs27 = 0x27, 92 | Cbs28 = 0x28, 93 | Cbs29 = 0x29, 94 | Cbs2a = 0x2a, 95 | Cbs2b = 0x2b, 96 | Cbs2c = 0x2c, 97 | Cbs2d = 0x2d, 98 | Cbs2e = 0x2e, 99 | Cbs2f = 0x2f, 100 | Cbs30 = 0x30, 101 | Cbs31 = 0x31, 102 | Cbs32 = 0x32, 103 | Cbs33 = 0x33, 104 | Cbs34 = 0x34, 105 | Cbs35 = 0x35, 106 | Cbs36 = 0x36, 107 | Cbs37 = 0x37, 108 | Cbs38 = 0x38, 109 | Cbs39 = 0x39, 110 | Cbs3a = 0x3a, 111 | Cbs3b = 0x3b, 112 | Cbs3c = 0x3c, 113 | Cbs3d = 0x3d, 114 | Cbs3e = 0x3e, 115 | Cbs3f = 0x3f, 116 | Cbs40 = 0x40, 117 | Cbs41 = 0x41, 118 | Cbs42 = 0x42, 119 | Cbs43 = 0x43, 120 | Cbs44 = 0x44, 121 | Cbs45 = 0x45, 122 | Cbs46 = 0x46, 123 | Cbs47 = 0x47, 124 | Cbs48 = 0x48, 125 | Cbs49 = 0x49, 126 | Cbs4a = 0x4a, 127 | Cbs4b = 0x4b, 128 | Cbs4c = 0x4c, 129 | Cbs4d = 0x4d, 130 | Cbs4e = 0x4e, 131 | Cbs4f = 0x4f, 132 | Cbs50 = 0x50, 133 | Cbs51 = 0x51, 134 | Cbs52 = 0x52, 135 | Cbs53 = 0x53, 136 | Cbs54 = 0x54, 137 | Cbs55 = 0x55, 138 | Cbs56 = 0x56, 139 | Cbs57 = 0x57, 140 | Cbs58 = 0x58, 141 | Cbs59 = 0x59, 142 | Cbs5a = 0x5a, 143 | Cbs5b = 0x5b, 144 | Cbs5c = 0x5c, 145 | Cbs5d = 0x5d, 146 | Cbs5e = 0x5e, 147 | Cbs5f = 0x5f, 148 | Cbs60 = 0x60, 149 | Cbs61 = 0x61, 150 | Cbs62 = 0x62, 151 | Cbs63 = 0x63, 152 | Cbs64 = 0x64, 153 | Cbs65 = 0x65, 154 | Cbs66 = 0x66, 155 | Cbs67 = 0x67, 156 | Cbs68 = 0x68, 157 | Cbs69 = 0x69, 158 | Cbs6a = 0x6a, 159 | Cbs6b = 0x6b, 160 | Cbs6c = 0x6c, 161 | Cbs6d = 0x6d, 162 | Cbs6e = 0x6e, 163 | Cbs6f = 0x6f, 164 | Cbs70 = 0x70, 165 | Cbs71 = 0x71, 166 | Cbs72 = 0x72, 167 | Cbs73 = 0x73, 168 | Cbs74 = 0x74, 169 | Cbs75 = 0x75, 170 | Cbs76 = 0x76, 171 | Cbs77 = 0x77, 172 | Cbs78 = 0x78, 173 | Cbs79 = 0x79, 174 | Cbs7a = 0x7a, 175 | Cbs7b = 0x7b, 176 | Cbs7c = 0x7c, 177 | Cbs7d = 0x7d, 178 | Cbs7e = 0x7e, 179 | Cbs7f = 0x7f, 180 | Cbs80 = 0x80, 181 | Cbs81 = 0x81, 182 | Cbs82 = 0x82, 183 | Cbs83 = 0x83, 184 | Cbs84 = 0x84, 185 | Cbs85 = 0x85, 186 | Cbs86 = 0x86, 187 | Cbs87 = 0x87, 188 | Cbs88 = 0x88, 189 | Cbs89 = 0x89, 190 | Cbs8a = 0x8a, 191 | Cbs8b = 0x8b, 192 | Cbs8c = 0x8c, 193 | Cbs8d = 0x8d, 194 | Cbs8e = 0x8e, 195 | Cbs8f = 0x8f, 196 | Cbs90 = 0x90, 197 | Cbs91 = 0x91, 198 | Cbs92 = 0x92, 199 | Cbs93 = 0x93, 200 | Cbs94 = 0x94, 201 | Cbs95 = 0x95, 202 | Cbs96 = 0x96, 203 | Cbs97 = 0x97, 204 | Cbs98 = 0x98, 205 | Cbs99 = 0x99, 206 | Cbs9a = 0x9a, 207 | Cbs9b = 0x9b, 208 | Cbs9c = 0x9c, 209 | Cbs9d = 0x9d, 210 | Cbs9e = 0x9e, 211 | Cbs9f = 0x9f, 212 | Cbsa0 = 0xa0, 213 | Cbsa1 = 0xa1, 214 | Cbsa2 = 0xa2, 215 | Cbsa3 = 0xa3, 216 | Cbsa4 = 0xa4, 217 | Cbsa5 = 0xa5, 218 | Cbsa6 = 0xa6, 219 | Cbsa7 = 0xa7, 220 | Cbsa8 = 0xa8, 221 | Cbsa9 = 0xa9, 222 | Cbsaa = 0xaa, 223 | Cbsab = 0xab, 224 | Cbsac = 0xac, 225 | Cbsad = 0xad, 226 | Cbsae = 0xae, 227 | Cbsaf = 0xaf, 228 | Cbsb0 = 0xb0, 229 | Cbsb1 = 0xb1, 230 | Cbsb2 = 0xb2, 231 | Cbsb3 = 0xb3, 232 | Cbsb4 = 0xb4, 233 | Cbsb5 = 0xb5, 234 | Cbsb6 = 0xb6, 235 | Cbsb7 = 0xb7, 236 | Cbsb8 = 0xb8, 237 | Cbsb9 = 0xb9, 238 | Cbsba = 0xba, 239 | Cbsbb = 0xbb, 240 | Cbsbc = 0xbc, 241 | Cbsbd = 0xbd, 242 | Cbsbe = 0xbe, 243 | Cbsbf = 0xbf, 244 | Cbsc0 = 0xc0, 245 | Cbsc1 = 0xc1, 246 | Cbsc2 = 0xc2, 247 | Cbsc3 = 0xc3, 248 | Cbsc4 = 0xc4, 249 | Cbsc5 = 0xc5, 250 | Cbsc6 = 0xc6, 251 | Cbsc7 = 0xc7, 252 | Cbsc8 = 0xc8, 253 | Cbsc9 = 0xc9, 254 | Cbsca = 0xca, 255 | Cbscb = 0xcb, 256 | Cbscc = 0xcc, 257 | Cbscd = 0xcd, 258 | Cbsce = 0xce, 259 | Cbscf = 0xcf, 260 | Cbsd0 = 0xd0, 261 | Cbsd1 = 0xd1, 262 | Cbsd2 = 0xd2, 263 | Cbsd3 = 0xd3, 264 | Cbsd4 = 0xd4, 265 | Cbsd5 = 0xd5, 266 | Cbsd6 = 0xd6, 267 | Cbsd7 = 0xd7, 268 | Cbsd8 = 0xd8, 269 | Cbsd9 = 0xd9, 270 | Cbsda = 0xda, 271 | Cbsdb = 0xdb, 272 | Cbsdc = 0xdc, 273 | Cbsdd = 0xdd, 274 | Cbsde = 0xde, 275 | Cbsdf = 0xdf, 276 | Cbse0 = 0xe0, 277 | Cbse1 = 0xe1, 278 | Cbse2 = 0xe2, 279 | Cbse3 = 0xe3, 280 | Cbse4 = 0xe4, 281 | Cbse5 = 0xe5, 282 | Cbse6 = 0xe6, 283 | Cbse7 = 0xe7, 284 | Cbse8 = 0xe8, 285 | Cbse9 = 0xe9, 286 | Cbsea = 0xea, 287 | Cbseb = 0xeb, 288 | Cbsec = 0xec, 289 | Cbsed = 0xed, 290 | Cbsee = 0xee, 291 | Cbsef = 0xef, 292 | Cbsf0 = 0xf0, 293 | Cbsf1 = 0xf1, 294 | Cbsf2 = 0xf2, 295 | Cbsf3 = 0xf3, 296 | Cbsf4 = 0xf4, 297 | Cbsf5 = 0xf5, 298 | Cbsf6 = 0xf6, 299 | Cbsf7 = 0xf7, 300 | Cbsf8 = 0xf8, 301 | Cbsf9 = 0xf9, 302 | Cbsfa = 0xfa, 303 | Cbsfb = 0xfb, 304 | Cbsfc = 0xfc, 305 | Cbsfd = 0xfd, 306 | Cbsfe = 0xfe, 307 | Cbsff = 0xff, 308 | 309 | // Ccx 310 | CcxMinSevAsid = 0x0101, 311 | 312 | // Df 313 | DfGmiEncrypt = 0x0301, 314 | DfXgmiEncrypt = 0x0302, 315 | DfSaveRestoreMemEncrypt = 0x0303, 316 | DfSysStorageAtTopOfMem = 0x0304, 317 | DfProbeFilter = 0x0305, 318 | DfBottomIo = 0x0306, 319 | DfMemInterleaving = 0x0307, 320 | DfMemInterleavingSize = 0x0308, 321 | DfMemInterleavingHash = 0x0309, 322 | DfPciMmioSize = 0x030A, 323 | DfCakeCrcThreshPerfBounds = 0x030B, 324 | DfMemClear = 0x030C, 325 | 326 | // TODO: mem 327 | MemBottomIo = 0x0701, 328 | MemHoleRemapping = 0x0702, 329 | MemLimitToBelow1TiB = 0x0703, 330 | MemUserTimingMode = 0x0704, 331 | MemClockValue = 0x0705, 332 | MemEnableChipSelectInterleaving = 0x0706, 333 | MemEnableChannelInterleaving = 0x0707, 334 | MemEnableEccFeature = 0x0708, 335 | MemEnablePowerDown = 0x0709, 336 | MemEnableParity = 0x070A, 337 | MemEnableBankSwizzle = 0x070B, 338 | MemEnableClearing = 0x070C, 339 | MemUmaMode = 0x070D, 340 | MemUmaSize = 0x070E, 341 | MemRestoreControl = 0x070F, 342 | MemSaveMemContextControl = 0x0710, 343 | MemIsCapsuleMode = 0x0711, 344 | MemForceTraining = 0x0712, 345 | MemDimmTypeMixedConfig = 0x0713, 346 | MemEnableAmp = 0x0714, 347 | MemDramDoubleRefreshRate = 0x0715, 348 | MemPmuTrainingMode = 0x0716, 349 | MemEccRedirection = 0x0717, 350 | MemScrubDramRate = 0x0718, 351 | MemScrubL2Rate = 0x0719, 352 | MemScrubL3Rate = 0x071A, 353 | MemScrubInstructionCacheRate = 0x071B, 354 | MemScrubDataCacheRate = 0x071C, 355 | MemEccSyncFlood = 0x071D, 356 | MemEccSymbolSize = 0x071E, 357 | MemDqsTrainingControl = 0x071F, 358 | MemUmaAbove4GiB = 0x0720, 359 | MemUmaAlignment = 0x0721, 360 | MemEnableAllClocks = 0x0722, 361 | MemBusFrequencyLimit = 0x0723, 362 | MemPowerDownMode = 0x0724, 363 | MemIgnoreSpdChecksum = 0x0725, 364 | MemModeUnganged = 0x0726, 365 | MemQuadRankCapable = 0x0727, 366 | MemRdimmCapable = 0x0728, 367 | MemLrdimmCapable = 0x0729, 368 | MemUdimmCapable = 0x072A, 369 | MemSodimmCapable = 0x072B, 370 | MemEnableDoubleRefreshRate = 0x072C, 371 | MemDimmTypeDdr4Capable = 0x072D, 372 | MemDimmTypeDdr3Capable = 0x072E, 373 | MemDimmTypeLpddr3Capable = 0x072F, 374 | MemEnableZqReset = 0x0730, 375 | MemEnableBankGroupSwap = 0x0731, 376 | MemEnableOdtsCmdThrottle = 0x0732, 377 | MemEnableSwCmdThrottle = 0x0733, 378 | MemEnableForcePowerDownThrotle = 0x0734, 379 | MemOdtsCmdThrottleCycles = 0x0735, 380 | // See PPR SwCmdThrotCyc 381 | MemSwCmdThrottleCycles = 0x0736, 382 | MemDimmSensorConf = 0x0737, 383 | MemDimmSensorUpper = 0x0738, 384 | MemDimmSensorLower = 0x0739, 385 | MemDimmSensorCritical = 0x073A, 386 | MemDimmSensorResolution = 0x073B, 387 | MemAutoRefreshFineGranMode = 0x073C, 388 | MemEnablePState = 0x073D, 389 | MemSolderedDown = 0x073E, 390 | MemDdrRouteBalancedTee = 0x073F, 391 | MemEnableMbistTest = 0x0740, 392 | MemEnableTsme = 0x0746, 393 | MemPlatformSpecificErrorHandling = 0x074A, 394 | MemEnableTemperatureControlledRefresh = 0x074B, 395 | MemEnableBankGroupSwapAlt = 0x074D, 396 | MemEnd = 0x074E, 397 | Mem74f = 0x074F, // FIXME 398 | Mem750 = 0x0750, // FIXME 399 | 400 | GnbBmcSocketNumber = 0x1801, 401 | GnbBmcStartLane = 0x1802, 402 | GnbBmcEndLane = 0x1803, 403 | GnbBmcDevice = 0x1804, 404 | GnbBmcFunction = 0x1805, 405 | GnbPcieResetControl = 0x1806, 406 | GnbEnd = 0x1807, 407 | 408 | FchConsoleOutEnable = 0x1C01, 409 | FchConsoleOutSerialPort = 0x1C02, 410 | FchSmbusSpeed = 0x1C03, 411 | FchEnd = 0x1C04, 412 | Fch1c05 = 0x1C05, // FIXME 413 | Fch1c06 = 0x1C06, // FIXME 414 | Fch1c07 = 0x1C07, // FIXME 415 | 416 | Limit = 0x1FFF, 417 | } 418 | 419 | impl Default for ParameterTokenConfig { 420 | fn default() -> Self { 421 | Self::Limit 422 | } 423 | } 424 | 425 | impl Getter> for ParameterTokenConfig { 426 | fn get1(self) -> Result { 427 | Ok(self) 428 | } 429 | } 430 | 431 | impl Setter for ParameterTokenConfig { 432 | fn set1(&mut self, value: Self) { 433 | *self = value 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /src/serializers.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! This file contains the serializers for the ondisk formats. 6 | //! These are meant automatically make serde use a temporary serde-aware 7 | //! struct as a proxy when serializing/deserializing a non-serde-aware struct. 8 | //! Note that fields which are private are not in the proxy struct in 9 | //! the first place. This might cause problems. Also, serialization can 10 | //! fail if the nice simple user-visible type cannot represent what we 11 | //! are doing. 12 | 13 | use crate::df::*; 14 | use crate::fch::*; 15 | use crate::gnb::*; 16 | use crate::memory::platform_tuning::*; 17 | use crate::memory::*; 18 | use crate::ondisk::memory::platform_specific_override::*; 19 | use crate::ondisk::*; 20 | use crate::psp::*; 21 | 22 | // Note: This is written such that it will fail if the underlying struct has 23 | // fields added/removed/renamed--if those have a public setter. 24 | macro_rules! impl_struct_serde_conversion{($StructName:ident, $SerdeStructName:ident, [$($field_name:ident),* $(,)?] 25 | ) => ( 26 | paste::paste!{ 27 | #[cfg(feature = "serde")] 28 | impl<'de> serde::de::Deserialize<'de> for $StructName { 29 | fn deserialize(deserializer: D) -> core::result::Result 30 | where D: serde::de::Deserializer<'de>, { 31 | let config = $SerdeStructName::deserialize(deserializer)?; 32 | Ok($StructName::builder() 33 | $( 34 | .[](config.$field_name.into()) 35 | )*.build()) 36 | } 37 | } 38 | #[cfg(feature = "serde")] 39 | impl serde::Serialize for $StructName { 40 | fn serialize(&self, serializer: S) -> core::result::Result 41 | where S: serde::Serializer, { 42 | $SerdeStructName { 43 | $( 44 | $field_name: self.[]().map_err(|_| serde::ser::Error::custom(format!("value unknown for {}.{}", stringify!($StructName), stringify!($field_name))))?.into(), 45 | )* 46 | }.serialize(serializer) 47 | } 48 | } 49 | #[cfg(feature = "schemars")] 50 | impl schemars::JsonSchema for $StructName { 51 | fn schema_name() -> String { 52 | $SerdeStructName::schema_name() 53 | } 54 | fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { 55 | $SerdeStructName::json_schema(generator) 56 | } 57 | fn is_referenceable() -> bool { 58 | $SerdeStructName::is_referenceable() 59 | } 60 | } 61 | } 62 | )} 63 | 64 | impl_struct_serde_conversion!( 65 | ENTRY_HEADER, 66 | SerdeENTRY_HEADER, 67 | [ 68 | group_id, 69 | entry_id, 70 | entry_size, 71 | instance_id, 72 | context_type, 73 | context_format, 74 | unit_size, 75 | priority_mask, 76 | key_size, 77 | key_pos, 78 | board_instance_mask 79 | ] 80 | ); 81 | 82 | impl_struct_serde_conversion!( 83 | PriorityLevels, 84 | SerdePriorityLevels, 85 | [hard_force, high, medium, event_logging, low, normal, _reserved_1,] 86 | ); 87 | 88 | impl_struct_serde_conversion!( 89 | Ddr4DataBusElement, 90 | SerdeDdr4DataBusElement, 91 | [ 92 | dimm_slots_per_channel, 93 | ddr_rates, 94 | vdd_io, 95 | dimm0_ranks, 96 | dimm1_ranks, 97 | rtt_nom, 98 | rtt_wr, 99 | rtt_park, 100 | dq_drive_strength, 101 | dqs_drive_strength, 102 | odt_drive_strength, 103 | pmu_phy_vref, 104 | vref_dq, 105 | ] 106 | ); 107 | impl_struct_serde_conversion!( 108 | Ddr4DimmRanks, 109 | SerdeDdr4DimmRanks, 110 | [unpopulated, single_rank, dual_rank, quad_rank,] 111 | ); 112 | impl_struct_serde_conversion!( 113 | LrdimmDdr4DimmRanks, 114 | SerdeLrdimmDdr4DimmRanks, 115 | [unpopulated, lr, _reserved_1,] 116 | ); 117 | impl_struct_serde_conversion!( 118 | DdrRates, 119 | SerdeDdrRates, 120 | [ 121 | _reserved_1, 122 | _reserved_2, 123 | _reserved_3, 124 | ddr400, 125 | ddr533, 126 | ddr667, 127 | ddr800, 128 | _reserved_4, 129 | ddr1066, 130 | _reserved_5, 131 | ddr1333, 132 | _reserved_6, 133 | ddr1600, 134 | _reserved_7, 135 | ddr1866, 136 | _reserved_8, 137 | ddr2133, 138 | _reserved_9, 139 | ddr2400, 140 | _reserved_10, 141 | ddr2667, 142 | _reserved_11, 143 | ddr2933, 144 | _reserved_12, 145 | ddr3200, 146 | _reserved_13, 147 | _reserved_14, 148 | _reserved_15, 149 | _reserved_16, 150 | _reserved_17, 151 | _reserved_18, 152 | _reserved_19, 153 | ] 154 | ); 155 | impl_struct_serde_conversion!( 156 | RdimmDdr4Voltages, 157 | CustomSerdeRdimmDdr4Voltages, 158 | [_1_2V, _reserved_1,] 159 | ); 160 | impl_struct_serde_conversion!( 161 | RdimmDdr4CadBusElement, 162 | SerdeRdimmDdr4CadBusElement, 163 | [ 164 | dimm_slots_per_channel, 165 | ddr_rates, 166 | vdd_io, 167 | dimm0_ranks, 168 | dimm1_ranks, 169 | gear_down_mode, 170 | _reserved_, 171 | slow_mode, 172 | _reserved_2, 173 | address_command_control, 174 | cke_drive_strength, 175 | cs_odt_drive_strength, 176 | address_command_drive_strength, 177 | clk_drive_strength, 178 | ] 179 | ); 180 | impl_struct_serde_conversion!( 181 | UdimmDdr4Voltages, 182 | CustomSerdeUdimmDdr4Voltages, 183 | [_1_5V, _1_35V, _1_25V, _reserved_1,] 184 | ); 185 | impl_struct_serde_conversion!( 186 | UdimmDdr4CadBusElement, 187 | SerdeUdimmDdr4CadBusElement, 188 | [ 189 | dimm_slots_per_channel, 190 | ddr_rates, 191 | vdd_io, 192 | dimm0_ranks, 193 | dimm1_ranks, 194 | gear_down_mode, 195 | _reserved_, 196 | slow_mode, 197 | _reserved_2, 198 | address_command_control, 199 | cke_drive_strength, 200 | cs_odt_drive_strength, 201 | address_command_drive_strength, 202 | clk_drive_strength, 203 | ] 204 | ); 205 | impl_struct_serde_conversion!( 206 | LrdimmDdr4Voltages, 207 | CustomSerdeLrdimmDdr4Voltages, 208 | [_1_2V, _reserved_1,] 209 | ); 210 | impl_struct_serde_conversion!( 211 | LrdimmDdr4CadBusElement, 212 | SerdeLrdimmDdr4CadBusElement, 213 | [ 214 | dimm_slots_per_channel, 215 | ddr_rates, 216 | vdd_io, 217 | dimm0_ranks, 218 | dimm1_ranks, 219 | gear_down_mode, 220 | _reserved_, 221 | slow_mode, 222 | _reserved_2, 223 | address_command_control, 224 | cke_drive_strength, 225 | cs_odt_drive_strength, 226 | address_command_drive_strength, 227 | clk_drive_strength, 228 | ] 229 | ); 230 | 231 | impl_struct_serde_conversion!( 232 | V2_HEADER, 233 | SerdeV2_HEADER, 234 | [ 235 | signature, 236 | header_size, 237 | version, 238 | apcb_size, 239 | unique_apcb_instance, 240 | checksum_byte, 241 | _reserved_1, 242 | _reserved_2, 243 | ] 244 | ); 245 | impl_struct_serde_conversion!( 246 | V3_HEADER_EXT, 247 | SerdeV3_HEADER_EXT, 248 | [ 249 | signature, 250 | _reserved_1, 251 | _reserved_2, 252 | struct_version, 253 | data_version, 254 | ext_header_size, 255 | _reserved_3, 256 | _reserved_4, 257 | _reserved_5, 258 | _reserved_6, 259 | _reserved_7, 260 | data_offset, 261 | header_checksum, 262 | _reserved_8, 263 | _reserved_9, 264 | integrity_sign, 265 | _reserved_10, 266 | signature_ending, 267 | ] 268 | ); 269 | impl_struct_serde_conversion!( 270 | GROUP_HEADER, 271 | SerdeGROUP_HEADER, 272 | [signature, group_id, header_size, version, _reserved_, group_size,] 273 | ); 274 | impl_struct_serde_conversion!( 275 | BoardIdGettingMethodEeprom, 276 | SerdeBoardIdGettingMethodEeprom, 277 | [ 278 | access_method, 279 | i2c_controller_index, 280 | device_address, 281 | board_id_offset, 282 | board_rev_offset, 283 | ] 284 | ); 285 | impl_struct_serde_conversion!( 286 | IdRevApcbMapping, 287 | SerdeIdRevApcbMapping, 288 | [ 289 | id_and_rev_and_feature_mask, 290 | id_and_feature_value, 291 | rev_and_feature_value, 292 | board_instance_index, 293 | ] 294 | ); 295 | impl_struct_serde_conversion!( 296 | SlinkRegion, 297 | SerdeSlinkRegion, 298 | [size, alignment, socket, phys_nbio_map, interleaving, _reserved_,] 299 | ); 300 | impl_struct_serde_conversion!( 301 | AblConsoleOutControl, 302 | SerdeAblConsoleOutControl, 303 | [ 304 | enable_console_logging, 305 | enable_mem_flow_logging, 306 | enable_mem_setreg_logging, 307 | enable_mem_getreg_logging, 308 | enable_mem_status_logging, 309 | enable_mem_pmu_logging, 310 | enable_mem_pmu_sram_read_logging, 311 | enable_mem_pmu_sram_write_logging, 312 | enable_mem_test_verbose_logging, 313 | enable_mem_basic_output_logging, 314 | _reserved_, 315 | abl_console_port, 316 | ] 317 | ); 318 | impl_struct_serde_conversion!( 319 | NaplesAblConsoleOutControl, 320 | SerdeNaplesAblConsoleOutControl, 321 | [ 322 | enable_console_logging, 323 | enable_mem_flow_logging, 324 | enable_mem_setreg_logging, 325 | enable_mem_getreg_logging, 326 | enable_mem_status_logging, 327 | enable_mem_pmu_logging, 328 | enable_mem_pmu_sram_read_logging, 329 | enable_mem_pmu_sram_write_logging, 330 | enable_mem_test_verbose_logging, 331 | _reserved_0, 332 | abl_console_port, 333 | ] 334 | ); 335 | impl_struct_serde_conversion!( 336 | AblBreakpointControl, 337 | SerdeAblBreakpointControl, 338 | [enable_breakpoint, break_on_all_dies,] 339 | ); 340 | impl_struct_serde_conversion!( 341 | ExtVoltageControl, 342 | SerdeExtVoltageControl, 343 | [ 344 | enabled, 345 | _reserved_, 346 | input_port, 347 | output_port, 348 | input_port_size, 349 | output_port_size, 350 | input_port_type, 351 | output_port_type, 352 | clear_acknowledgement, 353 | _reserved_2, 354 | ] 355 | ); 356 | impl_struct_serde_conversion!( 357 | LrdimmDdr4DataBusElement, 358 | SerdeLrdimmDdr4DataBusElement, 359 | [ 360 | dimm_slots_per_channel, 361 | ddr_rates, 362 | vdd_io, 363 | dimm0_ranks, 364 | dimm1_ranks, 365 | rtt_nom, 366 | rtt_wr, 367 | rtt_park, 368 | dq_drive_strength, 369 | dqs_drive_strength, 370 | odt_drive_strength, 371 | pmu_phy_vref, 372 | vref_dq, 373 | ] 374 | ); 375 | impl_struct_serde_conversion!( 376 | MaxFreqElement, 377 | SerdeMaxFreqElement, 378 | [dimm_slots_per_channel, _reserved_, conditions, speeds,] 379 | ); 380 | impl_struct_serde_conversion!( 381 | LrMaxFreqElement, 382 | SerdeLrMaxFreqElement, 383 | [dimm_slots_per_channel, _reserved_, conditions, speeds,] 384 | ); 385 | impl_struct_serde_conversion!( 386 | Gpio, 387 | SerdeGpio, 388 | [pin, iomux_control, bank_control,] 389 | ); 390 | impl_struct_serde_conversion!( 391 | ErrorOutControlBeepCode, 392 | CustomSerdeErrorOutControlBeepCode, 393 | [custom_error_type, peak_map, peak_attr,] 394 | ); 395 | impl_struct_serde_conversion!( 396 | ErrorOutControl116, 397 | SerdeErrorOutControl116, 398 | [ 399 | enable_error_reporting, 400 | enable_error_reporting_gpio, 401 | enable_error_reporting_beep_codes, 402 | enable_using_handshake, 403 | input_port, 404 | output_delay, 405 | output_port, 406 | stop_on_first_fatal_error, 407 | _reserved_, 408 | input_port_size, 409 | output_port_size, 410 | input_port_type, 411 | output_port_type, 412 | clear_acknowledgement, 413 | _reserved_before_gpio, 414 | error_reporting_gpio, 415 | _reserved_after_gpio, 416 | beep_code_table, 417 | enable_heart_beat, 418 | enable_power_good_gpio, 419 | power_good_gpio, 420 | _reserved_end, 421 | ] 422 | ); 423 | impl_struct_serde_conversion!( 424 | ErrorOutControl112, 425 | SerdeErrorOutControl112, 426 | [ 427 | enable_error_reporting, 428 | enable_error_reporting_gpio, 429 | enable_error_reporting_beep_codes, 430 | enable_using_handshake, 431 | input_port, 432 | output_delay, 433 | output_port, 434 | stop_on_first_fatal_error, 435 | _reserved_, 436 | input_port_size, 437 | output_port_size, 438 | input_port_type, 439 | output_port_type, 440 | clear_acknowledgement, 441 | _reserved_before_gpio, 442 | error_reporting_gpio, 443 | _reserved_after_gpio, 444 | beep_code_table, 445 | enable_heart_beat, 446 | enable_power_good_gpio, 447 | power_good_gpio, 448 | _reserved_end, 449 | ] 450 | ); 451 | 452 | impl_struct_serde_conversion!( 453 | DimmsPerChannelSelector, 454 | SerdeDimmsPerChannelSelector, 455 | [one_dimm, two_dimms, three_dimms, four_dimms, _reserved_1,] 456 | ); 457 | 458 | impl_struct_serde_conversion!( 459 | ErrorOutControlBeepCodePeakAttr, 460 | SerdeErrorOutControlBeepCodePeakAttr, 461 | [peak_count, pulse_width, repeat_count, _reserved_1,] 462 | ); 463 | 464 | impl_struct_serde_conversion!( 465 | OdtPatPatterns, 466 | SerdeOdtPatPatterns, 467 | [reading_pattern, _reserved_1, writing_pattern, _reserved_2,] 468 | ); 469 | 470 | impl_struct_serde_conversion!( 471 | LrdimmDdr4OdtPatDimmRankBitmaps, 472 | SerdeLrdimmDdr4OdtPatDimmRankBitmaps, 473 | [dimm0, dimm1, dimm2, _reserved_1,] 474 | ); 475 | impl_struct_serde_conversion!( 476 | Ddr4OdtPatDimmRankBitmaps, 477 | SerdeDdr4OdtPatDimmRankBitmaps, 478 | [dimm0, dimm1, dimm2, _reserved_1,] 479 | ); 480 | 481 | impl_struct_serde_conversion!( 482 | DimmSlotsSelection, 483 | SerdeDimmSlotsSelection, 484 | [dimm_slot_0, dimm_slot_1, dimm_slot_2, dimm_slot_3, _reserved_1,] 485 | ); 486 | impl_struct_serde_conversion!( 487 | ChannelIdsSelection, 488 | SerdeChannelIdsSelection, 489 | [a, b, c, d, e, f, g, h,] 490 | ); 491 | impl_struct_serde_conversion!( 492 | ChannelIdsSelection12, 493 | SerdeChannelIdsSelection12, 494 | [a, b, c, d, e, f, g, h, i, j, k, l, _reserved_1,] 495 | ); 496 | 497 | impl_struct_serde_conversion!( 498 | SocketIds, 499 | SerdeSocketIds, 500 | [ 501 | socket_0, socket_1, socket_2, socket_3, socket_4, socket_5, socket_6, 502 | socket_7, 503 | ] 504 | ); 505 | 506 | impl_struct_serde_conversion!( 507 | Ddr4OdtPatElement, 508 | SerdeDdr4OdtPatElement, 509 | [ 510 | dimm_rank_bitmaps, 511 | cs0_odt_patterns, 512 | cs1_odt_patterns, 513 | cs2_odt_patterns, 514 | cs3_odt_patterns, 515 | ] 516 | ); 517 | impl_struct_serde_conversion!( 518 | LrdimmDdr4OdtPatElement, 519 | SerdeLrdimmDdr4OdtPatElement, 520 | [ 521 | dimm_rank_bitmaps, 522 | cs0_odt_patterns, 523 | cs1_odt_patterns, 524 | cs2_odt_patterns, 525 | cs3_odt_patterns, 526 | ] 527 | ); 528 | impl_struct_serde_conversion!( 529 | CkeTristateMap, 530 | SerdeCkeTristateMap, 531 | [type_, payload_size, sockets, channels, dimms, connections,] 532 | ); 533 | impl_struct_serde_conversion!( 534 | OdtTristateMap, 535 | SerdeOdtTristateMap, 536 | [type_, payload_size, sockets, channels, dimms, connections,] 537 | ); 538 | impl_struct_serde_conversion!( 539 | CsTristateMap, 540 | SerdeCsTristateMap, 541 | [type_, payload_size, sockets, channels, dimms, connections,] 542 | ); 543 | impl_struct_serde_conversion!( 544 | MaxDimmsPerChannel, 545 | SerdeMaxDimmsPerChannel, 546 | [type_, payload_size, sockets, channels, dimms, value,] 547 | ); 548 | impl_struct_serde_conversion!( 549 | MaxDimmsPerChannel6, 550 | SerdeMaxDimmsPerChannel6, 551 | [type_, payload_size, sockets, channels, dimms, value, _padding_0] 552 | ); 553 | impl_struct_serde_conversion!( 554 | MemclkMap, 555 | SerdeMemclkMap, 556 | [type_, payload_size, sockets, channels, dimms, connections,] 557 | ); 558 | impl_struct_serde_conversion!( 559 | MaxChannelsPerSocket, 560 | SerdeMaxChannelsPerSocket, 561 | [type_, payload_size, sockets, channels, dimms, value,] 562 | ); 563 | impl_struct_serde_conversion!( 564 | MemBusSpeed, 565 | SerdeMemBusSpeed, 566 | [type_, payload_size, sockets, channels, dimms, timing_mode, bus_speed,] 567 | ); 568 | impl_struct_serde_conversion!( 569 | MaxCsPerChannel, 570 | SerdeMaxCsPerChannel, 571 | [type_, payload_size, sockets, channels, dimms, value,] 572 | ); 573 | impl_struct_serde_conversion!( 574 | MemTechnology, 575 | SerdeMemTechnology, 576 | [type_, payload_size, sockets, channels, dimms, technology_type,] 577 | ); 578 | impl_struct_serde_conversion!( 579 | WriteLevellingSeedDelay, 580 | SerdeWriteLevellingSeedDelay, 581 | [type_, payload_size, sockets, channels, dimms, seed, ecc_seed,] 582 | ); 583 | impl_struct_serde_conversion!( 584 | RxEnSeed, 585 | SerdeRxEnSeed, 586 | [type_, payload_size, sockets, channels, dimms, seed, ecc_seed,] 587 | ); 588 | impl_struct_serde_conversion!( 589 | LrDimmNoCs6Cs7Routing, 590 | SerdeLrDimmNoCs6Cs7Routing, 591 | [type_, payload_size, sockets, channels, dimms, value,] 592 | ); 593 | impl_struct_serde_conversion!( 594 | SolderedDownSodimm, 595 | SerdeSolderedDownSodimm, 596 | [type_, payload_size, sockets, channels, dimms, value,] 597 | ); 598 | impl_struct_serde_conversion!( 599 | LvDimmForce1V5, 600 | SerdeLvDimmForce1V5, 601 | [type_, payload_size, sockets, channels, dimms, value,] 602 | ); 603 | impl_struct_serde_conversion!( 604 | MinimumRwDataEyeWidth, 605 | SerdeMinimumRwDataEyeWidth, 606 | [ 607 | type_, 608 | payload_size, 609 | sockets, 610 | channels, 611 | dimms, 612 | min_read_data_eye_width, 613 | min_write_data_eye_width, 614 | ] 615 | ); 616 | impl_struct_serde_conversion!( 617 | CpuFamilyFilter, 618 | SerdeCpuFamilyFilter, 619 | [type_, payload_size, cpu_family_revision,] 620 | ); 621 | impl_struct_serde_conversion!( 622 | SolderedDownDimmsPerChannel, 623 | SerdeSolderedDownDimmsPerChannel, 624 | [type_, payload_size, sockets, channels, dimms, value,] 625 | ); 626 | impl_struct_serde_conversion!( 627 | MemPowerPolicy, 628 | SerdeMemPowerPolicy, 629 | [type_, payload_size, sockets, channels, dimms, value,] 630 | ); 631 | impl_struct_serde_conversion!( 632 | MotherboardLayers, 633 | SerdeMotherboardLayers, 634 | [type_, payload_size, sockets, channels, dimms, value,] 635 | ); 636 | impl_struct_serde_conversion!( 637 | IdApcbMapping, 638 | SerdeIdApcbMapping, 639 | [id_and_feature_mask, id_and_feature_value, board_instance_index,] 640 | ); 641 | impl_struct_serde_conversion!( 642 | BoardIdGettingMethodCustom, 643 | SerdeBoardIdGettingMethodCustom, 644 | [access_method, feature_mask,] 645 | ); 646 | impl_struct_serde_conversion!( 647 | BoardIdGettingMethodGpio, 648 | SerdeBoardIdGettingMethodGpio, 649 | [access_method, bit_locations,] 650 | ); 651 | impl_struct_serde_conversion!( 652 | BoardIdGettingMethodSmbus, 653 | SerdeBoardIdGettingMethodSmbus, 654 | [ 655 | access_method, 656 | i2c_controller_index, 657 | i2c_mux_address, 658 | mux_control_address, 659 | mux_channel, 660 | smbus_address, 661 | register_index, 662 | ] 663 | ); 664 | impl_struct_serde_conversion!( 665 | FchGppClkMapSelection, 666 | SerdeFchGppClkMapSelection, 667 | [ 668 | s0_gpp0_off, 669 | s0_gpp1_off, 670 | s0_gpp4_off, 671 | s0_gpp2_off, 672 | s0_gpp3_off, 673 | s0_gpp5_off, 674 | _reserved_1, 675 | s1_gpp0_off, 676 | s1_gpp1_off, 677 | s1_gpp4_off, 678 | s1_gpp2_off, 679 | s1_gpp3_off, 680 | s1_gpp5_off, 681 | _reserved_2, 682 | ] 683 | ); 684 | impl_struct_serde_conversion!(Terminator, SerdeTerminator, [type_,]); 685 | impl_struct_serde_conversion!( 686 | DdrPostPackageRepairElement, 687 | CustomSerdeDdrPostPackageRepairElement, 688 | [raw_body,] 689 | ); 690 | impl_struct_serde_conversion!( 691 | DdrPostPackageRepairBody, 692 | SerdeDdrPostPackageRepairBody, 693 | [ 694 | bank, 695 | rank_multiplier, 696 | xdevice_width, 697 | chip_select, 698 | column, 699 | hard_repair, 700 | valid, 701 | target_device, 702 | row, 703 | socket, 704 | channel, 705 | _reserved_1, 706 | ] 707 | ); 708 | impl_struct_serde_conversion!( 709 | DimmInfoSmbusElement, 710 | SerdeDimmInfoSmbusElement, 711 | [ 712 | dimm_slot_present, 713 | socket_id, 714 | channel_id, 715 | dimm_id, 716 | dimm_smbus_address, 717 | i2c_mux_address, 718 | mux_control_address, 719 | mux_channel, 720 | ] 721 | ); 722 | impl_struct_serde_conversion!( 723 | ConsoleOutControl, 724 | SerdeConsoleOutControl, 725 | [abl_console_out_control, abl_breakpoint_control, _reserved_,] 726 | ); 727 | impl_struct_serde_conversion!( 728 | NaplesConsoleOutControl, 729 | SerdeNaplesConsoleOutControl, 730 | [abl_console_out_control, abl_breakpoint_control, _reserved_,] 731 | ); 732 | impl_struct_serde_conversion!( 733 | BoardInstances, 734 | SerdeBoardInstances, 735 | [ 736 | instance_0, 737 | instance_1, 738 | instance_2, 739 | instance_3, 740 | instance_4, 741 | instance_5, 742 | instance_6, 743 | instance_7, 744 | instance_8, 745 | instance_9, 746 | instance_10, 747 | instance_11, 748 | instance_12, 749 | instance_13, 750 | instance_14, 751 | instance_15, 752 | ] 753 | ); 754 | impl_struct_serde_conversion!( 755 | Parameter, 756 | SerdeParameter, 757 | [time_point, token, value_size, value, _reserved_0,] 758 | ); 759 | impl_struct_serde_conversion!( 760 | ParameterAttributes, 761 | SerdeParameterAttributes, 762 | [time_point, token, size_minus_one, _reserved_0,] 763 | ); 764 | impl_struct_serde_conversion!( 765 | MemPmuBistTestSelect, 766 | SerdeMemPmuBistTestSelect, 767 | [ 768 | algorithm_1, 769 | algorithm_2, 770 | algorithm_3, 771 | algorithm_4, 772 | algorithm_5, 773 | _reserved_0, 774 | ] 775 | ); 776 | impl_struct_serde_conversion!( 777 | MemPmuBistAlgorithmSelect, 778 | SerdeMemPmuBistAlgorithmSelect, 779 | [ 780 | algorithm_1, 781 | algorithm_2, 782 | algorithm_3, 783 | algorithm_4, 784 | algorithm_5, 785 | algorithm_6, 786 | algorithm_7, 787 | algorithm_8, 788 | algorithm_9, 789 | _reserved_0, 790 | ] 791 | ); 792 | impl_struct_serde_conversion!( 793 | RdimmDdr5BusElementPayload, 794 | SerdeRdimmDdr5BusElementPayload, 795 | [ 796 | total_size, 797 | ca_timing_mode, 798 | dimm0_rttnomwr, 799 | dimm0_rttnomrd, 800 | dimm0_rttwr, 801 | dimm0_rttpack, 802 | dimm0_dqs_rttpark, 803 | dimm1_rttnomwr, 804 | dimm1_rttnomrd, 805 | dimm1_rttwr, 806 | dimm1_rttpack, 807 | dimm1_dqs_rttpark, 808 | dram_drv, 809 | ck_odt_a, 810 | cs_odt_a, 811 | ca_odt_a, 812 | ck_odt_b, 813 | cs_odt_b, 814 | ca_odt_b, 815 | p_odt, 816 | dq_drv, 817 | alert_pullup, 818 | ca_drv, 819 | phy_vref, 820 | dq_vref, 821 | ca_vref, 822 | cs_vref, 823 | d_ca_vref, 824 | d_cs_vref, 825 | rx_dfe, 826 | tx_dfe, 827 | ] 828 | ); 829 | impl_struct_serde_conversion!( 830 | Ddr5CaPinMapElementLane, 831 | SerdeDdr5CaPinMapElementLane, 832 | [pins,] 833 | ); 834 | impl_struct_serde_conversion!( 835 | DdrDqPinMapElementLane, 836 | SerdeDdrDqPinMapElementLane, 837 | [pins,] 838 | ); 839 | impl_struct_serde_conversion!( 840 | DdrDqPinMapElement, 841 | SerdeDdrDqPinMapElement, 842 | [lanes,] 843 | ); 844 | impl_struct_serde_conversion!( 845 | RdimmDdr5BusElementHeader, 846 | SerdeRdimmDdr5BusElementHeader, 847 | [ 848 | total_size, 849 | target_memclk, 850 | dimm_slots_per_channel, 851 | dimm0_rank_bitmap, 852 | dimm1_rank_bitmap, 853 | sdram_io_width_bitmap, 854 | ] 855 | ); 856 | impl_struct_serde_conversion!( 857 | RdimmDdr5BusElement, 858 | SerdeRdimmDdr5BusElement, 859 | [header, payload,] 860 | ); 861 | impl_struct_serde_conversion!( 862 | EspiInit, 863 | SerdeEspiInit, 864 | [ 865 | espi_enabled, 866 | data_bus_select, 867 | clock_pin_select, 868 | cs_pin_select, 869 | clock_frequency, 870 | io_mode, 871 | alert_mode, 872 | pltrst_deassert, 873 | io80_decoding_enabled, 874 | io6064_decoding_enabled, 875 | io_range_sizes_minus_one, 876 | io_range_bases, 877 | mmio_range_sizes_minus_one, 878 | mmio_range_bases, 879 | irq_mask, 880 | irq_polarity, 881 | cputemp_rtctime_vw_enabled, 882 | cputemp_rtctime_vw_index_select, 883 | _reserved_1, 884 | _reserved_2, 885 | cpu_temp_mmio_base, 886 | rtc_time_mmio_base, 887 | bus_master_enabled, 888 | _reserved_3, 889 | _reserved_4, 890 | _reserved_5, 891 | ] 892 | ); 893 | impl_struct_serde_conversion!( 894 | EspiSioInitElement, 895 | SerdeEspiSioInitElement, 896 | [io_port, access_width, data_mask, data_or,] 897 | ); 898 | impl_struct_serde_conversion!( 899 | Ddr5CaPinMapElement, 900 | SerdeDdr5CaPinMapElement, 901 | [lanes,] 902 | ); 903 | impl_struct_serde_conversion!( 904 | MemDfeSearchElementHeader, 905 | SerdeMemDfeSearchElementHeader, 906 | [ 907 | total_size, 908 | dimm_slots_per_channel, 909 | dimm0_rank_bitmap, 910 | dimm1_rank_bitmap, 911 | sdram_io_width_bitmap, 912 | ] 913 | ); 914 | impl_struct_serde_conversion!( 915 | MemDfeSearchElementHeader12, 916 | SerdeMemDfeSearchElementHeader12, 917 | [ 918 | total_size, 919 | _reserved, 920 | dimm_slots_per_channel, 921 | dimm0_rank_bitmap, 922 | dimm1_rank_bitmap, 923 | sdram_io_width_bitmap, 924 | ] 925 | ); 926 | impl_struct_serde_conversion!( 927 | MemDfeSearchElementPayload12, 928 | SerdeMemDfeSearchElementPayload12, 929 | [ 930 | total_size, 931 | tx_dfe_tap_1_start, 932 | tx_dfe_tap_1_end, 933 | tx_dfe_tap_2_start, 934 | tx_dfe_tap_2_end, 935 | tx_dfe_tap_3_start, 936 | tx_dfe_tap_3_end, 937 | tx_dfe_tap_4_start, 938 | tx_dfe_tap_4_end, 939 | ] 940 | ); 941 | impl_struct_serde_conversion!( 942 | MemDfeSearchElementPayloadExt12, 943 | SerdeMemDfeSearchElementPayloadExt12, 944 | [ 945 | total_size, 946 | rx_dfe_tap_2_min_mv, 947 | rx_dfe_tap_2_max_mv, 948 | rx_dfe_tap_3_min_mv, 949 | rx_dfe_tap_3_max_mv, 950 | rx_dfe_tap_4_min_mv, 951 | rx_dfe_tap_4_max_mv, 952 | _reserved_1, 953 | _reserved_2, 954 | ] 955 | ); 956 | impl_struct_serde_conversion!( 957 | MemDfeSearchElement32, 958 | SerdeMemDfeSearchElement32, 959 | [header, payload, payload_ext] 960 | ); 961 | impl_struct_serde_conversion!( 962 | MemDfeSearchElement36, 963 | SerdeMemDfeSearchElement36, 964 | [header, payload, payload_ext] 965 | ); 966 | impl_struct_serde_conversion!( 967 | DfXgmiChannelTypeSelect, 968 | SerdeDfXgmiChannelTypeSelect, 969 | [s0l0, s0l1, s0l2, s0l3, s1l0, s1l1, s1l2, s1l3,] 970 | ); 971 | impl_struct_serde_conversion!( 972 | FchConsoleOutSerialPortEspiControllerSelect, 973 | SerdeFchConsoleOutSerialPortEspiControllerSelect, 974 | [ 975 | espi_controller, 976 | _reserved_0, 977 | io_2e_2f_disabled, 978 | io_4e_4f_disabled, 979 | _reserved_1, 980 | ] 981 | ); 982 | 983 | impl_struct_serde_conversion!( 984 | DfXgmiAcDcCoupledLink, 985 | SerdeDfXgmiAcDcCoupledLink, 986 | [ 987 | socket_0_link_0, 988 | socket_0_link_1, 989 | socket_0_link_2, 990 | socket_0_link_3, 991 | socket_1_link_0, 992 | socket_1_link_1, 993 | socket_1_link_2, 994 | socket_1_link_3, 995 | ] 996 | ); 997 | impl_struct_serde_conversion!( 998 | EarlyPcieConfigBody, 999 | SerdeEarlyPcieConfigBody, 1000 | [ 1001 | start_lane, 1002 | end_lane, 1003 | socket, 1004 | port_present, 1005 | link_speed, 1006 | reset_pin, 1007 | root_function, 1008 | root_device, 1009 | max_payload, 1010 | tx_deemphasis, 1011 | _reserved_1, 1012 | _reserved_2, 1013 | ] 1014 | ); 1015 | impl_struct_serde_conversion!( 1016 | EarlyPcieConfigElement, 1017 | CustomSerdeEarlyPcieConfigElement, 1018 | [raw_body,] 1019 | ); 1020 | impl_struct_serde_conversion!( 1021 | PmuBistVendorAlgorithmElement, 1022 | SerdePmuBistVendorAlgorithmElement, 1023 | [dram_manufacturer_id, algorithm_bit_mask,] 1024 | ); 1025 | impl_struct_serde_conversion!( 1026 | Ddr5RawCardConfigElementHeader32, 1027 | SerdeDdr5RawCardConfigElementHeader32, 1028 | [ 1029 | total_size, 1030 | mem_clk, // DDR6400_FREQUENCY 1031 | dimm_type, // bitmap rank type 1032 | dev_width, // bitmap of SDRAM IO width 1033 | _reserved_1, 1034 | _reserved_2, 1035 | rcd_manufacturer_id, // JEDEC 1036 | rcd_generation, 1037 | _reserved_3, 1038 | _reserved_4, 1039 | _reserved_5, 1040 | raw_card_dev, // raw card revision 1041 | dram_die_stepping_revision, 1042 | dram_density, 1043 | _reserved_6, 1044 | _reserved_7, 1045 | _reserved_8, 1046 | ] 1047 | ); 1048 | impl_struct_serde_conversion!( 1049 | Ddr5RawCardConfigElementPayload, 1050 | SerdeDdr5RawCardConfigElementPayload, 1051 | [ 1052 | total_size, 1053 | qck_dev0, // those are numbers that go 1:1; NA = 255 means use ABL default 1054 | qck_dev1, 1055 | qck_dev2, 1056 | qck_dev3, 1057 | qck_dev4, 1058 | qck_dev5, 1059 | qck_dev6, 1060 | qck_dev7, 1061 | qck_dev8, 1062 | qck_dev9, 1063 | qck_drive_strength, 1064 | qck_slew, // NA = 255; MODERATE = 0, FAST = 1, SLOW = 2 1065 | qcs_dev0, // IMP_OFF 1066 | qcs_dev1, 1067 | qcs_dev2, 1068 | qcs_dev3, 1069 | qcs_dev4, 1070 | qcs_dev5, 1071 | qcs_dev6, 1072 | qcs_dev7, 1073 | qcs_dev8, 1074 | qcs_dev9, 1075 | qcs_drive_strength, 1076 | qcs_slew, // NA 1077 | qcs_vref, 1078 | qca_dev0, // OFF 1079 | qca_dev1, 1080 | qca_dev2, 1081 | qca_dev3, 1082 | qca_dev4, 1083 | qca_dev5, 1084 | qca_dev6, 1085 | qca_dev7, 1086 | qca_dev8, 1087 | qca_dev9, 1088 | qca_drive_strength, 1089 | qca_slew, // NA 1090 | qca_vref, 1091 | ] 1092 | ); 1093 | impl_struct_serde_conversion!( 1094 | Ddr5RawCardConfigElement, 1095 | SerdeDdr5RawCardConfigElement, 1096 | [header, payload,] 1097 | ); 1098 | impl_struct_serde_conversion!( 1099 | Ddr5TrainingOverrideEntryHeaderFlagsMemClkMask, 1100 | SerdeDdr5TrainingOverrideEntryHeaderFlagsMemClkMask, 1101 | [ddr4800, ddr5600, ddr6000, ddr6400,] 1102 | ); 1103 | impl_struct_serde_conversion!( 1104 | Ddr5TrainingOverrideEntryHeaderChannelSelectorMask, 1105 | SerdeDdr5TrainingOverrideEntryHeaderChannelSelectorMask, 1106 | [c_0, c_1, c_2, c_3, c_4, c_5, c_6, c_7, c_8, c_9, c_10, c_11,] 1107 | ); 1108 | // Note: Using impl_struct_serde_conversion would expose the fact that modular-bitfield doesn't actually have i8 (or i4). 1109 | impl_struct_serde_conversion!( 1110 | Ddr5TrainingOverrideEntryHeaderFlags, 1111 | CustomSerdeDdr5TrainingOverrideEntryHeaderFlags, 1112 | [ 1113 | selected_mem_clks, 1114 | selected_channels, 1115 | read_dq_delay_offset, 1116 | read_dq_vref_offset, 1117 | write_dq_delay_offset, 1118 | write_dq_vref_offset 1119 | ] 1120 | ); 1121 | impl_struct_serde_conversion!( 1122 | Ddr5TrainingOverride40Element, 1123 | SerdeDdr5TrainingOverride40Element, 1124 | [length, dimm_module_part_number, _reserved_1, flags,] 1125 | ); 1126 | -------------------------------------------------------------------------------- /src/struct_accessors.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #![macro_use] 6 | 7 | use crate::types::Error; 8 | use crate::types::Result; 9 | use four_cc::FourCC; 10 | use num_traits::{FromPrimitive, ToPrimitive}; 11 | use zerocopy::byteorder::LittleEndian; 12 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, U16, U32, U64}; 13 | 14 | #[cfg(feature = "serde")] 15 | use serde::{Deserialize, Serialize}; 16 | 17 | pub(crate) trait Getter { 18 | fn get1(self) -> T; 19 | } 20 | impl Getter for U64 { 21 | fn get1(self) -> u64 { 22 | self.get() 23 | } 24 | } 25 | impl Getter for U32 { 26 | fn get1(self) -> u32 { 27 | self.get() 28 | } 29 | } 30 | impl Getter for u8 { 31 | fn get1(self) -> u8 { 32 | self 33 | } 34 | } 35 | impl Getter> for i8 { 36 | fn get1(self) -> Result { 37 | Ok(self) 38 | } 39 | } 40 | impl<'a> Getter<&'a [u8]> for &'a [u8] { 41 | fn get1(self) -> &'a [u8] { 42 | self 43 | } 44 | } 45 | impl Getter> for u8 { 46 | fn get1(self) -> Result { 47 | T::from_u8(self).ok_or(Error::EntryTypeMismatch) 48 | } 49 | } 50 | impl Getter> for U16 { 51 | fn get1(self) -> Result { 52 | T::from_u16(self.get()).ok_or(Error::EntryTypeMismatch) 53 | } 54 | } 55 | impl Getter> for U32 { 56 | fn get1(self) -> Result { 57 | T::from_u32(self.get()).ok_or(Error::EntryTypeMismatch) 58 | } 59 | } 60 | impl Getter> for U64 { 61 | fn get1(self) -> Result { 62 | T::from_u64(self.get()).ok_or(Error::EntryTypeMismatch) 63 | } 64 | } 65 | // For Token 66 | impl Getter> for u32 { 67 | fn get1(self) -> Result { 68 | T::from_u32(self).ok_or(Error::EntryTypeMismatch) 69 | } 70 | } 71 | #[derive( 72 | Default, 73 | Debug, 74 | PartialEq, 75 | FromBytes, 76 | Immutable, 77 | IntoBytes, 78 | KnownLayout, 79 | Clone, 80 | Copy, 81 | )] 82 | #[repr(transparent)] 83 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 84 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 85 | pub struct BU8(pub(crate) u8); 86 | impl Getter> for BU8 { 87 | fn get1(self) -> Result { 88 | match self.0 { 89 | 0 => Ok(false), 90 | 1 => Ok(true), 91 | _ => Err(Error::EntryTypeMismatch), 92 | } 93 | } 94 | } 95 | 96 | impl Getter> for [u8; 4] { 97 | fn get1(self) -> Result { 98 | Ok(FourCC(self)) 99 | } 100 | } 101 | 102 | impl Setter for [u8; 4] { 103 | fn set1(&mut self, value: FourCC) { 104 | *self = value.0 105 | } 106 | } 107 | impl Setter for i8 { 108 | fn set1(&mut self, value: i8) { 109 | *self = value 110 | } 111 | } 112 | 113 | impl From for BU8 { 114 | fn from(value: bool) -> Self { 115 | match value { 116 | true => Self(1), 117 | false => Self(0), 118 | } 119 | } 120 | } 121 | 122 | #[derive( 123 | Debug, PartialEq, FromBytes, IntoBytes, KnownLayout, Immutable, Clone, Copy, 124 | )] 125 | #[repr(C, packed)] 126 | pub(crate) struct BLU16(pub(crate) U16); 127 | impl Getter> for BLU16 { 128 | fn get1(self) -> Result { 129 | match self.0.get() { 130 | 0 => Ok(false), 131 | 1 => Ok(true), 132 | _ => Err(Error::EntryTypeMismatch), 133 | } 134 | } 135 | } 136 | 137 | pub(crate) trait Setter { 138 | fn set1(&mut self, value: T); 139 | } 140 | impl Setter for U64 { 141 | fn set1(&mut self, value: T) { 142 | self.set(value.to_u64().unwrap()) 143 | } 144 | } 145 | impl Setter for U32 { 146 | fn set1(&mut self, value: T) { 147 | self.set(value.to_u32().unwrap()) 148 | } 149 | } 150 | impl Setter for U16 { 151 | // maybe never happens 152 | fn set1(&mut self, value: T) { 153 | self.set(value.to_u16().unwrap()) 154 | } 155 | } 156 | impl Setter for u8 { 157 | fn set1(&mut self, value: T) { 158 | *self = value.to_u8().unwrap() 159 | } 160 | } 161 | impl Setter for BU8 { 162 | fn set1(&mut self, value: bool) { 163 | *self = BU8(match value { 164 | false => 0, 165 | true => 1, 166 | }) 167 | } 168 | } 169 | impl Setter for BLU16 { 170 | fn set1(&mut self, value: bool) { 171 | *self = Self(match value { 172 | false => 0.into(), 173 | true => 1.into(), 174 | }) 175 | } 176 | } 177 | 178 | /// Since primitive types are not a Result, this just wraps them into a Result. 179 | /// The reason this exists is because our macros don't know whether or not 180 | /// the getters return Result--but they have to be able to call map_err 181 | /// on it in case these DO return Result. 182 | #[allow(dead_code)] 183 | pub(crate) trait DummyErrorChecks: Sized { 184 | fn map_err(self, _: O) -> core::result::Result 185 | where 186 | O: Fn(Self) -> F, 187 | { 188 | Ok(self) 189 | } 190 | } 191 | 192 | impl DummyErrorChecks for u32 {} 193 | 194 | impl DummyErrorChecks for u16 {} 195 | 196 | impl DummyErrorChecks for u8 {} 197 | 198 | impl DummyErrorChecks for i8 {} 199 | 200 | impl DummyErrorChecks for bool {} 201 | 202 | /// This macro expects a struct as a parameter (attributes are fine) and then, 203 | /// first, defines the exact same struct, and also a more user-friendly struct 204 | /// (name starts with "Serde") that can be used for serde (note: if you want 205 | /// to use it for that, you still have to impl Serialize and Deserialize for 206 | /// the former manually--forwarding to the respective "Serde" struct--which 207 | /// does have "Serialize" and "Deserialize" implemented). 208 | /// Afterwards, it automatically impls getters (and maybe setters) for the 209 | /// fields where there was "get" (and maybe "set") specified. 210 | /// The getters and setters so generated are hardcoded as calling 211 | /// self.field.get1 and self.field.set1, respectively. These are usually 212 | /// provided by a Getter and Setter trait impl (for example the ones in the same 213 | /// file this macro is in). 214 | /// 215 | /// Note: If you want to add a docstring, put it before the struct inside the 216 | /// macro parameter at the usage site, not before the macro call. 217 | /// 218 | /// This call also defines serde_* and serde_with* getters and setters with an 219 | /// optionally specified serde type that is used during serialization and 220 | /// deserialization 221 | /// 222 | /// This also defines a "builder" fn that will be used by the deserializers at 223 | /// the very beginning. That fn just calls "default()" (out of 224 | /// necessity--because we don't want to have lots of extra partial struct 225 | /// definitions). This is different to what make_bitfield_serde does 226 | /// (where it calls "new()") (on purpose). 227 | /// 228 | /// Field syntax: 229 | /// NAME [|| [SERDE_META]* SERDE_TYPE] : TYPE 230 | /// [| pub get GETTER_RETURN_TYPE [: pub set SETTER_PARAMETER_TYPE]] 231 | /// 232 | /// The brackets denote optional parts. 233 | /// Defines field NAME as TYPE. TYPE usually has to be the lowest-level 234 | /// machine type (so it's "Little-endian u32", or "3 bits", or similar). 235 | /// SERDE_TYPE, if given, will be the type used for the serde field. 236 | /// If SERDE_TYPE is not given, TYPE will be used instead. 237 | /// The "pub get" will use the given GETTER_RETURN_TYPE as the resulting type 238 | /// of the generated getter, using Getter converters to get there as needed. 239 | /// The "pub set" will use the given SETTER_PARAMETER_TYPE as the 240 | /// parameter type of the generated setter, using Setter converters to get 241 | /// there as needed. 242 | macro_rules! make_accessors {( 243 | $(#[$struct_meta:meta])* 244 | $struct_vis:vis 245 | struct $StructName:ident { 246 | $( 247 | $(#[$field_meta:meta])* 248 | $field_vis:vis 249 | $field_name:ident 250 | $(|| $(#[$serde_field_orig_meta:meta])* $serde_ty:ty : $field_orig_ty:ty)? 251 | $(: $field_ty:ty)? 252 | $(| $getter_vis:vis get $field_user_ty:ty 253 | $(: $setter_vis:vis set $field_setter_user_ty:ty)?)? 254 | ),* $(,)? 255 | } 256 | ) => ( 257 | $(#[$struct_meta])* 258 | $struct_vis 259 | struct $StructName { 260 | $( 261 | $(#[$field_meta])* 262 | $field_vis 263 | $($field_name: $field_ty,)? 264 | $($field_name: $field_orig_ty,)? 265 | )* 266 | } 267 | 268 | impl $StructName { 269 | #[allow(dead_code)] 270 | pub fn build(&self) -> Self { 271 | self.clone() 272 | } 273 | #[allow(dead_code)] 274 | pub fn builder() -> Self { 275 | Self::default() 276 | } 277 | $( 278 | $( 279 | #[inline] 280 | #[allow(dead_code)] 281 | $getter_vis 282 | fn $field_name (self: &'_ Self) 283 | -> Result<$field_user_ty> 284 | { 285 | self.$field_name.get1() 286 | } 287 | $( 288 | paste! { 289 | #[inline] 290 | #[allow(dead_code)] 291 | $setter_vis 292 | fn [] (self: &'_ mut Self, value: $field_setter_user_ty) { 293 | self.$field_name.set1(value) 294 | } 295 | #[inline] 296 | #[allow(dead_code)] 297 | $setter_vis 298 | fn [](self: &mut Self, value: $field_setter_user_ty) -> &mut Self { 299 | let result = self; 300 | result.$field_name.set1(value); 301 | result 302 | } 303 | } 304 | )? 305 | )? 306 | $( 307 | paste! { 308 | #[inline] 309 | #[allow(dead_code)] 310 | pub(crate) fn [] (self: &'_ Self) 311 | -> Result<$serde_ty> 312 | { 313 | self.$field_name.get1() 314 | } 315 | #[inline] 316 | #[allow(dead_code)] 317 | pub(crate) fn [](self: &mut Self, value: $serde_ty) -> &mut Self { 318 | let result = self; 319 | result.$field_name.set1(value); 320 | result 321 | }} 322 | )? 323 | $( 324 | paste! { 325 | #[inline] 326 | #[allow(dead_code)] 327 | pub(crate) fn [] (self: &'_ Self) 328 | -> Result<$field_ty> 329 | { 330 | self.$field_name.get1() 331 | } 332 | #[inline] 333 | #[allow(dead_code)] 334 | pub(crate) fn [](self: &mut Self, value: $field_ty) -> &mut Self { 335 | let result = self; 336 | result.$field_name = value.into(); 337 | result 338 | }} 339 | )? 340 | )* 341 | } 342 | // for serde 343 | #[cfg(feature = "serde")] 344 | paste::paste!{ 345 | #[doc(hidden)] 346 | #[allow(non_camel_case_types)] 347 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 348 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 349 | // Doing remoting automatically would make it impossible for the user to use another one. 350 | // Since the config format presumably needs to be 351 | // backward-compatible, that wouldn't be such a great idea. 352 | #[cfg_attr(feature = "serde", serde(rename = "" $StructName))] 353 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 354 | pub(crate) struct [] { 355 | $( 356 | $(pub $field_name: $field_ty,)? 357 | $($(#[$serde_field_orig_meta])* pub $field_name: $serde_ty,)? 358 | )* 359 | } 360 | } 361 | )} 362 | 363 | pub(crate) use make_accessors; 364 | 365 | /// A variant of the make_accessors macro for modular_bitfields. 366 | macro_rules! make_bitfield_serde {( 367 | $(#[$struct_meta:meta])* 368 | $struct_vis:vis 369 | struct $StructName:ident { 370 | $( 371 | $(#[$field_meta:meta])* 372 | $field_vis:vis 373 | $field_name:ident 374 | $(|| $(#[$serde_field_orig_meta:meta])* $serde_ty:ty : $field_orig_ty:ty)? 375 | $(: $field_ty:ty)? 376 | $(| $getter_vis:vis get $field_user_ty:ty $(: $setter_vis:vis set $field_setter_user_ty:ty)?)? 377 | ),* $(,)? 378 | } 379 | ) => { 380 | $(#[$struct_meta])* 381 | $struct_vis 382 | struct $StructName { 383 | $( 384 | $(#[$field_meta])* 385 | $field_vis 386 | $($field_name : $field_ty,)? 387 | $($field_name : $field_orig_ty,)? 388 | )* 389 | } 390 | 391 | impl $StructName { 392 | pub fn builder() -> Self { 393 | Self::new() // NOT default 394 | } 395 | pub fn build(&self) -> Self { 396 | self.clone() 397 | } 398 | } 399 | 400 | #[cfg(feature = "serde")] 401 | impl $StructName { 402 | $( 403 | paste!{ 404 | $( 405 | pub(crate) fn [] (self : &'_ Self) 406 | -> Result<$field_ty> { 407 | Ok(self.$field_name()) 408 | } 409 | )? 410 | $( 411 | pub(crate) fn [] (self : &'_ Self) 412 | -> Result<$serde_ty> { 413 | Ok(self.$field_name().into()) 414 | } 415 | )? 416 | $( 417 | pub(crate) fn [](self : &mut Self, value: $field_ty) -> &mut Self { 418 | self.[](value.into()); 419 | self 420 | } 421 | )? 422 | $( 423 | pub(crate) fn [](self : &mut Self, value: $serde_ty) -> &mut Self { 424 | self.[](value.into()); 425 | self 426 | } 427 | )? 428 | } 429 | )* 430 | } 431 | 432 | #[cfg(feature = "serde")] 433 | paste::paste! { 434 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 435 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 436 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 437 | #[cfg_attr(feature = "serde", serde(rename = "" $StructName))] 438 | pub(crate) struct [] { 439 | $( 440 | $(pub $field_name : <$field_ty as Specifier>::InOut,)? 441 | $($(#[$serde_field_orig_meta])* pub $field_name : $serde_ty,)? 442 | )* 443 | } 444 | } 445 | }} 446 | 447 | pub(crate) use make_bitfield_serde; 448 | -------------------------------------------------------------------------------- /src/struct_variants_enum.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #![macro_use] 6 | 7 | /// This macro expects module contents as a parameter, and then, first, defines 8 | /// the exact same contents. Then it generates two enums with all the items 9 | /// that implement EntryCompatible available in that module. It then implements 10 | /// SequenceElementFromBytes for the enum. 11 | macro_rules! collect_EntryCompatible_impl_into_enum { 12 | // This provides the deserializer's type matching. 13 | (@match2 {$type_:ident}{$skip_step:ident}{$xbuf:ident}) => { 14 | { 15 | let (raw_value, b) = $xbuf.split_at($skip_step); 16 | $xbuf = b; 17 | (Self::Unknown(raw_value), $xbuf) 18 | } 19 | }; 20 | (@match2 {$type_:ident}{$skip_step:ident}{$xbuf:ident} $struct_name:ident; $($tail:tt)*) => { 21 | if $skip_step == core::mem::size_of::<$struct_name>() && $type_ == <$struct_name>::TAG { 22 | (Self::$struct_name(take_header_from_collection::<$struct_name>(&mut $xbuf).ok_or_else(|| Error::EntryTypeMismatch)?), $xbuf) 23 | } else { 24 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match2 {$type_}{$skip_step}{$xbuf}$($tail)*) 25 | } 26 | }; 27 | (@match1 {$entry_id:ident}{$world:ident}{$($deserializer:tt)*}) => { 28 | { 29 | let (type_, skip_step) = Self::skip_step($entry_id, $world).ok_or_else(|| Error::EntryTypeMismatch)?; 30 | let mut xbuf = core::mem::take(&mut *$world); 31 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match2 {type_}{skip_step}{xbuf}$($deserializer)*) 32 | } 33 | }; 34 | (@match2mut {$type_:ident}{$skip_step:ident}{$xbuf:ident}) => { 35 | { 36 | let (raw_value, b) = $xbuf.split_at_mut($skip_step); 37 | $xbuf = b; 38 | (Self::Unknown(raw_value), $xbuf) 39 | } 40 | }; 41 | (@match2mut {$type_:ident}{$skip_step:ident}{$xbuf:ident} $struct_name:ident; $($tail:tt)*) => { 42 | if $skip_step == core::mem::size_of::<$struct_name>() && $type_ == <$struct_name>::TAG { 43 | (Self::$struct_name(take_header_from_collection_mut::<$struct_name>(&mut $xbuf).ok_or_else(|| Error::EntryTypeMismatch)?), $xbuf) 44 | } else { 45 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match2mut {$type_}{$skip_step}{$xbuf}$($tail)*) 46 | } 47 | }; 48 | (@match1mut {$entry_id:ident}{$world:ident}{$($deserializer:tt)*}) => { 49 | { 50 | let (type_, skip_step) = Self::skip_step($entry_id, $world).ok_or_else(|| Error::EntryTypeMismatch)?; 51 | let mut xbuf = core::mem::take(&mut *$world); 52 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match2mut {type_}{skip_step}{xbuf}$($deserializer)*) 53 | } 54 | }; 55 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 56 | ) => { 57 | #[non_exhaustive] 58 | #[derive(Debug)] 59 | #[cfg_attr(feature = "serde", derive(Serialize))] 60 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 61 | pub enum ElementRef<'a> { 62 | Unknown(&'a [u8]), 63 | $($state)* 64 | } 65 | #[non_exhaustive] 66 | #[derive(Debug)] 67 | pub enum MutElementRef<'a> { 68 | Unknown(&'a mut [u8]), 69 | $($state_mut)* 70 | } 71 | 72 | #[cfg(feature = "std")] 73 | #[non_exhaustive] 74 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 75 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 76 | #[repr(C)] 77 | pub enum Element { 78 | Unknown(Vec), 79 | $($state_obj)* 80 | } 81 | 82 | impl<'a> SequenceElementFromBytes<'a> for ElementRef<'a> { 83 | fn checked_from_bytes(entry_id: EntryId, world: &mut &'a [u8]) -> Result { 84 | let (result, xbuf) = $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match1 {entry_id}{world}{$($deserializer)*}); 85 | (*world) = xbuf; 86 | Ok(result) 87 | } 88 | } 89 | impl<'a> MutSequenceElementFromBytes<'a> for MutElementRef<'a> { 90 | fn checked_from_bytes(entry_id: EntryId, world: &mut &'a mut [u8]) -> Result { 91 | let (result, xbuf) = $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@match1mut {entry_id}{world}{$($deserializer)*}); 92 | (*world) = xbuf; 93 | Ok(result) 94 | } 95 | } 96 | 97 | #[cfg(feature = "std")] 98 | impl ElementAsBytes for Element { 99 | fn element_as_bytes(&self) -> &[u8] { 100 | match self { 101 | Element::Unknown(vec) => vec.as_slice(), 102 | $($as_bytes)* 103 | } 104 | } 105 | } 106 | }; 107 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 108 | $(#[$struct_meta:meta])* 109 | impl EntryCompatible for $struct_name:ident { 110 | $($impl_body:tt)* 111 | } 112 | $($tail:tt)* 113 | ) => { 114 | impl<'a> From<&'a $struct_name> for ElementRef<'a> { 115 | fn from(from: &'a $struct_name) -> Self { 116 | Self::$struct_name(from) 117 | } 118 | } 119 | impl<'a> From<&'a mut $struct_name> for MutElementRef<'a> { 120 | fn from(from: &'a mut $struct_name) -> Self { 121 | Self::$struct_name(from) 122 | } 123 | } 124 | $(#[$struct_meta])* 125 | impl EntryCompatible for $struct_name { 126 | $($impl_body)* 127 | } 128 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {$struct_name; $($deserializer)*}{$struct_name(&'a $struct_name), $($state)*}{$struct_name(&'a mut $struct_name), $($state_mut)*}{$struct_name($struct_name), $($state_obj)*}{Element::$struct_name($struct_name) => $struct_name.as_bytes(), $($as_bytes)*} 129 | $($tail)*); 130 | }; 131 | // Who could possibly want non-eager evaluation here? Sigh. 132 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 133 | impl_EntryCompatible!($struct_name:ident, $($args:tt)*); 134 | $($tail:tt)* 135 | ) => { 136 | impl<'a> From<&'a $struct_name> for ElementRef<'a> { 137 | fn from(from: &'a $struct_name) -> Self { 138 | Self::$struct_name(from) 139 | } 140 | } 141 | impl<'a> From<&'a mut $struct_name> for MutElementRef<'a> { 142 | fn from(from: &'a mut $struct_name) -> Self { 143 | Self::$struct_name(from) 144 | } 145 | } 146 | impl_EntryCompatible!($struct_name, $($args)*); 147 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {$struct_name; $($deserializer)*}{$struct_name(&'a $struct_name), $($state)*}{$struct_name(&'a mut $struct_name), $($state_mut)*}{$struct_name($struct_name), $($state_obj)*}{Element::$struct_name($struct_name) => $struct_name.as_bytes(), $($as_bytes)*} 148 | $($tail)*); 149 | }; 150 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 151 | $(#[$struct_meta:meta])* 152 | $struct_vis:vis 153 | struct $struct_name:ident { 154 | $($struct_body:tt)* 155 | } 156 | $($tail:tt)* 157 | ) => { 158 | $(#[$struct_meta])* 159 | $struct_vis 160 | struct $struct_name { $($struct_body)* } 161 | 162 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {$($deserializer)*}{$($state)*}{$($state_mut)*}{$($state_obj)*}{$($as_bytes)*} 163 | $($tail)*); 164 | }; 165 | // Who could possibly want non-eager evaluation here? Sigh. 166 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 167 | make_bitfield_serde! { 168 | $(#[$struct_meta:meta])* 169 | $struct_vis:vis 170 | struct $struct_name:ident { 171 | $($struct_body:tt)* 172 | } 173 | } 174 | $($tail:tt)* 175 | ) => { 176 | make_bitfield_serde! { 177 | $(#[$struct_meta])* 178 | $struct_vis 179 | struct $struct_name { $($struct_body)* } 180 | } 181 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {$($deserializer)*}{$($state)*}{$($state_mut)*}{$($state_obj)*}{$($as_bytes)*} 182 | $($tail)*); 183 | }; 184 | (@machine {$($deserializer:tt)*}{$($state:tt)*}{$($state_mut:tt)*}{$($state_obj:tt)*}{$($as_bytes:tt)*} 185 | $head:item 186 | $($tail:tt)* 187 | ) => { 188 | $head 189 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {$($deserializer)*}{$($state)*}{$($state_mut)*}{$($state_obj)*}{$($as_bytes)*} 190 | $($tail)*); 191 | }; 192 | ($($tts:tt)*) => { 193 | $crate::struct_variants_enum::collect_EntryCompatible_impl_into_enum!(@machine {}{}{}{}{} $($tts)*); 194 | }; 195 | } 196 | 197 | pub(crate) use collect_EntryCompatible_impl_into_enum; 198 | -------------------------------------------------------------------------------- /src/token_accessors.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #![macro_use] 6 | 7 | use crate::apcb::Apcb; 8 | use crate::entry::EntryItemBody; 9 | use crate::ondisk::BoardInstances; 10 | use crate::ondisk::GroupId; 11 | use crate::ondisk::PriorityLevels; 12 | use crate::ondisk::{ 13 | BoolToken, ByteToken, ContextType, DwordToken, EntryId, TokenEntryId, 14 | WordToken, 15 | }; 16 | use crate::types::Error; 17 | use crate::types::Result; 18 | 19 | pub struct TokensMut<'a, 'b> { 20 | pub(crate) apcb: &'b mut Apcb<'a>, 21 | pub(crate) instance_id: u16, 22 | pub(crate) board_instance_mask: BoardInstances, 23 | pub(crate) priority_mask: PriorityLevels, 24 | pub(crate) abl0_version: Option, 25 | } 26 | pub struct Tokens<'a, 'b> { 27 | pub(crate) apcb: &'b Apcb<'a>, 28 | pub(crate) instance_id: u16, 29 | pub(crate) board_instance_mask: BoardInstances, 30 | //pub(crate) priority_mask: PriorityLevels, 31 | } 32 | 33 | impl<'a, 'b> TokensMut<'a, 'b> { 34 | pub(crate) fn new( 35 | apcb: &'b mut Apcb<'a>, 36 | instance_id: u16, 37 | board_instance_mask: BoardInstances, 38 | priority_mask: PriorityLevels, 39 | abl0_version: Option, 40 | ) -> Result { 41 | match apcb.insert_group(GroupId::Token, *b"TOKN") { 42 | Err(Error::GroupUniqueKeyViolation { .. }) => {} 43 | Err(x) => { 44 | return Err(x); 45 | } 46 | _ => {} 47 | }; 48 | Ok(Self { 49 | apcb, 50 | instance_id, 51 | board_instance_mask, 52 | priority_mask, 53 | abl0_version, 54 | }) 55 | } 56 | 57 | pub(crate) fn get( 58 | &self, 59 | token_entry_id: TokenEntryId, 60 | field_key: u32, 61 | ) -> Result { 62 | let group = self 63 | .apcb 64 | .group(GroupId::Token)? 65 | .ok_or(Error::GroupNotFound { group_id: GroupId::Token })?; 66 | let entry = group 67 | .entry_exact( 68 | EntryId::Token(token_entry_id), 69 | self.instance_id, 70 | self.board_instance_mask, 71 | ) 72 | .ok_or(Error::EntryNotFound { 73 | entry_id: EntryId::Token(token_entry_id), 74 | instance_id: self.instance_id, 75 | board_instance_mask: self.board_instance_mask, 76 | })?; 77 | match &entry.body { 78 | EntryItemBody::<_>::Tokens(a) => { 79 | let token = a.token(field_key).ok_or(Error::TokenNotFound { 80 | token_id: field_key, 81 | //entry_id: token_entry_id, 82 | //instance_id: self.instance_id, 83 | //board_instance_mask: self.board_instance_mask, 84 | })?; 85 | assert!(token.id() == field_key); 86 | let token_value = token.value(); 87 | Ok(token_value) 88 | } 89 | _ => Err(Error::EntryTypeMismatch), 90 | } 91 | } 92 | 93 | pub(crate) fn set( 94 | &mut self, 95 | token_entry_id: TokenEntryId, 96 | field_key: u32, 97 | token_value: u32, 98 | ) -> Result<()> { 99 | let entry_id = EntryId::Token(token_entry_id); 100 | let token_id = field_key; 101 | //let token_value = value.to_u32().ok_or_else(|| 102 | // Error::EntryTypeMismatch)?; 103 | match self.apcb.insert_token( 104 | entry_id, 105 | self.instance_id, 106 | self.board_instance_mask, 107 | token_id, 108 | token_value, 109 | ) { 110 | Err(Error::EntryNotFound { .. }) => { 111 | match self.apcb.insert_entry( 112 | entry_id, 113 | self.instance_id, 114 | self.board_instance_mask, 115 | ContextType::Tokens, 116 | self.priority_mask, 117 | &[], 118 | ) { 119 | Err(Error::EntryUniqueKeyViolation { .. }) => {} 120 | Err(x) => { 121 | return Err(x); 122 | } 123 | _ => {} 124 | }; 125 | if let Some(abl0_version) = self.abl0_version { 126 | let valid = match token_entry_id { 127 | TokenEntryId::Bool => BoolToken::valid_for_abl0_raw( 128 | abl0_version, 129 | token_id, 130 | ), 131 | TokenEntryId::Byte => ByteToken::valid_for_abl0_raw( 132 | abl0_version, 133 | token_id, 134 | ), 135 | TokenEntryId::Word => WordToken::valid_for_abl0_raw( 136 | abl0_version, 137 | token_id, 138 | ), 139 | TokenEntryId::Dword => DwordToken::valid_for_abl0_raw( 140 | abl0_version, 141 | token_id, 142 | ), 143 | TokenEntryId::Unknown(_) => false, 144 | }; 145 | if !valid { 146 | return Err(Error::TokenVersionMismatch { 147 | entry_id: token_entry_id, 148 | token_id, 149 | abl0_version, 150 | }); 151 | } 152 | } 153 | self.apcb.insert_token( 154 | entry_id, 155 | self.instance_id, 156 | self.board_instance_mask, 157 | token_id, 158 | token_value, 159 | )?; 160 | } 161 | Err(Error::TokenUniqueKeyViolation { .. }) => { 162 | let mut group = self.apcb.group_mut(GroupId::Token)?.unwrap(); 163 | let mut entry = group 164 | .entry_exact_mut( 165 | entry_id, 166 | self.instance_id, 167 | self.board_instance_mask, 168 | ) 169 | .unwrap(); 170 | match &mut entry.body { 171 | EntryItemBody::<_>::Tokens(a) => { 172 | let mut token = a.token_mut(token_id).unwrap(); 173 | token.set_value(token_value)?; 174 | } 175 | _ => { 176 | return Err(Error::EntryTypeMismatch); 177 | } 178 | } 179 | } 180 | Err(x) => { 181 | return Err(x); 182 | } 183 | _ => { // inserted new token, and set its value 184 | } 185 | } 186 | Ok(()) 187 | } 188 | } 189 | 190 | impl<'a, 'b> Tokens<'a, 'b> { 191 | pub(crate) fn new( 192 | apcb: &'b Apcb<'a>, 193 | instance_id: u16, 194 | board_instance_mask: BoardInstances, 195 | ) -> Result { 196 | Ok(Self { apcb, instance_id, board_instance_mask }) 197 | } 198 | 199 | pub fn get( 200 | &self, 201 | token_entry_id: TokenEntryId, 202 | field_key: u32, 203 | ) -> Result { 204 | let group = self 205 | .apcb 206 | .group(GroupId::Token)? 207 | .ok_or(Error::GroupNotFound { group_id: GroupId::Token })?; 208 | let entry = group 209 | .entry_exact( 210 | EntryId::Token(token_entry_id), 211 | self.instance_id, 212 | self.board_instance_mask, 213 | ) 214 | .ok_or(Error::EntryNotFound { 215 | entry_id: EntryId::Token(token_entry_id), 216 | instance_id: self.instance_id, 217 | board_instance_mask: self.board_instance_mask, 218 | })?; 219 | match &entry.body { 220 | EntryItemBody::<_>::Tokens(a) => { 221 | let token = a.token(field_key).ok_or(Error::TokenNotFound { 222 | token_id: field_key, 223 | //instance_id: self.instance_id, 224 | //board_instance_mask: self.board_instance_mask, 225 | })?; 226 | assert!(token.id() == field_key); 227 | let token_value = token.value(); 228 | Ok(token_value) 229 | } 230 | _ => Err(Error::EntryTypeMismatch), 231 | } 232 | } 233 | } 234 | 235 | /// Automatically impl getters (and setters) for the fields where there was 236 | /// "get" (and "set") specified. The getters and setters so generated are 237 | /// hardcoded as calling from_u32() and to_u32(), respectively. Variant syntax: 238 | /// [ATTRIBUTES] 239 | /// NAME(TYPE, default DEFAULT_VALUE, id TOKEN_ID) = KEY: pub get TYPE [: pub 240 | /// set TYPE] 241 | /// The ATTRIBUTES (`#`...) make it into the resulting enum variant. 242 | /// We ensure that MINIMAL_VERSION <= abl0_version < FRONTIER_VERSION at 243 | /// runtime. 244 | macro_rules! make_token_accessors {( 245 | $(#[$enum_meta:meta])* 246 | $enum_vis:vis enum $enum_name:ident: {$field_entry_id:expr} { 247 | $( 248 | $(#[$field_meta:meta])* 249 | $field_vis:vis 250 | $field_name:ident( 251 | $(minimal_version $minimal_version:expr, 252 | frontier_version $frontier_version:expr,)? 253 | default $field_default_value:expr, id $field_key:expr) 254 | | $getter_vis:vis 255 | get $field_user_ty:ty 256 | $(: $setter_vis:vis set $field_setter_user_ty:ty)? 257 | ),* $(,)? 258 | } 259 | ) => ( 260 | $(#[$enum_meta])* 261 | #[derive(Debug)] // TODO: EnumString 262 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 263 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 264 | $enum_vis enum $enum_name { 265 | $( 266 | $(#[$field_meta])* 267 | $field_name($field_user_ty), // = $field_key 268 | )* 269 | } 270 | impl core::convert::TryFrom<&TOKEN_ENTRY> for $enum_name { 271 | type Error = Error; 272 | fn try_from(entry: &TOKEN_ENTRY) -> core::result::Result { 273 | let tag = entry.key.get(); 274 | let value = entry.value.get(); 275 | $( 276 | if ($field_key == tag) { 277 | let value = <$field_user_ty>::from_u32(value).ok_or(Error::TokenRange { token_id: tag })?; 278 | Ok(Self::$field_name(value)) 279 | } else 280 | )*{ 281 | Err(Error::TokenNotFound { 282 | token_id: tag, 283 | //entry_id: TokenEntryId::from(entry), 284 | //instance_id 285 | //board_instance_mask 286 | }) 287 | } 288 | } 289 | } 290 | impl core::convert::TryFrom<&$enum_name> for TOKEN_ENTRY { 291 | type Error = Error; 292 | fn try_from(x: &$enum_name) -> core::result::Result { 293 | $( 294 | if let $enum_name::$field_name(value) = x { 295 | let key: u32 = $field_key; 296 | Ok(Self { 297 | key: key.into(), 298 | value: value.to_u32().ok_or(Error::TokenRange { token_id: key })?.into(), 299 | }) 300 | } else 301 | )*{ 302 | panic!("having a token enum variant for something we don't have a key for should be impossible--since both are generated by the same macro. But it just happened.") 303 | } 304 | } 305 | } 306 | $( 307 | impl<'a, 'b> Tokens<'a, 'b> { 308 | paste! { 309 | #[allow(non_snake_case)] 310 | #[inline] 311 | $getter_vis 312 | fn [<$field_name:snake>] (self: &'_ Self) 313 | -> Result<$field_user_ty> 314 | { 315 | <$field_user_ty>::from_u32(self.get($field_entry_id, $field_key)?).ok_or_else(|| Error::EntryTypeMismatch) 316 | } 317 | } 318 | } 319 | impl<'a, 'b> TokensMut<'a, 'b> { 320 | 321 | paste! { 322 | #[allow(non_snake_case)] 323 | #[inline] 324 | $getter_vis 325 | fn [<$field_name:snake>] (self: &'_ Self) 326 | -> Result<$field_user_ty> 327 | { 328 | <$field_user_ty>::from_u32(self.get($field_entry_id, $field_key)?).ok_or_else(|| Error::EntryTypeMismatch) 329 | } 330 | } 331 | $( 332 | paste! { 333 | #[allow(non_snake_case)] 334 | #[inline] 335 | $setter_vis 336 | fn [] (self: &'_ mut Self, value: $field_setter_user_ty) -> Result<()> { 337 | let token_value = value.to_u32().unwrap(); 338 | self.set($field_entry_id, $field_key, token_value) 339 | } 340 | } 341 | )? 342 | } 343 | )* 344 | impl $enum_name { 345 | #[allow(unused_variables)] 346 | pub(crate) fn valid_for_abl0_raw(abl0_version: u32, field_key: u32) -> bool { 347 | $( 348 | if (field_key == $field_key) { 349 | $( 350 | #[allow(unused_comparisons)] 351 | return ($minimal_version..$frontier_version).contains(&abl0_version); 352 | )? 353 | #[allow(unreachable_code)] 354 | return true 355 | } else 356 | )* 357 | { 358 | // We default to VALID for things that have no declaration at all. 359 | // 360 | // This is in order to simplify both bringup of a new generation 361 | // and also to allow the user to temporarily add debug token that 362 | // we don't statically know. Downside is that you can add nonsense 363 | // tokens and we will not complain. 364 | true 365 | } 366 | } 367 | pub fn valid_for_abl0(&self, abl0_version: u32) -> core::result::Result { 368 | let token_entry = TOKEN_ENTRY::try_from(self)?; 369 | Ok(Self::valid_for_abl0_raw(abl0_version, token_entry.key.get())) 370 | } 371 | } 372 | )} 373 | 374 | pub(crate) use make_token_accessors; 375 | -------------------------------------------------------------------------------- /src/tokens_entry.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use crate::ondisk::{ 6 | BoolToken, ByteToken, ContextFormat, DwordToken, ENTRY_HEADER, TOKEN_ENTRY, 7 | TokenEntryId, WordToken, take_header_from_collection, 8 | take_header_from_collection_mut, 9 | }; 10 | use crate::types::{ApcbContext, Error, FileSystemError, Result}; 11 | use core::convert::TryFrom; 12 | use core::mem::size_of; 13 | use num_traits::FromPrimitive; 14 | use zerocopy::ByteSlice; 15 | 16 | #[cfg(feature = "serde")] 17 | use serde::ser::{Serialize, Serializer}; 18 | 19 | #[derive(Debug, Clone, Copy)] 20 | #[allow(dead_code)] // unit_size is not read when building without serde 21 | pub struct TokensEntryBodyItem { 22 | unit_size: u8, 23 | key_pos: u8, 24 | key_size: u8, 25 | context_format: u8, 26 | entry_id: u16, 27 | buf: BufferType, 28 | used_size: usize, 29 | context: ApcbContext, 30 | } 31 | 32 | // Note: Only construct those if unit_size, key_pos and key_size are correct! 33 | pub struct TokensEntryIter { 34 | context_format: u8, 35 | entry_id: TokenEntryId, 36 | buf: BufferType, 37 | remaining_used_size: usize, 38 | } 39 | 40 | impl TokensEntryBodyItem { 41 | pub(crate) fn new( 42 | header: &ENTRY_HEADER, 43 | buf: BufferType, 44 | used_size: usize, 45 | context: ApcbContext, 46 | ) -> Result { 47 | Ok(Self { 48 | unit_size: header.unit_size, 49 | key_pos: header.key_pos, 50 | key_size: header.key_size, 51 | context_format: header.context_format, 52 | entry_id: header.entry_id.get(), 53 | buf, 54 | used_size, 55 | context, 56 | }) 57 | } 58 | pub(crate) fn prepare_iter(&self) -> Result { 59 | if self.unit_size != 8 { 60 | return Err(Error::FileSystem( 61 | FileSystemError::InconsistentHeader, 62 | "ENTRY_HEADER::unit_size", 63 | )); 64 | } 65 | if self.key_pos != 0 { 66 | return Err(Error::FileSystem( 67 | FileSystemError::InconsistentHeader, 68 | "ENTRY_HEADER::key_pos", 69 | )); 70 | } 71 | if self.key_size != 4 { 72 | return Err(Error::FileSystem( 73 | FileSystemError::InconsistentHeader, 74 | "ENTRY_HEADER::key_size", 75 | )); 76 | } 77 | let entry_id = 78 | TokenEntryId::from_u16(self.entry_id).ok_or(Error::FileSystem( 79 | FileSystemError::InconsistentHeader, 80 | "ENTRY_HEADER::entry_id", 81 | ))?; 82 | Ok(entry_id) 83 | } 84 | } 85 | 86 | pub struct TokensEntryItem { 87 | pub(crate) entry_id: TokenEntryId, 88 | pub(crate) token: TokenType, 89 | } 90 | 91 | impl TokensEntryItem<&mut TOKEN_ENTRY> { 92 | pub fn id(&self) -> u32 { 93 | self.token.key.get() 94 | } 95 | pub fn value(&self) -> u32 { 96 | self.token.value.get() 97 | & match self.entry_id { 98 | TokenEntryId::Bool => 0x1, 99 | TokenEntryId::Byte => 0xFF, 100 | TokenEntryId::Word => 0xFFFF, 101 | TokenEntryId::Dword => 0xFFFF_FFFF, 102 | TokenEntryId::Unknown(_) => 0xFFFF_FFFF, 103 | } 104 | } 105 | 106 | // Since the id is a sort key, it cannot be mutated. 107 | 108 | pub fn set_value(&mut self, value: u32) -> Result<()> { 109 | if value 110 | == (value 111 | & match self.entry_id { 112 | TokenEntryId::Bool => 0x1, 113 | TokenEntryId::Byte => 0xFF, 114 | TokenEntryId::Word => 0xFFFF, 115 | TokenEntryId::Dword => 0xFFFF_FFFF, 116 | TokenEntryId::Unknown(_) => 0xFFFF_FFFF, 117 | }) 118 | { 119 | self.token.value.set(value); 120 | Ok(()) 121 | } else { 122 | Err(Error::TokenRange { token_id: self.id() }) 123 | } 124 | } 125 | } 126 | 127 | impl<'a> TokensEntryIter<&'a mut [u8]> { 128 | /// It's useful to have some way of NOT mutating self.buf. This is what 129 | /// this function does. Note: The caller needs to manually decrease 130 | /// remaining_used_size for each call if desired. 131 | fn next_item<'b>( 132 | entry_id: TokenEntryId, 133 | buf: &mut &'b mut [u8], 134 | ) -> Result> { 135 | if buf.is_empty() { 136 | return Err(Error::FileSystem( 137 | FileSystemError::InconsistentHeader, 138 | "TOKEN_ENTRY", 139 | )); 140 | } 141 | let token = 142 | match take_header_from_collection_mut::(&mut *buf) { 143 | Some(item) => item, 144 | None => { 145 | return Err(Error::FileSystem( 146 | FileSystemError::InconsistentHeader, 147 | "TOKEN_ENTRY", 148 | )); 149 | } 150 | }; 151 | Ok(TokensEntryItem { entry_id, token }) 152 | } 153 | 154 | /// Find the place BEFORE which the entry TOKEN_ID is supposed to go. 155 | pub(crate) fn move_insertion_point_before( 156 | &mut self, 157 | token_id: u32, 158 | ) -> Result<()> { 159 | loop { 160 | let mut buf = &mut self.buf[..self.remaining_used_size]; 161 | if buf.is_empty() { 162 | break; 163 | } 164 | match Self::next_item(self.entry_id, &mut buf) { 165 | Ok(e) => { 166 | if e.id() < token_id { 167 | self.next().unwrap(); 168 | } else { 169 | break; 170 | } 171 | } 172 | Err(e) => { 173 | return Err(e); 174 | } 175 | } 176 | } 177 | Ok(()) 178 | } 179 | /// Find the place BEFORE which the entry TOKEN_ID is supposed to go. 180 | pub(crate) fn move_point_to(&mut self, token_id: u32) -> Result<()> { 181 | loop { 182 | let mut buf = &mut self.buf[..self.remaining_used_size]; 183 | if buf.is_empty() { 184 | return Err(Error::TokenNotFound { 185 | token_id, 186 | //entry_id: self.entry_id, 187 | //board_instance_mask 188 | }); 189 | } 190 | match Self::next_item(self.entry_id, &mut buf) { 191 | Ok(e) => { 192 | if e.id() != token_id { 193 | self.next().unwrap(); 194 | } else { 195 | return Ok(()); 196 | } 197 | } 198 | Err(e) => { 199 | return Err(e); 200 | } 201 | } 202 | } 203 | } 204 | 205 | /// Inserts the given entry data at the right spot. 206 | /// Precondition: Caller must have already increased the group size 207 | /// by `size_of::()`. 208 | pub(crate) fn insert_token( 209 | &mut self, 210 | token_id: u32, 211 | token_value: u32, 212 | ) -> Result> { 213 | let token_size = size_of::(); 214 | 215 | // Make sure that move_insertion_point_before does not notice the new 216 | // uninitialized token 217 | self.remaining_used_size = self 218 | .remaining_used_size 219 | .checked_sub(token_size) 220 | .ok_or(Error::FileSystem( 221 | FileSystemError::InconsistentHeader, 222 | "TOKEN_ENTRY", 223 | ))?; 224 | self.move_insertion_point_before(token_id)?; 225 | // Move the entries from after the insertion point to the right (in 226 | // order to make room before for our new entry). 227 | self.buf.copy_within(0..self.remaining_used_size, token_size); 228 | 229 | self.remaining_used_size = self 230 | .remaining_used_size 231 | .checked_add(token_size) 232 | .ok_or(Error::FileSystem( 233 | FileSystemError::InconsistentHeader, 234 | "TOKEN_ENTRY", 235 | ))?; 236 | let token = 237 | match take_header_from_collection_mut::(&mut self.buf) 238 | { 239 | Some(item) => item, 240 | None => { 241 | return Err(Error::FileSystem( 242 | FileSystemError::InconsistentHeader, 243 | "TOKEN_ENTRY", 244 | )); 245 | } 246 | }; 247 | 248 | token.key.set(token_id); 249 | token.value.set(0); // always valid 250 | let mut result = TokensEntryItem { entry_id: self.entry_id, token }; 251 | 252 | // This has better error checking 253 | result.set_value(token_value)?; 254 | Ok(result) 255 | } 256 | 257 | /// Delete the token TOKEN_ID from the entry. 258 | /// Postcondition: Caller resizes the containers afterwards. 259 | pub(crate) fn delete_token(&mut self, token_id: u32) -> Result<()> { 260 | self.move_point_to(token_id)?; 261 | let token_size = size_of::(); 262 | // Move the tokens behind this one to the left 263 | self.buf.copy_within(token_size..self.remaining_used_size, 0); 264 | self.remaining_used_size = self 265 | .remaining_used_size 266 | .checked_sub(token_size) 267 | .ok_or(Error::FileSystem( 268 | FileSystemError::InconsistentHeader, 269 | "TOKEN_ENTRY", 270 | ))?; 271 | Ok(()) 272 | } 273 | } 274 | 275 | impl<'a> Iterator for TokensEntryIter<&'a mut [u8]> { 276 | type Item = TokensEntryItem<&'a mut TOKEN_ENTRY>; 277 | 278 | fn next(&mut self) -> Option { 279 | if self.remaining_used_size == 0 { 280 | return None; 281 | } 282 | match Self::next_item(self.entry_id, &mut self.buf) { 283 | Ok(e) => { 284 | assert!(self.remaining_used_size >= 8); 285 | self.remaining_used_size -= 8; 286 | Some(e) 287 | } 288 | Err(_) => None, 289 | } 290 | } 291 | } 292 | 293 | #[cfg(feature = "serde-hex")] 294 | use serde_hex::{SerHex, StrictPfx}; 295 | 296 | #[cfg(feature = "serde")] 297 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 298 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 299 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 300 | pub(crate) enum SerdeTokensEntryItem { 301 | Bool(BoolToken), 302 | Byte(ByteToken), 303 | Word(WordToken), 304 | Dword(DwordToken), 305 | Unknown { 306 | entry_id: TokenEntryId, 307 | #[cfg_attr( 308 | feature = "serde-hex", 309 | serde( 310 | serialize_with = "SerHex::::serialize", 311 | deserialize_with = "SerHex::::deserialize" 312 | ) 313 | )] 314 | tag: u32, 315 | #[cfg_attr( 316 | feature = "serde-hex", 317 | serde( 318 | serialize_with = "SerHex::::serialize", 319 | deserialize_with = "SerHex::::deserialize" 320 | ) 321 | )] 322 | value: u32, 323 | }, 324 | } 325 | 326 | #[cfg(feature = "serde")] 327 | impl SerdeTokensEntryItem { 328 | pub(crate) fn entry_id(&self) -> TokenEntryId { 329 | match self { 330 | Self::Bool(_) => TokenEntryId::Bool, 331 | Self::Byte(_) => TokenEntryId::Byte, 332 | Self::Word(_) => TokenEntryId::Word, 333 | Self::Dword(_) => TokenEntryId::Dword, 334 | Self::Unknown { entry_id, tag: _, value: _ } => *entry_id, 335 | } 336 | } 337 | } 338 | 339 | #[cfg(feature = "serde")] 340 | impl From<&TokensEntryItem<&'_ TOKEN_ENTRY>> for SerdeTokensEntryItem { 341 | fn from(item: &TokensEntryItem<&'_ TOKEN_ENTRY>) -> Self { 342 | let entry = item.token; 343 | let key = entry.key.get(); 344 | let mut st = Self::Unknown { 345 | entry_id: item.entry_id, 346 | tag: key, 347 | value: item.value(), 348 | }; 349 | match item.entry_id { 350 | TokenEntryId::Bool => { 351 | if let Ok(token) = BoolToken::try_from(entry) { 352 | st = Self::Bool(token); 353 | } 354 | } 355 | TokenEntryId::Byte => { 356 | if let Ok(token) = ByteToken::try_from(entry) { 357 | st = Self::Byte(token); 358 | } 359 | } 360 | TokenEntryId::Word => { 361 | if let Ok(token) = WordToken::try_from(entry) { 362 | st = Self::Word(token); 363 | } 364 | } 365 | TokenEntryId::Dword => { 366 | if let Ok(token) = DwordToken::try_from(entry) { 367 | st = Self::Dword(token); 368 | } 369 | } 370 | TokenEntryId::Unknown(_) => {} 371 | } 372 | st 373 | } 374 | } 375 | 376 | #[cfg(feature = "serde")] 377 | impl core::convert::TryFrom for TOKEN_ENTRY { 378 | type Error = Error; 379 | fn try_from( 380 | st: SerdeTokensEntryItem, 381 | ) -> core::result::Result { 382 | match st { 383 | SerdeTokensEntryItem::Bool(t) => TOKEN_ENTRY::try_from(&t), 384 | SerdeTokensEntryItem::Byte(t) => TOKEN_ENTRY::try_from(&t), 385 | SerdeTokensEntryItem::Word(t) => TOKEN_ENTRY::try_from(&t), 386 | SerdeTokensEntryItem::Dword(t) => TOKEN_ENTRY::try_from(&t), 387 | SerdeTokensEntryItem::Unknown { entry_id: _, tag, value } => { 388 | Ok(Self { key: tag.into(), value: value.into() }) 389 | } 390 | } 391 | } 392 | } 393 | 394 | #[cfg(feature = "schemars")] 395 | impl schemars::JsonSchema for TokensEntryItem<&TOKEN_ENTRY> { 396 | fn schema_name() -> std::string::String { 397 | SerdeTokensEntryItem::schema_name() 398 | } 399 | fn json_schema( 400 | generator: &mut schemars::r#gen::SchemaGenerator, 401 | ) -> schemars::schema::Schema { 402 | SerdeTokensEntryItem::json_schema(generator) 403 | } 404 | fn is_referenceable() -> bool { 405 | SerdeTokensEntryItem::is_referenceable() 406 | } 407 | } 408 | 409 | #[cfg(feature = "serde")] 410 | impl Serialize for TokensEntryItem<&TOKEN_ENTRY> { 411 | fn serialize( 412 | &self, 413 | serializer: S, 414 | ) -> core::result::Result 415 | where 416 | S: Serializer, 417 | { 418 | SerdeTokensEntryItem::from(self).serialize(serializer) 419 | } 420 | } 421 | 422 | impl core::fmt::Debug for TokensEntryItem<&TOKEN_ENTRY> { 423 | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 424 | let entry = self.token; 425 | let key = entry.key.get(); 426 | let mut ds = fmt.debug_struct("TokensEntryItem_TOKEN_ENTRY"); 427 | ds.field("entry_id", &self.entry_id); 428 | let value = entry.value.get(); 429 | match self.entry_id { 430 | TokenEntryId::Bool => { 431 | if let Ok(token) = BoolToken::try_from(entry) { 432 | ds.field("token", &token) 433 | } else { 434 | ds.field("key", &key) 435 | } 436 | } 437 | TokenEntryId::Byte => { 438 | if let Ok(token) = ByteToken::try_from(entry) { 439 | ds.field("token", &token) 440 | } else { 441 | ds.field("key", &key) 442 | } 443 | } 444 | TokenEntryId::Word => { 445 | if let Ok(token) = WordToken::try_from(entry) { 446 | ds.field("token", &token) 447 | } else { 448 | ds.field("key", &key) 449 | } 450 | } 451 | TokenEntryId::Dword => { 452 | if let Ok(token) = DwordToken::try_from(entry) { 453 | ds.field("token", &token) 454 | } else { 455 | ds.field("key", &key) 456 | } 457 | } 458 | TokenEntryId::Unknown(_) => ds.field("key", &key), 459 | } 460 | .field("value", &value) 461 | .finish() 462 | } 463 | } 464 | 465 | impl TokensEntryItem<&TOKEN_ENTRY> { 466 | pub fn id(&self) -> u32 { 467 | self.token.key.get() 468 | } 469 | pub fn value(&self) -> u32 { 470 | self.token.value.get() 471 | & match self.entry_id { 472 | TokenEntryId::Bool => 0x1, 473 | TokenEntryId::Byte => 0xFF, 474 | TokenEntryId::Word => 0xFFFF, 475 | TokenEntryId::Dword => 0xFFFF_FFFF, 476 | TokenEntryId::Unknown(_) => 0xFFFF_FFFF, 477 | } 478 | } 479 | } 480 | 481 | impl<'a> TokensEntryIter<&'a [u8]> { 482 | /// It's useful to have some way of NOT mutating self.buf. This is what 483 | /// this function does. Note: The caller needs to manually decrease 484 | /// remaining_used_size for each call if desired. 485 | fn next_item<'b>( 486 | entry_id: TokenEntryId, 487 | buf: &mut &'b [u8], 488 | ) -> Result> { 489 | if buf.is_empty() { 490 | return Err(Error::FileSystem( 491 | FileSystemError::InconsistentHeader, 492 | "TOKEN_ENTRY", 493 | )); 494 | } 495 | let header = match take_header_from_collection::(&mut *buf) 496 | { 497 | Some(item) => item, 498 | None => { 499 | return Err(Error::FileSystem( 500 | FileSystemError::InconsistentHeader, 501 | "TOKEN_ENTRY", 502 | )); 503 | } 504 | }; 505 | Ok(TokensEntryItem { entry_id, token: header }) 506 | } 507 | pub(crate) fn next1(&mut self) -> Result> { 508 | if self.remaining_used_size == 0 { 509 | panic!("Internal error"); 510 | } 511 | match Self::next_item(self.entry_id, &mut self.buf) { 512 | Ok(e) => { 513 | if self.remaining_used_size >= 8 { 514 | } else { 515 | return Err(Error::FileSystem( 516 | FileSystemError::InconsistentHeader, 517 | "TOKEN_ENTRY", 518 | )); 519 | } 520 | self.remaining_used_size -= 8; 521 | Ok(e) 522 | } 523 | Err(e) => Err(e), 524 | } 525 | } 526 | /// Validates the entries (recursively). Also consumes iterator. 527 | pub(crate) fn validate(mut self, _context: ApcbContext) -> Result<()> { 528 | let context_format = ContextFormat::from_u8(self.context_format) 529 | .ok_or(Error::FileSystem( 530 | FileSystemError::InconsistentHeader, 531 | "ENTRY_HEADER::context_format", 532 | ))?; 533 | if context_format != ContextFormat::SortAscending { 534 | return Err(Error::FileSystem( 535 | FileSystemError::InconsistentHeader, 536 | "ENTRY_HEADER::context_format", 537 | )); 538 | } 539 | let mut previous_id = 0_u32; 540 | while self.remaining_used_size > 0 { 541 | match self.next1() { 542 | Ok(item) => { 543 | let id = item.id(); 544 | if id < previous_id { 545 | return Err(Error::TokenOrderingViolation); 546 | } 547 | previous_id = id 548 | } 549 | Err(e) => { 550 | return Err(e); 551 | } 552 | } 553 | } 554 | Ok(()) 555 | } 556 | } 557 | 558 | impl<'a> Iterator for TokensEntryIter<&'a [u8]> { 559 | type Item = TokensEntryItem<&'a TOKEN_ENTRY>; 560 | 561 | fn next(&mut self) -> Option { 562 | if self.remaining_used_size == 0 { 563 | return None; 564 | } 565 | self.next1().ok() 566 | } 567 | } 568 | 569 | impl TokensEntryBodyItem { 570 | pub fn iter(&self) -> Result> { 571 | let entry_id = self.prepare_iter()?; 572 | Ok(TokensEntryIter { 573 | context_format: self.context_format, 574 | entry_id, 575 | buf: &self.buf, 576 | remaining_used_size: self.used_size, 577 | }) 578 | } 579 | pub fn token( 580 | &self, 581 | token_id: u32, 582 | ) -> Option> { 583 | (self.iter().ok()?).find(|entry| entry.id() == token_id) 584 | } 585 | pub fn validate(&self) -> Result<()> { 586 | self.iter()?.validate(self.context) 587 | } 588 | } 589 | 590 | impl TokensEntryBodyItem<&mut [u8]> { 591 | pub fn iter_mut(&mut self) -> Result> { 592 | let entry_id = self.prepare_iter()?; 593 | Ok(TokensEntryIter { 594 | context_format: self.context_format, 595 | entry_id, 596 | buf: self.buf, 597 | remaining_used_size: self.used_size, 598 | }) 599 | } 600 | pub fn token_mut( 601 | &mut self, 602 | token_id: u32, 603 | ) -> Option> { 604 | (self.iter_mut().ok()?).find(|entry| entry.id() == token_id) 605 | } 606 | 607 | /// Precondition: Caller already increased the group size by 608 | /// `size_of::()`. 609 | pub(crate) fn insert_token( 610 | &mut self, 611 | token_id: u32, 612 | token_value: u32, 613 | ) -> Result<()> { 614 | let mut iter = self.iter_mut()?; 615 | match iter.insert_token(token_id, token_value) { 616 | Ok(_) => Ok(()), 617 | Err(e) => Err(e), 618 | } 619 | } 620 | 621 | pub(crate) fn delete_token(&mut self, token_id: u32) -> Result<()> { 622 | self.iter_mut()?.delete_token(token_id) 623 | } 624 | } 625 | 626 | impl TokenEntryId { 627 | pub(crate) fn ensure_abl0_compatibility( 628 | &self, 629 | abl0_version: u32, 630 | tokens: &TokensEntryBodyItem<&[u8]>, 631 | ) -> Result<()> { 632 | match *self { 633 | TokenEntryId::Bool => { 634 | for token in tokens.iter()? { 635 | let token_id = token.id(); 636 | if !BoolToken::valid_for_abl0_raw(abl0_version, token_id) { 637 | return Err(Error::TokenVersionMismatch { 638 | entry_id: *self, 639 | token_id, 640 | abl0_version, 641 | }); 642 | } 643 | } 644 | } 645 | TokenEntryId::Byte => { 646 | for token in tokens.iter()? { 647 | let token_id = token.id(); 648 | if !ByteToken::valid_for_abl0_raw(abl0_version, token_id) { 649 | return Err(Error::TokenVersionMismatch { 650 | entry_id: *self, 651 | token_id, 652 | abl0_version, 653 | }); 654 | } 655 | } 656 | } 657 | TokenEntryId::Word => { 658 | for token in tokens.iter()? { 659 | let token_id = token.id(); 660 | if !WordToken::valid_for_abl0_raw(abl0_version, token_id) { 661 | return Err(Error::TokenVersionMismatch { 662 | entry_id: *self, 663 | token_id, 664 | abl0_version, 665 | }); 666 | } 667 | } 668 | } 669 | TokenEntryId::Dword => { 670 | for token in tokens.iter()? { 671 | let token_id = token.id(); 672 | if !DwordToken::valid_for_abl0_raw(abl0_version, token_id) { 673 | return Err(Error::TokenVersionMismatch { 674 | entry_id: *self, 675 | token_id, 676 | abl0_version, 677 | }); 678 | } 679 | } 680 | } 681 | TokenEntryId::Unknown(_) => {} 682 | } 683 | Ok(()) 684 | } 685 | } 686 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use crate::naples::ParameterTokenConfig; 6 | use crate::ondisk::BoardInstances; 7 | use crate::ondisk::EntryId; 8 | use crate::ondisk::GroupId; 9 | use crate::ondisk::TokenEntryId; 10 | 11 | #[derive(Debug)] 12 | #[cfg_attr(feature = "std", derive(thiserror::Error))] 13 | #[non_exhaustive] 14 | pub enum FileSystemError { 15 | #[cfg_attr(feature = "std", error("inconsistent header"))] 16 | InconsistentHeader, 17 | #[cfg_attr(feature = "std", error("payload too big"))] 18 | PayloadTooBig, 19 | } 20 | 21 | #[derive(Debug)] 22 | #[cfg_attr(feature = "std", derive(thiserror::Error))] 23 | #[non_exhaustive] 24 | pub enum Error { 25 | #[cfg_attr(feature = "std", error("arithmetic overflow"))] 26 | ArithmeticOverflow, 27 | #[cfg_attr(feature = "std", error("file system error {0}: {1}"))] 28 | FileSystem(FileSystemError, &'static str), // message, field name 29 | #[cfg_attr(feature = "std", error("out of space"))] 30 | OutOfSpace, 31 | #[cfg_attr(feature = "std", error("group not found - group: {group_id:?}"))] 32 | #[non_exhaustive] 33 | GroupNotFound { group_id: GroupId }, 34 | #[cfg_attr( 35 | feature = "std", 36 | error("group unique key violation - group: {group_id:?}") 37 | )] 38 | #[non_exhaustive] 39 | GroupUniqueKeyViolation { group_id: GroupId }, 40 | #[cfg_attr( 41 | feature = "std", 42 | error( 43 | "group type mismatch - group: {group_id:?}, signature: {signature:?}" 44 | ) 45 | )] 46 | #[non_exhaustive] 47 | GroupTypeMismatch { group_id: GroupId, signature: [u8; 4] }, 48 | #[cfg_attr( 49 | feature = "std", 50 | error( 51 | "entry not found - entry: {entry_id:?}, instance: {instance_id:#04x}, board mask: {board_instance_mask:?}" 52 | ) 53 | )] 54 | #[non_exhaustive] 55 | EntryNotFound { 56 | entry_id: EntryId, 57 | instance_id: u16, 58 | board_instance_mask: BoardInstances, 59 | }, 60 | #[cfg_attr( 61 | feature = "std", 62 | error( 63 | "entry unique key violation - entry: {entry_id:?}, instance: {instance_id:#04x}, board mask: {board_instance_mask:?}" 64 | ) 65 | )] 66 | #[non_exhaustive] 67 | EntryUniqueKeyViolation { 68 | entry_id: EntryId, 69 | instance_id: u16, 70 | board_instance_mask: BoardInstances, 71 | }, 72 | #[cfg_attr(feature = "std", error("entry type mismatch"))] 73 | EntryTypeMismatch, 74 | #[cfg_attr(feature = "std", error("entry range"))] 75 | EntryRange, 76 | #[cfg_attr(feature = "std", error("token not found"))] 77 | #[non_exhaustive] 78 | TokenNotFound { 79 | token_id: u32, 80 | //entry_id: TokenEntryId, 81 | //instance_id: u16, 82 | //board_instance_mask: BoardInstances, 83 | }, 84 | #[cfg_attr(feature = "std", error("token ordering violation"))] 85 | TokenOrderingViolation, 86 | #[cfg_attr( 87 | feature = "std", 88 | error( 89 | "token unique key violation - entry: {entry_id:?}, instance: {instance_id:#04x}, board mask: {board_instance_mask:?}, token: {token_id:#08x}" 90 | ) 91 | )] 92 | #[non_exhaustive] 93 | TokenUniqueKeyViolation { 94 | entry_id: EntryId, 95 | instance_id: u16, 96 | board_instance_mask: BoardInstances, 97 | token_id: u32, 98 | }, 99 | #[cfg_attr(feature = "std", error("token range - token {token_id:#08x}"))] 100 | #[non_exhaustive] 101 | TokenRange { token_id: u32 }, 102 | #[cfg_attr( 103 | feature = "std", 104 | error( 105 | "token entry {entry_id:?} token {token_id:#08x} is incompatible with ABL version {abl0_version:#08x}" 106 | ) 107 | )] 108 | #[non_exhaustive] 109 | TokenVersionMismatch { 110 | entry_id: TokenEntryId, 111 | token_id: u32, 112 | abl0_version: u32, 113 | }, 114 | #[cfg_attr(feature = "std", error("parameter not found"))] 115 | #[non_exhaustive] 116 | ParameterNotFound { parameter_id: ParameterTokenConfig }, 117 | #[cfg_attr(feature = "std", error("parameter range"))] 118 | ParameterRange, 119 | // Errors used only for Serde 120 | #[cfg_attr(feature = "std", error("entry not extractable"))] 121 | EntryNotExtractable, 122 | #[cfg_attr(feature = "std", error("context mismatch"))] 123 | ContextMismatch, 124 | } 125 | 126 | pub type Result = core::result::Result; 127 | 128 | pub enum PriorityLevel { 129 | HardForce, 130 | High, 131 | Medium, 132 | EventLogging, 133 | Low, 134 | Normal, // the default 135 | } 136 | 137 | #[cfg(feature = "std")] 138 | extern crate std; 139 | 140 | #[cfg(feature = "std")] 141 | use std::borrow::Cow; 142 | 143 | #[cfg(feature = "std")] 144 | pub(crate) type PtrMut<'a, T> = Cow<'a, T>; 145 | 146 | #[cfg(not(feature = "std"))] 147 | pub(crate) type PtrMut<'a, T> = &'a mut T; 148 | 149 | // Note: The integer is 0x100 * MemDfeSearchElement.header_size + 0x10000 * 150 | // MemDfeSearchElement.payload_size + 0x1000000 * 151 | // MemDfeSearchElement.payload_ext_size 152 | // and is mostly for debugging 153 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 154 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 155 | #[non_exhaustive] 156 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] // FIXME: Remove Copy? 157 | pub enum MemDfeSearchVersion { 158 | /// At least Genoa 1.0.0.? or lower. 159 | Genoa1 = 0x000c08, 160 | /// At least Genoa 1.0.0.8 or higher. 161 | Genoa2 = 0x0c0c08, 162 | /// At least Raphael AM5 1.7.0 or higher. 163 | /// At least Granite Ridge AM5 1.7.0 or higher. 164 | // At least Fire Range 0.0.6.0 or higher. 165 | Turin1 = 0x0c0c0c, 166 | } 167 | 168 | #[derive(Copy, Clone, Debug, Default)] // TODO: Remove Copy? 169 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 170 | #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 171 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 172 | pub struct ApcbContext { 173 | #[cfg_attr(feature = "serde", serde(default))] 174 | mem_dfe_search_version: Option, 175 | } 176 | 177 | impl ApcbContext { 178 | pub fn builder() -> Self { 179 | Self::default() 180 | } 181 | pub fn mem_dfe_search_version(&self) -> Option { 182 | self.mem_dfe_search_version 183 | } 184 | pub fn with_mem_dfe_search_version( 185 | &mut self, 186 | value: Option, 187 | ) -> &mut Self { 188 | self.mem_dfe_search_version = value; 189 | self 190 | } 191 | pub fn build(&self) -> Self { 192 | *self 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/compat.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | #[test] 3 | #[allow(non_snake_case)] 4 | fn test_current_FchConsoleOutMode_Disabled() { 5 | let mode: amd_apcb::FchConsoleOutMode = 6 | serde_yaml::from_str("\"Disabled\"") 7 | .expect("configuration be valid JSON"); 8 | assert_eq!(mode, amd_apcb::FchConsoleOutMode::Disabled); 9 | } 10 | 11 | #[cfg(feature = "serde")] 12 | #[test] 13 | #[allow(non_snake_case)] 14 | fn test_current_FchConsoleOutMode_Enabled() { 15 | let mode: amd_apcb::FchConsoleOutMode = serde_yaml::from_str("\"Enabled\"") 16 | .expect("configuration be valid JSON"); 17 | assert_eq!(mode, amd_apcb::FchConsoleOutMode::Enabled); 18 | } 19 | 20 | #[cfg(feature = "serde")] 21 | #[test] 22 | #[allow(non_snake_case)] 23 | fn test_compat_FchConsoleOutMode_0() { 24 | let mode: amd_apcb::FchConsoleOutMode = 25 | serde_yaml::from_str("0").expect("configuration be valid JSON"); 26 | assert_eq!(mode, amd_apcb::FchConsoleOutMode::Disabled); 27 | } 28 | 29 | #[cfg(feature = "serde")] 30 | #[test] 31 | #[allow(non_snake_case)] 32 | fn test_compat_FchConsoleOutMode_1() { 33 | let mode: amd_apcb::FchConsoleOutMode = 34 | serde_yaml::from_str("1").expect("configuration be valid JSON"); 35 | assert_eq!(mode, amd_apcb::FchConsoleOutMode::Enabled); 36 | } 37 | 38 | #[cfg(feature = "serde")] 39 | #[test] 40 | #[allow(non_snake_case)] 41 | fn test_invalid_FchConsoleOutMode() { 42 | match serde_yaml::from_str::("\"Disabledx\"") { 43 | Ok(_) => { 44 | panic!("unexpected success"); 45 | } 46 | Err(_) => {} 47 | }; 48 | } 49 | 50 | #[cfg(feature = "serde")] 51 | #[test] 52 | #[allow(non_snake_case)] 53 | fn test_invalid_FchConsoleOutMode_5() { 54 | match serde_yaml::from_str::("5") { 55 | Ok(_) => { 56 | panic!("unexpected success"); 57 | } 58 | Err(_) => {} 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /tests/headers.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | const V3_CONFIG_STR: &str = r#" 3 | { 4 | version: "0.1.0", 5 | header: { 6 | signature: "APCB", 7 | header_size: 0x0000, 8 | version: 48, 9 | unique_apcb_instance: 0x00000002, 10 | }, 11 | v3_header_ext: { 12 | signature: "ECB2", 13 | _reserved_2: 0x0010, 14 | struct_version: 18, 15 | data_version: 256, 16 | ext_header_size: 0x00000060, 17 | _reserved_4: 0xffff, 18 | _reserved_5: 0x0040, 19 | data_offset: 0x0058, 20 | header_checksum: 0x00, 21 | integrity_sign: [ 22 | 0x00, 23 | 0x42, 24 | 0x00, 25 | 0x00, 26 | 0x00, 27 | 0x00, 28 | 0x00, 29 | 0x00, 30 | 0x00, 31 | 0x00, 32 | 0x00, 33 | 0x00, 34 | 0x00, 35 | 0x00, 36 | 0x00, 37 | 0x00, 38 | 0x00, 39 | 0x00, 40 | 0x00, 41 | 0x00, 42 | 0x00, 43 | 0x00, 44 | 0x00, 45 | 0x00, 46 | 0x00, 47 | 0x00, 48 | 0x00, 49 | 0x00, 50 | 0x00, 51 | 0x00, 52 | 0x00, 53 | 0x00 54 | ], 55 | signature_ending: "BCBA" 56 | }, 57 | groups: [ 58 | ], 59 | entries: [ 60 | ] 61 | } 62 | "#; 63 | 64 | #[cfg(feature = "serde")] 65 | const INVALID_CONFIG_STR: &str = r#" 66 | { 67 | version: "0.1.0", 68 | header: { 69 | signature: "APCB", 70 | header_size: 0x0000, 71 | version: 48, 72 | apcb_size: 0x00001274, 73 | unique_apcb_instance: 0x00000002, 74 | checksum_byte: 0x79 75 | }, 76 | v3_headerquux: { 77 | signature: "ECB2", 78 | _reserved_2: 0x0010, 79 | struct_version: 18, 80 | data_version: 256, 81 | ext_header_size: 0x00000060, 82 | _reserved_4: 0xffff, 83 | _reserved_5: 0x0040, 84 | data_offset: 0x0058, 85 | header_checksum: 0x00, 86 | integrity_sign: [ 87 | 0x00, 88 | 0x42, 89 | 0x00, 90 | 0x00, 91 | 0x00, 92 | 0x00, 93 | 0x00, 94 | 0x00, 95 | 0x00, 96 | 0x00, 97 | 0x00, 98 | 0x00, 99 | 0x00, 100 | 0x00, 101 | 0x00, 102 | 0x00, 103 | 0x00, 104 | 0x00, 105 | 0x00, 106 | 0x00, 107 | 0x00, 108 | 0x00, 109 | 0x00, 110 | 0x00, 111 | 0x00, 112 | 0x00, 113 | 0x00, 114 | 0x00, 115 | 0x00, 116 | 0x00, 117 | 0x00, 118 | 0x00 119 | ], 120 | signature_ending: "BCBA" 121 | }, 122 | groups: [ 123 | ], 124 | entries: [ 125 | ] 126 | } 127 | "#; 128 | 129 | #[cfg(feature = "serde")] 130 | const V2_CONFIG_STR: &str = r#" 131 | { 132 | version: "0.1.0", 133 | header: { 134 | signature: "APCB", 135 | header_size: 0x0000, 136 | version: 48, 137 | unique_apcb_instance: 0x00000002, 138 | }, 139 | groups: [ 140 | ], 141 | entries: [ 142 | ] 143 | } 144 | "#; 145 | 146 | #[cfg(feature = "serde")] 147 | #[test] 148 | fn test_v3_header() { 149 | let configuration: amd_apcb::Apcb = serde_yaml::from_str(&V3_CONFIG_STR) 150 | .expect("configuration be valid JSON"); 151 | let header = configuration.header().unwrap(); 152 | assert_eq!(header.unique_apcb_instance().unwrap(), 2); 153 | assert_eq!(header.header_size.get(), 128); 154 | let v3_header_ext = configuration.v3_header_ext().unwrap().unwrap(); 155 | assert_eq!(v3_header_ext.integrity_sign[1], 0x42); 156 | } 157 | 158 | #[cfg(feature = "serde")] 159 | #[test] 160 | fn test_v2_header() { 161 | let configuration: amd_apcb::Apcb = serde_yaml::from_str(&V2_CONFIG_STR) 162 | .expect("configuration be valid JSON"); 163 | let header = configuration.header().unwrap(); 164 | assert_eq!(header.header_size.get(), 32); 165 | assert_eq!(header.unique_apcb_instance().unwrap(), 2); 166 | assert!(configuration.v3_header_ext().unwrap().is_none()); 167 | } 168 | 169 | #[cfg(feature = "serde")] 170 | #[test] 171 | fn test_unknown_field() { 172 | match serde_yaml::from_str::(&INVALID_CONFIG_STR) { 173 | Ok(_) => { 174 | panic!("unexpected success"); 175 | } 176 | Err(e) => { 177 | if e.to_string().contains("unknown field") { 178 | return; 179 | } else { 180 | panic!("unexpected error: {}", e.to_string()); 181 | } 182 | } 183 | }; 184 | } 185 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | clap = "4.0.30" 8 | duct = "0.13.6" 9 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | //! 6 | //! Build driver for pico host boot loader. 7 | //! 8 | use clap; 9 | use duct::cmd; 10 | use std::env; 11 | use std::path::Path; 12 | use std::process; 13 | 14 | /// BuildProfile defines whether we build in release or 15 | /// debug mode. 16 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 17 | enum BuildProfile { 18 | Debug, 19 | Release, 20 | } 21 | 22 | impl BuildProfile { 23 | /// Returns a new BuildProfile constructed from the 24 | /// given args. 25 | fn new(matches: &clap::ArgMatches) -> BuildProfile { 26 | if matches.get_flag("release") { 27 | BuildProfile::Release 28 | } else { 29 | BuildProfile::Debug 30 | } 31 | } 32 | 33 | /// Returns the subdirectory component corresponding 34 | /// to the build type. 35 | fn _dir(self) -> &'static Path { 36 | Path::new(match self { 37 | Self::Debug => "debug", 38 | Self::Release => "release", 39 | }) 40 | } 41 | 42 | /// Yields the appropriate cargo argument for the given 43 | /// build profile. 44 | fn build_type(self) -> Option<&'static str> { 45 | match self { 46 | Self::Release => Some("--release"), 47 | Self::Debug => None, 48 | } 49 | } 50 | } 51 | 52 | /// Build arguments including path to the compressed 53 | /// cpio archive we use as a "ramdisk 54 | #[derive(Clone, Debug)] 55 | struct BuildArgs { 56 | profile: BuildProfile, 57 | locked: bool, 58 | verbose: bool, 59 | } 60 | 61 | impl BuildArgs { 62 | /// Extracts the build profile type from the given matched 63 | /// arguments. Debug is the default. 64 | fn new(matches: &clap::ArgMatches) -> BuildArgs { 65 | let profile = BuildProfile::new(matches); 66 | let locked = matches.get_flag("locked"); 67 | let verbose = matches.get_flag("verbose"); 68 | BuildArgs { profile, locked, verbose } 69 | } 70 | } 71 | 72 | fn main() { 73 | let matches = parse_args(); 74 | match matches.subcommand() { 75 | Some(("build", m)) => build(BuildArgs::new(m)), 76 | Some(("test", m)) => test(BuildArgs::new(m)), 77 | Some(("tests", m)) => tests(BuildArgs::new(m)), 78 | Some(("expand", _m)) => expand(), 79 | Some(("clippy", m)) => clippy(m.get_flag("locked")), 80 | Some(("clean", _m)) => clean(), 81 | _ => { 82 | println!("Unknown command"); 83 | process::exit(1); 84 | } 85 | } 86 | } 87 | 88 | /// Parse program arguments and return the match structure. 89 | fn parse_args() -> clap::ArgMatches { 90 | clap::Command::new("xtask") 91 | .version("0.1.0") 92 | .author("Oxide Computer Company") 93 | .about("xtask build tool") 94 | .subcommand( 95 | clap::Command::new("build").about("Builds").args(&[ 96 | clap::arg!(--locked "Build locked to Cargo.lock"), 97 | clap::arg!(--verbose "Build verbosely"), 98 | clap::arg!(--release "Build optimized version") 99 | .conflicts_with("debug"), 100 | clap::arg!(--debug "Build debug version (default)") 101 | .conflicts_with("release"), 102 | ]), 103 | ) 104 | .subcommand( 105 | clap::Command::new("test").about("Run unit tests").args(&[ 106 | clap::arg!(--locked "Build or test locked to Cargo.lock"), 107 | clap::arg!(--verbose "Build verbosely"), 108 | clap::arg!(--release "Test optimized version") 109 | .conflicts_with("debug"), 110 | clap::arg!(--debug "Test debug version (default)") 111 | .conflicts_with("release"), 112 | ]), 113 | ) 114 | .subcommand( 115 | clap::Command::new("tests").about("Run system tests").args(&[ 116 | clap::arg!(--locked "Build or test locked to Cargo.lock"), 117 | clap::arg!(--verbose "Build verbosely"), 118 | clap::arg!(--release "Test optimized version") 119 | .conflicts_with("debug"), 120 | clap::arg!(--debug "Test debug version (default)") 121 | .conflicts_with("release"), 122 | ]), 123 | ) 124 | .subcommand(clap::Command::new("expand").about("Expand macros")) 125 | .subcommand( 126 | clap::Command::new("clippy") 127 | .about("Run cargo clippy linter") 128 | .args(&[clap::arg!(--locked "Lint locked to Cargo.lock")]), 129 | ) 130 | .subcommand(clap::Command::new("clean").about("cargo clean")) 131 | .get_matches() 132 | } 133 | 134 | /// Runs a cross-compiled build. 135 | fn build(args: BuildArgs) { 136 | let build_type = args.profile.build_type().unwrap_or(""); 137 | let locked = args.locked.then_some("--locked").unwrap_or(""); 138 | let verbose = args.verbose.then_some("--verbose").unwrap_or(""); 139 | let args = format!("build {locked} {verbose} {build_type}"); 140 | cmd(cargo(), args.split_whitespace()).run().expect("build successful"); 141 | } 142 | 143 | /// Runs unit tests. 144 | fn test(args: BuildArgs) { 145 | let build_type = args.profile.build_type().unwrap_or(""); 146 | let locked = args.locked.then_some("--locked").unwrap_or(""); 147 | let verbose = args.verbose.then_some("--verbose").unwrap_or(""); 148 | // This should not run the integration tests, otherwise serde_yaml 149 | // will fail because there's no serde. 150 | let args = format!( 151 | "test --no-default-features --tests --lib {locked} {verbose} {build_type}" 152 | ); 153 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 154 | } 155 | 156 | /// Runs system tests. 157 | fn tests(args: BuildArgs) { 158 | let build_type = args.profile.build_type().unwrap_or(""); 159 | let locked = args.locked.then_some("--locked").unwrap_or(""); 160 | let verbose = args.verbose.then_some("--verbose").unwrap_or(""); 161 | let args = format!( 162 | "test --no-default-features {locked} {build_type} {verbose} --tests --lib" 163 | ); 164 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 165 | let args = format!("build {locked} {build_type} {verbose} --features std"); 166 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 167 | let args = 168 | format!("build {locked} {build_type} {verbose} --features serde"); 169 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 170 | let args = format!( 171 | "build {locked} {build_type} {verbose} --features serde,schemars,serde-hex" 172 | ); 173 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 174 | let args = format!( 175 | "build {locked} {build_type} {verbose} --features serde,schemars --example fromyaml" 176 | ); 177 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 178 | let args = format!( 179 | "test {locked} {build_type} {verbose} --test * --features serde,schemars" 180 | ); 181 | cmd(cargo(), args.split_whitespace()).run().expect("test successful"); 182 | } 183 | 184 | /// Expands macros. 185 | fn expand() { 186 | cmd!(cargo(), "expand").run().expect("expand successful"); 187 | } 188 | 189 | /// Runs the Clippy linter. 190 | fn clippy(with_locked: bool) { 191 | let locked = with_locked.then_some("--locked").unwrap_or(""); 192 | let args = format!("clippy {locked}"); 193 | cmd(cargo(), args.split_whitespace()).run().expect("clippy successful"); 194 | } 195 | 196 | /// Runs clean on the project. 197 | fn clean() { 198 | cmd!(cargo(), "clean").run().expect("clean successful"); 199 | } 200 | 201 | /// Returns the value of the given environment variable, 202 | /// or the default if unspecified. 203 | fn env_or(var: &str, default: &str) -> String { 204 | env::var(var).unwrap_or(default.into()) 205 | } 206 | 207 | /// Returns the name of the cargo binary. 208 | fn cargo() -> String { 209 | env_or("CARGO", "cargo") 210 | } 211 | --------------------------------------------------------------------------------