diff options
Diffstat (limited to 'hw/fsp/fsp-rtc.c')
-rw-r--r-- | hw/fsp/fsp-rtc.c | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/hw/fsp/fsp-rtc.c b/hw/fsp/fsp-rtc.c new file mode 100644 index 0000000..887091a --- /dev/null +++ b/hw/fsp/fsp-rtc.c @@ -0,0 +1,572 @@ +/* Copyright 2013-2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <skiboot.h> +#include <fsp.h> +#include <lock.h> +#include <timebase.h> +#include <time.h> +#include <fsp-elog.h> + +//#define DBG(fmt...) printf("RTC: " fmt) +#define DBG(fmt...) do { } while(0) + +/* + * Note on how those operate: + * + * Because the RTC calls can be pretty slow, these functions will shoot + * an asynchronous request to the FSP (if none is already pending) + * + * The requests will return OPAL_BUSY_EVENT as long as the event has + * not been completed. + * + * WARNING: An attempt at doing an RTC write while one is already pending + * will simply ignore the new arguments and continue returning + * OPAL_BUSY_EVENT. This is to be compatible with existing Linux code. + * + * Completion of the request will result in an event OPAL_EVENT_RTC + * being signaled, which will remain raised until a corresponding call + * to opal_rtc_read() or opal_rtc_write() finally returns OPAL_SUCCESS, + * at which point the operation is complete and the event cleared. + * + * If we end up taking longer than rtc_read_timeout_ms millieconds waiting + * for the response from a read request, we simply return a cached value (plus + * an offset calculated from the timebase. When the read request finally + * returns, we update our cache value accordingly. + * + * There is two separate set of state for reads and writes. If both are + * attempted at the same time, the event bit will remain set as long as either + * of the two has a pending event to signal. + */ + +enum { + RTC_TOD_VALID, + RTC_TOD_INVALID, + RTC_TOD_PERMANENT_ERROR, +} rtc_tod_state = RTC_TOD_INVALID; + +static struct lock rtc_lock; +static struct fsp_msg *rtc_read_msg; +static struct fsp_msg *rtc_write_msg; +/* TODO We'd probably want to export and use this variable declared in fsp.c, + * instead of each component individually maintaining the state.. may be for + * later optimization + */ +static bool fsp_in_reset = false; + +/* last synchonisation point */ +static struct { + struct tm tm; + unsigned long tb; + bool dirty; +} rtc_tod_cache; + +/* Timebase value when we last initiated a RTC read request */ +static unsigned long read_req_tb; + +/* If a RTC read takes longer than this, we return a value generated + * from the cache + timebase */ +static const int rtc_read_timeout_ms = 1500; + +DEFINE_LOG_ENTRY(OPAL_RC_RTC_TOD, OPAL_PLATFORM_ERR_EVT, OPAL_RTC, + OPAL_PLATFORM_FIRMWARE, OPAL_INFO, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_RTC_READ, OPAL_PLATFORM_ERR_EVT, OPAL_RTC, + OPAL_PLATFORM_FIRMWARE, OPAL_INFO, + OPAL_NA, NULL); + +static int days_in_month(int month, int year) +{ + static int month_days[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + + assert(1 <= month && month <= 12); + + /* we may need to update this in the year 4000, pending a + * decision on whether or not it's a leap year */ + if (month == 2) { + bool is_leap = !(year % 400) || ((year % 100) && !(year % 4)); + return is_leap ? 29 : 28; + } + + return month_days[month - 1]; +} + +static void tm_add(struct tm *in, struct tm *out, unsigned long secs) +{ + unsigned long year, month, mday, hour, minute, second, d; + static const unsigned long sec_in_400_years = + ((3903ul * 365) + (97 * 366)) * 24 * 60 * 60; + + assert(in); + assert(out); + + second = in->tm_sec; + minute = in->tm_min; + hour = in->tm_hour; + mday = in->tm_mday; + month = in->tm_mon; + year = in->tm_year; + + second += secs; + + /* There are the same number of seconds in any 400-year block; this + * limits the iterations in the loop below */ + year += 400 * (second / sec_in_400_years); + second = second % sec_in_400_years; + + if (second >= 60) { + minute += second / 60; + second = second % 60; + } + + if (minute >= 60) { + hour += minute / 60; + minute = minute % 60; + } + + if (hour >= 24) { + mday += hour / 24; + hour = hour % 24; + } + + for (d = days_in_month(month, year); mday >= d; + d = days_in_month(month, year)) { + month++; + if (month > 12) { + month = 1; + year++; + } + mday -= d; + } + + out->tm_year = year; + out->tm_mon = month; + out->tm_mday = mday; + out->tm_hour = hour; + out->tm_min = minute; + out->tm_sec = second; +} + +/* MSB is byte 3, LSB is byte 0 */ +static unsigned int bcd_byte(uint32_t bcd, int byteno) +{ + bcd >>= byteno * 8; + return (bcd >> 4 & 0xf) * 10 + (bcd & 0xf); +} + +static uint32_t int_to_bcd2(unsigned int x) +{ + return (((x / 10) << 4) & 0xf0) | (x % 10); +} + +static uint32_t int_to_bcd4(unsigned int x) +{ + return int_to_bcd2(x / 100) << 8 | int_to_bcd2(x % 100); +} + +static void rtc_to_tm(struct fsp_msg *msg, struct tm *tm) +{ + uint32_t x; + + /* The FSP returns in BCD: + * + * | year | month | mday | + * +------------------------------------+ + * | hour | minute | secs | reserved | + * +------------------------------------+ + * | microseconds | + */ + x = msg->data.words[0]; + tm->tm_year = bcd_byte(x, 3) * 100 + bcd_byte(x, 2); + tm->tm_mon = bcd_byte(x, 1); + tm->tm_mday = bcd_byte(x, 0); + + x = msg->data.words[1]; + tm->tm_hour = bcd_byte(x, 3); + tm->tm_min = bcd_byte(x, 2); + tm->tm_sec = bcd_byte(x, 1); +} + +static void tm_to_datetime(struct tm *tm, uint32_t *y_m_d, uint64_t *h_m_s_m) +{ + uint64_t h_m_s; + /* + * The OPAL API is defined as returned a u64 of a similar + * format to the FSP message; the 32-bit date field is + * in the format: + * + * | year | year | month | day | + * + */ + *y_m_d = int_to_bcd4(tm->tm_year) << 16 | + int_to_bcd2(tm->tm_mon) << 8 | + int_to_bcd2(tm->tm_mday); + + /* + * ... and the 64-bit time field is in the format + * + * | hour | minutes | secs | millisec | + * | ------------------------------------- + * | millisec | reserved | + * + * We simply ignore the microseconds/milliseconds for now + * as I don't quite understand why the OPAL API defines that + * it needs 6 digits for the milliseconds :-) I suspect the + * doc got that wrong and it's supposed to be micro but + * let's ignore it. + * + * Note that Linux doesn't use nor set the ms field anyway. + */ + h_m_s = int_to_bcd2(tm->tm_hour) << 24 | + int_to_bcd2(tm->tm_min) << 16 | + int_to_bcd2(tm->tm_sec) << 8; + + *h_m_s_m = h_m_s << 32; +} + +static void fsp_rtc_process_read(struct fsp_msg *read_resp) +{ + int val = (read_resp->word1 >> 8) & 0xff; + + switch (val) { + case 0xa9: + log_simple_error(&e_info(OPAL_RC_RTC_TOD), + "RTC TOD in invalid state\n"); + rtc_tod_state = RTC_TOD_INVALID; + break; + + case 0xaf: + log_simple_error(&e_info(OPAL_RC_RTC_TOD), + "RTC TOD in permanent error state\n"); + rtc_tod_state = RTC_TOD_PERMANENT_ERROR; + break; + + case 0: + /* Save the read RTC value in our cache */ + rtc_to_tm(read_resp, &rtc_tod_cache.tm); + rtc_tod_cache.tb = mftb(); + rtc_tod_state = RTC_TOD_VALID; + break; + + default: + log_simple_error(&e_info(OPAL_RC_RTC_TOD), + "RTC TOD read failed: %d\n", val); + rtc_tod_state = RTC_TOD_INVALID; + } +} + +static void opal_rtc_eval_events(void) +{ + bool pending = false; + + if (rtc_read_msg && !fsp_msg_busy(rtc_read_msg)) + pending = true; + if (rtc_write_msg && !fsp_msg_busy(rtc_write_msg)) + pending = true; + opal_update_pending_evt(OPAL_EVENT_RTC, pending ? OPAL_EVENT_RTC : 0); +} + +static void fsp_rtc_req_complete(struct fsp_msg *msg) +{ + lock(&rtc_lock); + DBG("RTC completion %p\n", msg); + if (msg == rtc_read_msg) + fsp_rtc_process_read(msg->resp); + opal_rtc_eval_events(); + unlock(&rtc_lock); +} + +static int64_t fsp_rtc_send_read_request(void) +{ + struct fsp_msg *msg; + int rc; + + msg = fsp_mkmsg(FSP_CMD_READ_TOD, 0); + if (!msg) { + log_simple_error(&e_info(OPAL_RC_RTC_READ), + "RTC: failed to allocate read message\n"); + return OPAL_INTERNAL_ERROR; + } + + rc = fsp_queue_msg(msg, fsp_rtc_req_complete); + if (rc) { + fsp_freemsg(msg); + log_simple_error(&e_info(OPAL_RC_RTC_READ), + "RTC: failed to queue read message: %d\n", rc); + return OPAL_INTERNAL_ERROR; + } + + read_req_tb = mftb(); + rtc_read_msg = msg; + + return OPAL_BUSY_EVENT; +} + +static void encode_cached_tod(uint32_t *year_month_day, + uint64_t *hour_minute_second_millisecond) +{ + unsigned long cache_age_sec; + struct tm tm; + + cache_age_sec = tb_to_msecs(mftb() - rtc_tod_cache.tb) / 1000; + + tm_add(&rtc_tod_cache.tm, &tm, cache_age_sec); + + /* Format to OPAL API values */ + tm_to_datetime(&tm, year_month_day, hour_minute_second_millisecond); +} + +int fsp_rtc_get_cached_tod(uint32_t *year_month_day, + uint64_t *hour_minute_second_millisecond) +{ + + if (rtc_tod_state != RTC_TOD_VALID) + return -1; + + encode_cached_tod(year_month_day, + hour_minute_second_millisecond); + return 0; +} + +static int64_t fsp_opal_rtc_read(uint32_t *year_month_day, + uint64_t *hour_minute_second_millisecond) +{ + struct fsp_msg *msg; + int64_t rc; + + if (!year_month_day || !hour_minute_second_millisecond) + return OPAL_PARAMETER; + + lock(&rtc_lock); + /* During R/R of FSP, read cached TOD */ + if (fsp_in_reset) { + fsp_rtc_get_cached_tod(year_month_day, + hour_minute_second_millisecond); + rc = OPAL_SUCCESS; + goto out; + } + + msg = rtc_read_msg; + + if (rtc_tod_state == RTC_TOD_PERMANENT_ERROR) { + if (msg && !fsp_msg_busy(msg)) + fsp_freemsg(msg); + rc = OPAL_HARDWARE; + goto out; + } + + /* If we don't have a read pending already, fire off a request and + * return */ + if (!msg) { + DBG("Sending new RTC read request\n"); + rc = fsp_rtc_send_read_request(); + + /* If our pending read is done, clear events and return the time + * from the cache */ + } else if (!fsp_msg_busy(msg)) { + DBG("RTC read complete, state %d\n", rtc_tod_state); + + rtc_read_msg = NULL; + opal_rtc_eval_events(); + fsp_freemsg(msg); + + if (rtc_tod_state == RTC_TOD_VALID) { + encode_cached_tod(year_month_day, + hour_minute_second_millisecond); + rc = OPAL_SUCCESS; + } else + rc = OPAL_INTERNAL_ERROR; + + /* Timeout: return our cached value (updated from tb), but leave the + * read request pending so it will update the cache later */ + } else if (mftb() > read_req_tb + msecs_to_tb(rtc_read_timeout_ms)) { + DBG("RTC read timed out\n"); + + encode_cached_tod(year_month_day, + hour_minute_second_millisecond); + rc = OPAL_SUCCESS; + + /* Otherwise, we're still waiting on the read to complete */ + } else { + rc = OPAL_BUSY_EVENT; + } +out: + unlock(&rtc_lock); + return rc; +} + +static int64_t fsp_opal_rtc_write(uint32_t year_month_day, + uint64_t hour_minute_second_millisecond) +{ + struct fsp_msg *msg; + uint32_t w0, w1, w2; + int64_t rc; + + lock(&rtc_lock); + if (rtc_tod_state == RTC_TOD_PERMANENT_ERROR) { + rc = OPAL_HARDWARE; + msg = NULL; + goto bail; + } + + /* Do we have a request already ? */ + msg = rtc_write_msg; + if (msg) { + /* If it's still in progress, return */ + if (fsp_msg_busy(msg)) { + /* Don't free the message */ + msg = NULL; + rc = OPAL_BUSY_EVENT; + goto bail; + } + + DBG("Completed write request @%p, state=%d\n", msg, msg->state); + /* It's complete, clear events */ + rtc_write_msg = NULL; + opal_rtc_eval_events(); + + /* Check error state */ + if (msg->state != fsp_msg_done) { + DBG(" -> request not in done state -> error !\n"); + rc = OPAL_INTERNAL_ERROR; + goto bail; + } + rc = OPAL_SUCCESS; + goto bail; + } + + DBG("Sending new write request...\n"); + + /* Create a request and send it. Just like for read, we ignore + * the "millisecond" field which is probably supposed to be + * microseconds and which Linux ignores as well anyway + */ + w0 = year_month_day; + w1 = (hour_minute_second_millisecond >> 32) & 0xffffff00; + w2 = 0; + + rtc_write_msg = fsp_mkmsg(FSP_CMD_WRITE_TOD, 3, w0, w1, w2); + if (!rtc_write_msg) { + DBG(" -> allocation failed !\n"); + rc = OPAL_INTERNAL_ERROR; + goto bail; + } + DBG(" -> req at %p\n", rtc_write_msg); + + if (fsp_in_reset) { + rtc_to_tm(rtc_write_msg, &rtc_tod_cache.tm); + rtc_tod_cache.tb = mftb(); + rtc_tod_cache.dirty = true; + fsp_freemsg(rtc_write_msg); + rtc_write_msg = NULL; + rc = OPAL_SUCCESS; + goto bail; + } else if (fsp_queue_msg(rtc_write_msg, fsp_rtc_req_complete)) { + DBG(" -> queueing failed !\n"); + rc = OPAL_INTERNAL_ERROR; + fsp_freemsg(rtc_write_msg); + rtc_write_msg = NULL; + goto bail; + } + rc = OPAL_BUSY_EVENT; + bail: + unlock(&rtc_lock); + if (msg) + fsp_freemsg(msg); + return rc; +} + +static void rtc_flush_cached_tod(void) +{ + struct fsp_msg *msg; + uint64_t h_m_s_m; + uint32_t y_m_d; + + if (fsp_rtc_get_cached_tod(&y_m_d, &h_m_s_m)) + return; + msg = fsp_mkmsg(FSP_CMD_WRITE_TOD, 3, y_m_d, + (h_m_s_m >> 32) & 0xffffff00, 0); + if (msg) + fsp_queue_msg(msg, fsp_freemsg); +} + +static bool fsp_rtc_msg_rr(u32 cmd_sub_mod, struct fsp_msg *msg) +{ + + int rc = false; + assert(msg == NULL); + + switch (cmd_sub_mod) { + case FSP_RESET_START: + lock(&rtc_lock); + fsp_in_reset = true; + unlock(&rtc_lock); + rc = true; + break; + case FSP_RELOAD_COMPLETE: + lock(&rtc_lock); + fsp_in_reset = false; + if (rtc_tod_cache.dirty) { + rtc_flush_cached_tod(); + rtc_tod_cache.dirty = false; + } + unlock(&rtc_lock); + rc = true; + break; + } + + return rc; +} + +static struct fsp_client fsp_rtc_client_rr = { + .message = fsp_rtc_msg_rr, +}; + +void fsp_rtc_init(void) +{ + struct fsp_msg msg, resp; + int rc; + + if (!fsp_present()) { + rtc_tod_state = RTC_TOD_PERMANENT_ERROR; + return; + } + + opal_register(OPAL_RTC_READ, fsp_opal_rtc_read, 2); + opal_register(OPAL_RTC_WRITE, fsp_opal_rtc_write, 2); + + /* Register for the reset/reload event */ + fsp_register_client(&fsp_rtc_client_rr, FSP_MCLASS_RR_EVENT); + + msg.resp = &resp; + fsp_fillmsg(&msg, FSP_CMD_READ_TOD, 0); + + DBG("Getting initial RTC TOD\n"); + + lock(&rtc_lock); + + rc = fsp_sync_msg(&msg, false); + + if (rc >= 0) + fsp_rtc_process_read(&resp); + else + rtc_tod_state = RTC_TOD_PERMANENT_ERROR; + + unlock(&rtc_lock); +} |