Matt Johnston | bde874e | 2024-11-05 16:03:51 +0800 | [diff] [blame] | 1 | #include <stdint.h> |
| 2 | #include <stdbool.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | #include <libpldm/pldm.h> |
| 6 | #include <libpldm/utils.h> |
| 7 | #include <libpldm/platform.h> |
| 8 | #include <libpldm/control.h> |
| 9 | #include <compiler.h> |
| 10 | #include <msgbuf.h> |
| 11 | |
| 12 | #include "control-internal.h" |
| 13 | |
| 14 | #define PLDM_BASE_VERSIONS_COUNT 2 |
| 15 | static const uint32_t PLDM_BASE_VERSIONS[PLDM_BASE_VERSIONS_COUNT] = { |
| 16 | /* PLDM 1.1.0 is current implemented. */ |
| 17 | 0xf1f1f000, |
| 18 | /* CRC. Calculated with python: |
| 19 | hex(crccheck.crc.Crc32.calc(struct.pack('<I', 0xf1f1f000))) |
| 20 | */ |
| 21 | 0x539dbeba, |
| 22 | }; |
| 23 | const bitfield8_t PLDM_CONTROL_COMMANDS[32] = { |
| 24 | // 0x00..0x07 |
| 25 | { .byte = (1 << PLDM_GET_TID | 1 << PLDM_GET_PLDM_VERSION | |
| 26 | 1 << PLDM_GET_PLDM_TYPES | 1 << PLDM_GET_PLDM_COMMANDS) } |
| 27 | }; |
| 28 | |
| 29 | static int pldm_control_reply_error(uint8_t ccode, |
| 30 | const struct pldm_header_info *req_hdr, |
| 31 | struct pldm_msg *resp, |
| 32 | size_t *resp_payload_len) |
| 33 | { |
| 34 | int rc; |
| 35 | |
| 36 | /* 1 byte completion code */ |
| 37 | if (*resp_payload_len < 1) { |
| 38 | return -EOVERFLOW; |
| 39 | } |
| 40 | *resp_payload_len = 1; |
| 41 | |
| 42 | rc = encode_cc_only_resp(req_hdr->instance, PLDM_FWUP, req_hdr->command, |
| 43 | ccode, resp); |
| 44 | if (rc != PLDM_SUCCESS) { |
| 45 | return -EINVAL; |
| 46 | } |
| 47 | return 0; |
| 48 | } |
| 49 | |
| 50 | static int pldm_control_get_tid(const struct pldm_header_info *hdr, |
| 51 | const struct pldm_msg *req LIBPLDM_CC_UNUSED, |
| 52 | size_t req_payload_len, struct pldm_msg *resp, |
| 53 | size_t *resp_payload_len) |
| 54 | { |
| 55 | if (req_payload_len != PLDM_GET_TID_REQ_BYTES) { |
| 56 | return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr, |
| 57 | resp, resp_payload_len); |
| 58 | } |
| 59 | |
| 60 | if (*resp_payload_len <= PLDM_GET_TID_RESP_BYTES) { |
| 61 | return -EOVERFLOW; |
| 62 | } |
| 63 | *resp_payload_len = PLDM_GET_TID_RESP_BYTES; |
| 64 | |
| 65 | uint8_t cc = encode_get_tid_resp(hdr->instance, PLDM_SUCCESS, |
| 66 | PLDM_TID_UNASSIGNED, resp); |
| 67 | if (cc) { |
| 68 | return pldm_control_reply_error(cc, hdr, resp, |
| 69 | resp_payload_len); |
| 70 | } |
| 71 | return 0; |
| 72 | } |
| 73 | |
| 74 | static int pldm_control_get_version(struct pldm_control *control, |
| 75 | const struct pldm_header_info *hdr, |
| 76 | const struct pldm_msg *req, |
| 77 | size_t req_payload_len, |
| 78 | struct pldm_msg *resp, |
| 79 | size_t *resp_payload_len) |
| 80 | { |
| 81 | uint8_t cc; |
| 82 | |
| 83 | uint32_t handle; |
| 84 | uint8_t opflag; |
| 85 | uint8_t type; |
| 86 | cc = decode_get_version_req(req, req_payload_len, &handle, &opflag, |
| 87 | &type); |
| 88 | if (cc) { |
| 89 | return pldm_control_reply_error(cc, hdr, resp, |
| 90 | resp_payload_len); |
| 91 | } |
| 92 | |
| 93 | /* Response is always sent as a single transfer */ |
| 94 | if (opflag != PLDM_GET_FIRSTPART) { |
| 95 | return pldm_control_reply_error( |
| 96 | PLDM_CONTROL_INVALID_TRANSFER_OPERATION_FLAG, hdr, resp, |
| 97 | resp_payload_len); |
| 98 | } |
| 99 | |
| 100 | const struct pldm_type_versions *v = NULL; |
| 101 | for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) { |
| 102 | if (control->types[i].pldm_type == type && |
| 103 | control->types[i].versions) { |
| 104 | v = &control->types[i]; |
| 105 | break; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | if (!v) { |
| 110 | return pldm_control_reply_error( |
| 111 | PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr, |
| 112 | resp, resp_payload_len); |
| 113 | } |
| 114 | |
| 115 | /* encode_get_version_resp doesn't have length checking */ |
| 116 | uint32_t required_resp_payload = |
| 117 | 1 + 4 + 1 + v->versions_count * sizeof(ver32_t); |
| 118 | if (*resp_payload_len < required_resp_payload) { |
| 119 | return -EOVERFLOW; |
| 120 | } |
| 121 | *resp_payload_len = required_resp_payload; |
| 122 | |
| 123 | /* crc32 is included in the versions buffer */ |
| 124 | cc = encode_get_version_resp(hdr->instance, PLDM_SUCCESS, 0, |
| 125 | PLDM_START_AND_END, v->versions, |
| 126 | v->versions_count * sizeof(ver32_t), resp); |
| 127 | if (cc) { |
| 128 | return pldm_control_reply_error(cc, hdr, resp, |
| 129 | resp_payload_len); |
| 130 | } |
| 131 | return 0; |
| 132 | } |
| 133 | |
| 134 | static int pldm_control_get_types(struct pldm_control *control, |
| 135 | const struct pldm_header_info *hdr, |
| 136 | const struct pldm_msg *req LIBPLDM_CC_UNUSED, |
| 137 | size_t req_payload_len, struct pldm_msg *resp, |
| 138 | size_t *resp_payload_len) |
| 139 | { |
| 140 | uint8_t cc; |
| 141 | |
| 142 | if (req_payload_len != PLDM_GET_TYPES_REQ_BYTES) { |
| 143 | return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr, |
| 144 | resp, resp_payload_len); |
| 145 | } |
| 146 | |
| 147 | bitfield8_t types[8]; |
| 148 | memset(types, 0, sizeof(types)); |
| 149 | for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) { |
| 150 | uint8_t ty = control->types[i].pldm_type; |
| 151 | if (ty < 64 && control->types[i].versions) { |
| 152 | uint8_t bit = 1 << (ty % 8); |
| 153 | types[ty / 8].byte |= bit; |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /* encode_get_types_resp doesn't have length checking */ |
| 158 | uint32_t required_resp_payload = 1 + 8; |
| 159 | if (*resp_payload_len < required_resp_payload) { |
| 160 | return -EOVERFLOW; |
| 161 | } |
| 162 | *resp_payload_len = required_resp_payload; |
| 163 | |
| 164 | cc = encode_get_types_resp(hdr->instance, PLDM_SUCCESS, types, resp); |
| 165 | if (cc) { |
| 166 | return pldm_control_reply_error(cc, hdr, resp, |
| 167 | resp_payload_len); |
| 168 | } |
| 169 | return 0; |
| 170 | } |
| 171 | |
| 172 | static int pldm_control_get_commands(struct pldm_control *control, |
| 173 | const struct pldm_header_info *hdr, |
| 174 | const struct pldm_msg *req, |
| 175 | size_t req_payload_len, |
| 176 | struct pldm_msg *resp, |
| 177 | size_t *resp_payload_len) |
| 178 | { |
| 179 | uint8_t cc; |
| 180 | |
| 181 | uint8_t ty; |
| 182 | // version in request is ignored, since SelectPLDMVersion isn't supported currently |
| 183 | ver32_t version; |
| 184 | cc = decode_get_commands_req(req, req_payload_len, &ty, &version); |
| 185 | if (cc) { |
| 186 | return pldm_control_reply_error(cc, hdr, resp, |
| 187 | resp_payload_len); |
| 188 | } |
| 189 | |
| 190 | const struct pldm_type_versions *v = NULL; |
| 191 | for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) { |
| 192 | if (control->types[i].pldm_type == ty && |
| 193 | control->types[i].versions && control->types[i].commands) { |
| 194 | v = &control->types[i]; |
| 195 | break; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | if (!v) { |
| 200 | return pldm_control_reply_error( |
| 201 | PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr, |
| 202 | resp, resp_payload_len); |
| 203 | } |
| 204 | |
| 205 | /* encode_get_commands_resp doesn't have length checking */ |
| 206 | uint32_t required_resp_payload = 1 + 32; |
| 207 | if (*resp_payload_len < required_resp_payload) { |
| 208 | return -EOVERFLOW; |
| 209 | } |
| 210 | *resp_payload_len = required_resp_payload; |
| 211 | |
| 212 | cc = encode_get_commands_resp(hdr->instance, PLDM_SUCCESS, v->commands, |
| 213 | resp); |
| 214 | if (cc) { |
| 215 | return pldm_control_reply_error(cc, hdr, resp, |
| 216 | resp_payload_len); |
| 217 | } |
| 218 | return 0; |
| 219 | } |
| 220 | |
| 221 | /* A response should only be used when this returns 0, and *resp_len > 0 */ |
| 222 | LIBPLDM_ABI_TESTING |
| 223 | int pldm_control_handle_msg(struct pldm_control *control, const void *req_msg, |
| 224 | size_t req_len, void *resp_msg, size_t *resp_len) |
| 225 | { |
| 226 | int rc; |
| 227 | |
| 228 | /* Space for header plus completion code */ |
| 229 | if (*resp_len < sizeof(struct pldm_msg_hdr) + 1) { |
| 230 | return -EOVERFLOW; |
| 231 | } |
| 232 | size_t resp_payload_len = *resp_len - sizeof(struct pldm_msg_hdr); |
| 233 | struct pldm_msg *resp = resp_msg; |
| 234 | |
| 235 | if (req_len < sizeof(struct pldm_msg_hdr)) { |
| 236 | return -EOVERFLOW; |
| 237 | } |
| 238 | size_t req_payload_len = req_len - sizeof(struct pldm_msg_hdr); |
| 239 | const struct pldm_msg *req = req_msg; |
| 240 | |
| 241 | struct pldm_header_info hdr; |
| 242 | rc = unpack_pldm_header(&req->hdr, &hdr); |
| 243 | if (rc != PLDM_SUCCESS) { |
| 244 | return -EINVAL; |
| 245 | } |
| 246 | |
| 247 | if (hdr.pldm_type != PLDM_BASE) { |
| 248 | /* Caller should not have passed non-control */ |
| 249 | return -ENOMSG; |
| 250 | } |
| 251 | |
| 252 | if (hdr.msg_type != PLDM_REQUEST) { |
| 253 | return -EINVAL; |
| 254 | } |
| 255 | |
| 256 | /* Dispatch command */ |
| 257 | switch (hdr.command) { |
| 258 | case PLDM_GET_TID: |
| 259 | rc = pldm_control_get_tid(&hdr, req, req_payload_len, resp, |
| 260 | &resp_payload_len); |
| 261 | break; |
| 262 | case PLDM_GET_PLDM_VERSION: |
| 263 | rc = pldm_control_get_version(control, &hdr, req, |
| 264 | req_payload_len, resp, |
| 265 | &resp_payload_len); |
| 266 | break; |
| 267 | case PLDM_GET_PLDM_TYPES: |
| 268 | rc = pldm_control_get_types(control, &hdr, req, req_payload_len, |
| 269 | resp, &resp_payload_len); |
| 270 | break; |
| 271 | case PLDM_GET_PLDM_COMMANDS: |
| 272 | rc = pldm_control_get_commands(control, &hdr, req, |
| 273 | req_payload_len, resp, |
| 274 | &resp_payload_len); |
| 275 | break; |
| 276 | default: |
| 277 | rc = pldm_control_reply_error(PLDM_ERROR_UNSUPPORTED_PLDM_CMD, |
| 278 | &hdr, resp, &resp_payload_len); |
| 279 | } |
| 280 | |
| 281 | if (rc == 0) { |
| 282 | *resp_len = resp_payload_len + sizeof(struct pldm_msg_hdr); |
| 283 | } |
| 284 | |
| 285 | return rc; |
| 286 | } |
| 287 | |
| 288 | LIBPLDM_ABI_TESTING |
| 289 | int pldm_control_setup(struct pldm_control *control, size_t pldm_control_size) |
| 290 | { |
| 291 | int rc; |
| 292 | |
| 293 | if (pldm_control_size < sizeof(struct pldm_control)) { |
| 294 | return -EINVAL; |
| 295 | } |
| 296 | |
| 297 | memset(control, 0, sizeof(struct pldm_control)); |
| 298 | |
| 299 | rc = pldm_control_add_type(control, PLDM_BASE, &PLDM_BASE_VERSIONS, |
| 300 | PLDM_BASE_VERSIONS_COUNT, |
| 301 | PLDM_CONTROL_COMMANDS); |
| 302 | if (rc) { |
| 303 | return rc; |
| 304 | } |
| 305 | |
| 306 | return 0; |
| 307 | } |
| 308 | |
| 309 | LIBPLDM_ABI_TESTING |
| 310 | int pldm_control_add_type(struct pldm_control *control, uint8_t pldm_type, |
| 311 | const void *versions, size_t versions_count, |
| 312 | const bitfield8_t *commands) |
| 313 | { |
| 314 | if (versions_count < 2) { |
| 315 | /* At least one version must be provided, along with a CRC32 */ |
| 316 | return -EINVAL; |
| 317 | } |
| 318 | |
| 319 | struct pldm_type_versions *v = NULL; |
| 320 | for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) { |
| 321 | if (control->types[i].versions == NULL || |
| 322 | (control->types[i].versions != NULL && |
| 323 | control->types[i].pldm_type == pldm_type)) { |
| 324 | v = &control->types[i]; |
| 325 | break; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | if (!v) { |
| 330 | return -ENOMEM; |
| 331 | // No spare slots |
| 332 | } |
| 333 | |
| 334 | v->pldm_type = pldm_type; |
| 335 | v->versions = versions; |
| 336 | v->versions_count = versions_count; |
| 337 | v->commands = commands; |
| 338 | |
| 339 | return 0; |
| 340 | } |