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