blob: 16797999da5f41574d5111923b998ad218aece66 [file] [log] [blame]
// 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();
}