// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later // Copyright 2022 IBM Corp. #define pr_fmt(fmt) "PLDM: " fmt #include #include #include #include #include #include #include #include #include #include "pldm.h" struct get_date_time_resp { uint8_t completion_code; uint8_t seconds; uint8_t minutes; uint8_t hours; uint8_t day; uint8_t month; uint16_t year; }; static enum {idle, waiting, updated, error} time_status; static void cache_get_datetime(struct tm *tm) { if (tm == NULL) time_status = error; else { rtc_cache_update(tm); time_status = updated; } } static void get_date_time_req_complete(struct pldm_rx_data *rx, void *data __unused) { struct get_date_time_resp response; size_t payload_len; struct tm tm; int rc; if (rx == NULL) { prlog(PR_ERR, "%s: Response not received\n", __func__); cache_get_datetime(NULL); return; } /* Decode the message */ payload_len = rx->msg_len - sizeof(struct pldm_msg_hdr); rc = decode_get_date_time_resp( rx->msg, payload_len, &response.completion_code, &response.seconds, &response.minutes, &response.hours, &response.day, &response.month, &response.year); if (rc != PLDM_SUCCESS || response.completion_code != PLDM_SUCCESS) { prlog(PR_ERR, "Decode GetBiosDateTimeReq Error, rc: %d, cc: %d\n", rc, response.completion_code); cache_get_datetime(NULL); return; } /* The data arrives from BMC in BCD format. Convert it to * decimal for processing */ tm.tm_sec = bcd2dec8(response.seconds); tm.tm_min = bcd2dec8(response.minutes); tm.tm_hour = bcd2dec8(response.hours); tm.tm_mday = bcd2dec8(response.day); tm.tm_mon = bcd2dec8(response.month); tm.tm_year = bcd2dec16(response.year); if (!is_time_legal(tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year)) { prlog(PR_ERR, "%s: Invalid date time value\n", __func__); cache_get_datetime(NULL); return; } cache_get_datetime(&tm); } /* * Send a PLDM GetBiosDateTime request message */ static int get_date_time_req(void) { size_t data_size = PLDM_MSG_SIZE(0); /* the command doesn't have a message payload */ struct pldm_tx_data *tx = NULL; int rc; /* Encode the date time request */ tx = zalloc(sizeof(struct pldm_tx_data) + data_size); if (!tx) return OPAL_NO_MEM; tx->data_size = data_size; rc = encode_get_date_time_req(DEFAULT_INSTANCE_ID, (struct pldm_msg *)tx->data); if (rc != PLDM_SUCCESS) { prlog(PR_ERR, "Encode GetBiosDateTimeReq Error, rc: %d\n", rc); free(tx); return OPAL_PARAMETER; } /* Queue and get the response message bytes */ rc = pldm_requester_queue(tx, get_date_time_req_complete, NULL); if (rc) { prlog(PR_ERR, "Communication Error, req: GetBiosDateTimeReq, rc: %d\n", rc); free(tx); return rc; } free(tx); return OPAL_SUCCESS; } static int64_t pldm_opal_rtc_read(__be32 *__ymd, __be64 *__hmsm) { uint32_t ymd; uint64_t hmsm; int rc = OPAL_SUCCESS; if (!__ymd || !__hmsm) return OPAL_PARAMETER; switch (time_status) { case idle: rc = get_date_time_req(); if (rc) return OPAL_HARDWARE; time_status = waiting; rc = OPAL_BUSY_EVENT; break; case waiting: rc = OPAL_BUSY_EVENT; break; case updated: rtc_cache_get_datetime(&ymd, &hmsm); *__ymd = cpu_to_be32(ymd); *__hmsm = cpu_to_be64(hmsm); time_status = idle; rc = OPAL_SUCCESS; break; case error: time_status = idle; rc = OPAL_HARDWARE; break; } return rc; } /* * Receive the PLDM SetBiosDateTime response */ static void set_date_time_req_complete(struct pldm_rx_data *rx, void *data __unused) { uint8_t completion_code; size_t payload_len; int rc; if (rx == NULL) { prlog(PR_ERR, "%s: Response not received\n", __func__); return; } /* Decode the message */ payload_len = rx->msg_len - sizeof(struct pldm_msg_hdr); rc = decode_set_date_time_resp(rx->msg, payload_len, &completion_code); if (rc != PLDM_SUCCESS || (completion_code > PLDM_ERROR)) { /* FIXME: Time value from OPAL_RTC_WRITE is never correct */ prlog(PR_ERR, "Decode SetBiosDateTimeReq Error, rc: %d, cc: %d\n", rc, completion_code); } } /* * Send a PLDM SetBiosDateTime request message */ static int set_date_time_req(struct tm *tm) { size_t data_size = PLDM_MSG_SIZE(struct pldm_set_date_time_req); struct pldm_tx_data *tx = NULL; int rc; /* Encode the date time request */ tx = zalloc(sizeof(struct pldm_tx_data) + data_size); if (!tx) return OPAL_NO_MEM; tx->data_size = data_size; rc = encode_set_date_time_req( DEFAULT_INSTANCE_ID, tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, tm->tm_mon, tm->tm_year, (struct pldm_msg *)tx->data, sizeof(struct pldm_set_date_time_req)); if (rc != PLDM_SUCCESS) { prlog(PR_ERR, "Encode SetBiosDateTimeReq Error, rc: %d\n", rc); free(tx); return OPAL_PARAMETER; } /* Queue and get the response message bytes */ rc = pldm_requester_queue(tx, set_date_time_req_complete, NULL); if (rc) { prlog(PR_ERR, "Communication Error, req: SetBiosDateTimeReq, rc: %d\n", rc); free(tx); return rc; } free(tx); return OPAL_SUCCESS; } static int64_t pldm_opal_rtc_write(uint32_t year_month_day, uint64_t hour_minute_second_millisecond) { struct tm tm; int rc; datetime_to_tm(year_month_day, hour_minute_second_millisecond, &tm); rc = set_date_time_req(&tm); if (rc == OPAL_BUSY) return OPAL_BUSY; return OPAL_SUCCESS; } void pldm_rtc_init(void) { struct dt_node *np = dt_new(opal_node, "rtc"); dt_add_property_strings(np, "compatible", "ibm,opal-rtc"); opal_register(OPAL_RTC_READ, pldm_opal_rtc_read, 2); opal_register(OPAL_RTC_WRITE, pldm_opal_rtc_write, 2); /* Initialise the rtc cache */ get_date_time_req(); }