1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include <string.h>
5 
6 #include <ddk/debug.h>
7 
8 #include <ddktl/device.h>
9 #include <ddktl/protocol/empty-protocol.h>
10 
11 #include <fbl/unique_ptr.h>
12 
13 #include <zircon/compiler.h>
14 
15 #include <librtc.h>
16 
17 namespace {
18 
set_utc_offset(const fuchsia_hardware_rtc_Time * rtc)19 zx_status_t set_utc_offset(const fuchsia_hardware_rtc_Time* rtc) {
20     uint64_t rtc_nanoseconds = seconds_since_epoch(rtc) * 1000000000;;
21     int64_t offset = rtc_nanoseconds - zx_clock_get_monotonic();
22     return zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
23 }
24 
25 static zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn);
26 static zx_status_t fidl_Set(void* ctx, const fuchsia_hardware_rtc_Time* rtc, fidl_txn_t* txn);
27 
28 class FallbackRtc;
29 using RtcDevice = ddk::Device<FallbackRtc, ddk::Messageable>;
30 
31 // The fallback RTC driver is a fake driver which avoids to special case
32 // in the upper layers on boards which don't have an RTC chip (and battery).
33 // it assumes that an external entity will set it to a approximately correct
34 // time based on other sources, most likely the roughtime service which
35 // runs at every boot.
36 class FallbackRtc : public RtcDevice,
37                     public ddk::EmptyProtocol<ZX_PROTOCOL_RTC> {
38   public:
FallbackRtc(zx_device_t * parent)39     FallbackRtc(zx_device_t* parent)
40         : RtcDevice(parent), rtc_last_({}) {
41         // We don't rely on the default value to be correct to any approximation
42         // but for debugging purposes is best to return a known value.
43         rtc_last_.year = 2018;
44         rtc_last_.month = 1;
45         rtc_last_.day = 1;
46     }
47 
Bind()48     zx_status_t Bind() {
49         return DdkAdd("fallback-rtc");
50     }
51 
DdkRelease()52     void DdkRelease() {
53         delete this;
54     }
55 
DdkMessage(fidl_msg_t * msg,fidl_txn_t * txn)56     zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
57         return fuchsia_hardware_rtc_Device_dispatch(this, txn, msg, &fidl_ops_);
58     }
59 
60   private:
Get(fuchsia_hardware_rtc_Time & rtc)61       zx_status_t Get(fuchsia_hardware_rtc_Time& rtc) {
62 
63           // TODO(cpu): Advance the clock. This is not strictly necessary at the
64           // moment because this driver basically serves as a rendezvous between
65           // a Internet time server and the rest of the system.
66 
67           rtc = rtc_last_;
68           return ZX_OK;
69       }
70 
Set(const fuchsia_hardware_rtc_Time & rtc)71       zx_status_t Set(const fuchsia_hardware_rtc_Time& rtc) {
72           if (rtc_is_invalid(&rtc)) {
73               return ZX_ERR_OUT_OF_RANGE;
74           }
75 
76           rtc_last_ = rtc;
77 
78           auto status = set_utc_offset(&rtc_last_);
79           if (status != ZX_OK) {
80               zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
81           }
82 
83           return ZX_OK;
84       }
85 
86     friend zx_status_t fidl_Get(void*, fidl_txn_t*);
87     friend zx_status_t fidl_Set(void*, const fuchsia_hardware_rtc_Time*, fidl_txn_t*);
88 
89     const fuchsia_hardware_rtc_Device_ops_t fidl_ops_ = {
90         .Get = fidl_Get,
91         .Set = fidl_Set,
92     };
93 
94     fuchsia_hardware_rtc_Time rtc_last_;
95 };
96 
fidl_Get(void * ctx,fidl_txn_t * txn)97 zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn) {
98     auto dev = static_cast<FallbackRtc*>(ctx);
99     fuchsia_hardware_rtc_Time rtc;
100     dev->Get(rtc);
101     return fuchsia_hardware_rtc_DeviceGet_reply(txn, &rtc);
102 }
103 
fidl_Set(void * ctx,const fuchsia_hardware_rtc_Time * rtc,fidl_txn_t * txn)104 zx_status_t fidl_Set(void* ctx, const fuchsia_hardware_rtc_Time* rtc, fidl_txn_t* txn) {
105     auto dev = static_cast<FallbackRtc*>(ctx);
106     auto status = dev->Set(*rtc);
107     return fuchsia_hardware_rtc_DeviceSet_reply(txn, status);
108 }
109 
110 }  // namespace
111 
fallback_rtc_bind(void * ctx,zx_device_t * parent)112 extern "C" zx_status_t fallback_rtc_bind(void* ctx, zx_device_t* parent) {
113     auto dev = fbl::make_unique<FallbackRtc>(parent);
114     auto status = dev->Bind();
115     if (status == ZX_OK) {
116         // devmgr is now in charge of the device, until DdkRelease().
117         __UNUSED auto ptr = dev.release();
118     }
119     return status;
120 }
121