@@ -11,7 +11,7 @@ PLDM_OBJS = pldm-mctp.o pldm-responder.o pldm-requester.o
PLDM_OBJS += pldm-base-requests.o pldm-platform-requests.o
PLDM_OBJS += pldm-bios-requests.o pldm-fru-requests.o
PLDM_OBJS += pldm-file-io-requests.o pldm-lid-files.o
-PLDM_OBJS += pldm-watchdog.o
+PLDM_OBJS += pldm-watchdog.o pldm-rtc.o
PLDM = $(PLDM_DIR)/built-in.a
$(PLDM): $(PLDM_OBJS:%=$(PLDM_DIR)/%)
new file mode 100644
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+// Copyright 2022 IBM Corp.
+
+#define pr_fmt(fmt) "PLDM: " fmt
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <time-utils.h>
+#include <device.h>
+#include <opal.h>
+#include <rtc.h>
+#include <bios.h>
+#include <utils.h>
+#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();
+}
@@ -65,4 +65,9 @@ int pldm_watchdog_init(void);
int pldm_platform_send_progress_state_change(
enum pldm_state_set_boot_progress_state_values state);
+/**
+ * Configure real-time clock
+ */
+void pldm_rtc_init(void);
+
#endif /* __PLDM_H__ */
OPAL_RTC_READ/WRITE are used to retrieve and write the time. PLDM stack provides GetBiosDateTimeReq and SetBiosDateTimeReq commands to exercise. Signed-off-by: Christophe Lombard <clombard@linux.ibm.com> --- core/pldm/Makefile.inc | 2 +- core/pldm/pldm-rtc.c | 260 +++++++++++++++++++++++++++++++++++++++++ include/pldm.h | 5 + 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 core/pldm/pldm-rtc.c