| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| // Copyright 2022 IBM Corp. |
| |
| #define pr_fmt(fmt) "PLDM: " fmt |
| |
| #include <cpu.h> |
| #include <opal.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <timebase.h> |
| #include <inttypes.h> |
| #include <libpldm/entity.h> |
| #include <libpldm/pdr.h> |
| #include <libpldm/state_set.h> |
| #include <libpldm/platform.h> |
| #include "pldm.h" |
| |
| #define NO_MORE_PDR_HANDLES 0 |
| |
| static pldm_pdr *pdrs_repo; |
| static bool pdr_ready; |
| |
| struct pldm_pdrs { |
| struct pldm_tx_data *tx; |
| uint32_t record_hndl; |
| bool done; |
| int rc; |
| }; |
| |
| struct pldm_pdrs *pdrs; |
| |
| static void pdr_init_complete(bool success) |
| { |
| /* Read not successful, error out and free the buffer */ |
| if (!success) { |
| pdr_ready = false; |
| |
| if (pdrs_repo) |
| pldm_pdr_destroy(pdrs_repo); |
| return; |
| } |
| |
| /* Mark ready */ |
| pdr_ready = true; |
| } |
| |
| /* |
| * Search the matching record and return the effecter id. |
| * PDR type = PLDM_STATE_EFFECTER_PDR |
| */ |
| static int find_effecter_id_by_state_set_Id(uint16_t entity_type, |
| uint16_t state_set_id, |
| uint16_t *effecter_id, |
| uint16_t terminus_handle) |
| { |
| struct state_effecter_possible_states *possible_states; |
| struct pldm_state_effecter_pdr *state_effecter_pdr; |
| const pldm_pdr_record *record = NULL; |
| uint8_t *outData = NULL; |
| uint32_t size; |
| |
| do { |
| /* Find (first) PDR record by PLDM_STATE_EFFECTER_PDR type |
| * if record not NULL, then search will begin from this |
| * record's next record |
| */ |
| record = pldm_pdr_find_record_by_type( |
| pdrs_repo, /* PDR repo handle */ |
| PLDM_STATE_EFFECTER_PDR, |
| record, /* PDR record handle */ |
| &outData, &size); |
| |
| if (record) { |
| state_effecter_pdr = (struct pldm_state_effecter_pdr *) outData; |
| |
| *effecter_id = le16_to_cpu(state_effecter_pdr->effecter_id); |
| |
| possible_states = (struct state_effecter_possible_states *) |
| state_effecter_pdr->possible_states; |
| |
| if ((le16_to_cpu(state_effecter_pdr->entity_type) == entity_type) && |
| (le16_to_cpu(state_effecter_pdr->terminus_handle) == terminus_handle) && |
| (le16_to_cpu(possible_states->state_set_id) == state_set_id)) |
| return OPAL_SUCCESS; |
| } |
| |
| } while (record); |
| |
| return OPAL_PARAMETER; |
| } |
| |
| struct set_effecter_state_response { |
| uint8_t completion_code; |
| }; |
| |
| /* |
| * Create and send a PLDM request message for SetStateEffecterStates. |
| */ |
| static int set_state_effecter_states_req(uint16_t effecter_id, |
| set_effecter_state_field *field, |
| bool no_timeout) |
| { |
| size_t data_size = PLDM_MSG_SIZE(struct pldm_set_state_effecter_states_req); |
| struct set_effecter_state_response response; |
| size_t response_len, payload_len; |
| struct pldm_tx_data *tx = NULL; |
| void *response_msg; |
| int rc; |
| |
| struct pldm_set_state_effecter_states_req states_req = { |
| .effecter_id = effecter_id, |
| .comp_effecter_count = 1 |
| }; |
| |
| /* Encode the state effecter states request */ |
| tx = zalloc(sizeof(struct pldm_tx_data) + data_size); |
| if (!tx) |
| return OPAL_NO_MEM; |
| tx->data_size = data_size; |
| |
| rc = encode_set_state_effecter_states_req( |
| DEFAULT_INSTANCE_ID, |
| states_req.effecter_id, |
| states_req.comp_effecter_count, |
| field, |
| (struct pldm_msg *)tx->data); |
| if (rc != PLDM_SUCCESS) { |
| prlog(PR_ERR, "Encode SetStateEffecter Error, rc: %d\n", |
| rc); |
| free(tx); |
| return OPAL_PARAMETER; |
| } |
| |
| /* Send and get the response message bytes. |
| * It may happen that for some commands, the responder does not |
| * have time to respond. |
| */ |
| if (no_timeout) { |
| rc = pldm_mctp_message_tx(tx); |
| if (rc) |
| prlog(PR_ERR, "Failed to send SetStateEffecter request, rc = %d\n", rc); |
| free(tx); |
| return rc; |
| } |
| |
| /* Send and get the response message bytes */ |
| rc = pldm_requester_queue_and_wait(tx, &response_msg, &response_len); |
| if (rc) { |
| prlog(PR_ERR, "Communication Error, req: SetStateEffecter, rc: %d\n", rc); |
| free(tx); |
| return rc; |
| } |
| |
| /* Decode the message */ |
| payload_len = response_len - sizeof(struct pldm_msg_hdr); |
| |
| rc = decode_set_state_effecter_states_resp( |
| response_msg, |
| payload_len, |
| &response.completion_code); |
| if (rc != PLDM_SUCCESS || response.completion_code != PLDM_SUCCESS) { |
| prlog(PR_ERR, "Decode SetStateEffecter Error, rc: %d, cc: %d\n", |
| rc, response.completion_code); |
| free(tx); |
| free(response_msg); |
| return OPAL_PARAMETER; |
| } |
| |
| free(tx); |
| free(response_msg); |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * entity_type: System chassis (main enclosure) |
| * state_set: System Power State (260) |
| * states: Off-Soft Graceful(9) |
| */ |
| int pldm_platform_power_off(void) |
| { |
| set_effecter_state_field field; |
| uint16_t effecter_id; |
| int rc; |
| |
| if (!pdr_ready) |
| return OPAL_HARDWARE; |
| |
| rc = find_effecter_id_by_state_set_Id( |
| PLDM_ENTITY_SYSTEM_CHASSIS, |
| PLDM_STATE_SET_SYSTEM_POWER_STATE, |
| &effecter_id, BMC_TID); |
| if (rc) { |
| prlog(PR_ERR, "%s - effecter id not found\n", __func__); |
| return rc; |
| } |
| |
| field.set_request = PLDM_REQUEST_SET; |
| field.effecter_state = PLDM_STATE_SET_SYS_POWER_STATE_OFF_SOFT_GRACEFUL; |
| |
| prlog(PR_INFO, "sending system chassis Off-Soft Graceful request (effecter_id: %d)\n", |
| effecter_id); |
| |
| return set_state_effecter_states_req(effecter_id, &field, true); |
| } |
| |
| /* |
| * entity_type: System Firmware |
| * state_set: Software Termination Status(129) |
| * states: Graceful Restart Requested(6) |
| */ |
| int pldm_platform_restart(void) |
| { |
| set_effecter_state_field field; |
| uint16_t effecter_id; |
| int rc; |
| |
| if (!pdr_ready) |
| return OPAL_HARDWARE; |
| |
| rc = find_effecter_id_by_state_set_Id( |
| PLDM_ENTITY_SYS_FIRMWARE, |
| PLDM_STATE_SET_SW_TERMINATION_STATUS, |
| &effecter_id, BMC_TID); |
| if (rc) { |
| prlog(PR_ERR, "%s - effecter id not found\n", __func__); |
| return rc; |
| } |
| |
| field.set_request = PLDM_REQUEST_SET; |
| field.effecter_state = PLDM_SW_TERM_GRACEFUL_RESTART_REQUESTED; |
| |
| prlog(PR_INFO, "sending system firmware Graceful Restart request (effecter_id: %d)\n", |
| effecter_id); |
| |
| return set_state_effecter_states_req(effecter_id, &field, true); |
| } |
| |
| struct get_pdr_response { |
| uint8_t completion_code; |
| uint32_t next_record_hndl; |
| uint32_t next_data_transfer_hndl; |
| uint8_t transfer_flag; |
| uint16_t resp_cnt; |
| uint8_t *record_data; |
| size_t record_data_length; |
| uint8_t transfer_crc; |
| }; |
| |
| static int encode_and_queue_get_pdr_req(struct pldm_pdrs *pdrs); |
| |
| static void get_pdr_req_complete(struct pldm_rx_data *rx, |
| void *data) |
| { |
| struct pldm_pdrs *pdrs = (struct pldm_pdrs *)data; |
| uint32_t record_hndl = pdrs->record_hndl; |
| struct get_pdr_response response; |
| size_t payload_len; |
| int rc, i; |
| |
| prlog(PR_DEBUG, "%s - record_hndl: %d\n", __func__, record_hndl); |
| |
| if (rx == NULL) { |
| pdrs->rc = OPAL_PARAMETER; |
| pdrs->done = true; |
| } |
| |
| /* Decode the message twice; the first time, the payload buffer |
| * will be null so that the decoder will simply tell us how big |
| * the buffer should be. Then we create a suitable payload |
| * buffer and call the decoder again, this time with the real |
| * buffer so that it can fill it with data from the message. |
| * |
| * transfer_crc is not used in case of PLDM_START_AND_END. |
| */ |
| payload_len = rx->msg_len - sizeof(struct pldm_msg_hdr); |
| response.record_data_length = 0; |
| response.record_data = NULL; |
| |
| for (i = 0; i < 2; i++) { |
| rc = decode_get_pdr_resp( |
| rx->msg, payload_len, |
| &response.completion_code, |
| &response.next_record_hndl, |
| &response.next_data_transfer_hndl, |
| &response.transfer_flag, |
| &response.resp_cnt, |
| response.record_data, |
| response.record_data_length, |
| &response.transfer_crc); |
| |
| if (rc != PLDM_SUCCESS || response.completion_code != PLDM_SUCCESS) { |
| /* Message decoding failed */ |
| prlog(PR_ERR, "Decode GetPDRResp Error (rc: %d, cc: %d)\n", |
| rc, response.completion_code); |
| |
| /* BMC is not ready, try again. This behavior can be |
| * encountered when the BMC reboots and the host is |
| * still operational. |
| * The host receives a GET VERSION request indicating |
| * that we must rehcrage the pdrs. |
| */ |
| if (response.completion_code == PLDM_ERROR_NOT_READY) { |
| time_wait_ms(500); |
| encode_and_queue_get_pdr_req(pdrs); |
| return; |
| } |
| |
| pdrs->rc = OPAL_PARAMETER; |
| pdrs->done = true; |
| return; |
| } |
| |
| if (response.record_data == NULL) { |
| response.record_data_length = response.resp_cnt; |
| response.record_data = zalloc(response.resp_cnt); |
| if (!response.record_data) { |
| prlog(PR_ERR, "failed to allocate record data (size: 0x%lx)\n", response.record_data_length); |
| pdrs->rc = OPAL_NO_MEM; |
| pdrs->done = true; |
| return; |
| } |
| } |
| } |
| |
| /* we do not support multipart transfer */ |
| if (response.transfer_flag != PLDM_START_AND_END) |
| prlog(PR_ERR, "Transfert GetPDRResp not complete, transfer_flag: %d\n", |
| response.transfer_flag); |
| |
| prlog(PR_DEBUG, "%s - record_hndl: %d, next_record_hndl: %d, resp_cnt: %d\n", |
| __func__, record_hndl, |
| response.next_record_hndl, |
| response.resp_cnt); |
| |
| /* Add a PDR record to a PDR repository. |
| * Use HOST_TID as terminus handle |
| */ |
| pldm_pdr_add(pdrs_repo, |
| response.record_data, |
| response.resp_cnt, |
| record_hndl, |
| false, |
| HOST_TID); |
| |
| free(response.record_data); |
| |
| if (response.next_record_hndl != NO_MORE_PDR_HANDLES) { |
| pdrs->record_hndl = response.next_record_hndl; |
| encode_and_queue_get_pdr_req(pdrs); |
| } else { |
| /* We have to indicate the end of the initialization when we |
| * reload the pdrs in background |
| */ |
| pdr_init_complete(true); |
| pdrs->done = true; |
| pdrs->rc = OPAL_SUCCESS; |
| prlog(PR_DEBUG, "%s - done\n", __func__); |
| } |
| } |
| |
| /* |
| * Send/receive a PLDM GetPDR stateEffecter request message |
| * Get platform descriptor records. |
| * |
| * pldmtool platform GetPDR -t stateEffecter |
| * ... |
| * { |
| * "nextRecordHandle": 138, |
| * "responseCount": 30, |
| * "recordHandle": 137, |
| * "PDRHeaderVersion": 1, |
| * "PDRType": "State Effecter PDR", |
| * "recordChangeNumber": 0, |
| * "dataLength": 20, |
| * "PLDMTerminusHandle": 1, |
| * "effecterID": 43, |
| * "entityType": "[Physical] System chassis (main enclosure)", |
| * ... |
| * "Off-Soft Graceful(9)" |
| * } |
| * ... |
| */ |
| static int encode_and_queue_get_pdr_req(struct pldm_pdrs *pdrs) |
| { |
| uint32_t record_hndl = pdrs->record_hndl; |
| int rc; |
| |
| struct pldm_get_pdr_req pdr_req = { |
| .record_handle = record_hndl, /* record change number (0 for first request) */ |
| .data_transfer_handle = 0, /* (0 if transfer op is FIRSTPART) */ |
| .transfer_op_flag = PLDM_GET_FIRSTPART, /* transfer op flag */ |
| .request_count = SHRT_MAX, /* Don't limit the size of the PDR */ |
| .record_change_number = 0 /* record change number (0 for first request) */ |
| }; |
| |
| prlog(PR_DEBUG, "%s - record_hndl: %d\n", __func__, record_hndl); |
| |
| /* Encode the get_PDR request */ |
| rc = encode_get_pdr_req(DEFAULT_INSTANCE_ID, |
| pdr_req.record_handle, |
| pdr_req.data_transfer_handle, |
| pdr_req.transfer_op_flag, |
| pdr_req.request_count, |
| pdr_req.record_change_number, |
| (struct pldm_msg *)pdrs->tx->data, |
| PLDM_GET_PDR_REQ_BYTES); |
| if (rc != PLDM_SUCCESS) { |
| prlog(PR_ERR, "Encode GetPDRReq Error, rc: %d\n", rc); |
| pdrs->done = true; |
| pdrs->rc = OPAL_PARAMETER; |
| return OPAL_PARAMETER; |
| } |
| |
| /* Queue the first getpdr request */ |
| rc = pldm_requester_queue(pdrs->tx, get_pdr_req_complete, pdrs); |
| if (rc) { |
| prlog(PR_ERR, "Communication Error, req: GetPDRReq, rc: %d\n", rc); |
| pdrs->done = true; |
| pdrs->rc = OPAL_PARAMETER; |
| } |
| |
| return rc; |
| } |
| |
| static int pldm_platform_load_pdrs(void) |
| { |
| /* destroy current repo and mark repo not ready */ |
| pdr_init_complete(false); |
| |
| /* make a new PDR repository */ |
| pdrs_repo = pldm_pdr_init(); |
| |
| /* collect all PDrs into a PDR Repository */ |
| pdrs->record_hndl = 0; |
| pdrs->done = false; |
| return encode_and_queue_get_pdr_req(pdrs); |
| } |
| |
| static int pdrs_init(void) |
| { |
| int rc; |
| |
| rc = pldm_platform_load_pdrs(); |
| if (rc) |
| return rc; |
| |
| /* wait for the end of pdrs received */ |
| for (;;) { |
| if (pdrs->done) |
| break; |
| |
| time_wait_ms(5); |
| } |
| return pdrs->rc; |
| } |
| |
| int pldm_platform_init(void) |
| { |
| size_t data_size = PLDM_MSG_SIZE(struct pldm_get_pdr_req); |
| int rc; |
| |
| pdrs = zalloc(sizeof(struct pldm_pdrs)); |
| if (!pdrs) { |
| prlog(PR_ERR, "failed to allocate pdrs\n"); |
| return OPAL_NO_MEM; |
| } |
| |
| pdrs->tx = zalloc(sizeof(struct pldm_tx_data) + data_size); |
| if (!pdrs->tx) |
| return OPAL_NO_MEM; |
| pdrs->tx->data_size = data_size; |
| |
| /* retrieve all PDRs */ |
| rc = pdrs_init(); |
| if (rc) |
| goto err; |
| |
| pdr_init_complete(true); |
| prlog(PR_DEBUG, "%s - done\n", __func__); |
| |
| return OPAL_SUCCESS; |
| |
| err: |
| prlog(PR_ERR, "%s - failed to initialize pdrs, rc: %d\n", __func__, rc); |
| pdr_init_complete(false); |
| free(pdrs->tx); |
| free(pdrs); |
| return rc; |
| } |
| |
| void pldm_platform_exit(void) |
| { |
| if (pdr_ready) |
| pldm_pdr_destroy(pdrs_repo); |
| |
| if (pdrs) { |
| free(pdrs->tx); |
| free(pdrs); |
| } |
| } |