The Pedigree Project  0.1
Rtc.cc
1 /*
2  * Copyright (c) 2008-2014, Pedigree Developers
3  *
4  * Please see the CONTRIB file in the root of the source tree for a full
5  * list of contributors.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include "Rtc.h"
21 #include "pedigree/kernel/LockGuard.h"
22 #include "pedigree/kernel/Log.h"
23 #include "pedigree/kernel/compiler.h"
24 #include "pedigree/kernel/core/SlamAllocator.h"
25 #include "pedigree/kernel/machine/IrqManager.h"
26 #include "pedigree/kernel/machine/Machine.h"
27 #include "pedigree/kernel/machine/Serial.h"
28 #include "pedigree/kernel/machine/TimerHandler.h"
29 #include "pedigree/kernel/process/Process.h"
30 #include "pedigree/kernel/process/Scheduler.h"
31 #include "pedigree/kernel/process/Thread.h"
32 #include "pedigree/kernel/processor/Processor.h"
33 #include "pedigree/kernel/processor/ProcessorInformation.h"
34 #include "pedigree/kernel/time/Time.h"
35 #include "pedigree/kernel/utilities/Iterator.h"
36 #include "pedigree/kernel/utilities/StaticString.h"
37 #include "pedigree/kernel/utilities/utility.h"
38 
39 class Event;
40 
41 // RTC frequency to set at startup - tradeoff between precision of timers
42 // against constant RTC noise.
44 #ifdef BOCHS
45 #define INITIAL_RTC_HZ 64
46 #else
47 #define INITIAL_RTC_HZ 512
48 #endif
49 #define BCD_TO_BIN8(x) (((((x) &0xF0) >> 4) * 10) + ((x) &0x0F))
50 #define BIN_TO_BCD8(x) ((((x) / 10) * 16) + ((x) % 10))
51 
53  {4, 0x0e, {250000000ULL, 250000000ULL}},
54  {8, 0x0d, {125000000ULL, 125000000ULL}},
55  {16, 0x0c, {62500000ULL, 62500000ULL}},
56  {32, 0x0b, {31250000ULL, 31250000ULL}},
57  {64, 0x0a, {15625000ULL, 15625000ULL}},
58  {128, 0x09, {7812500ULL, 7812500ULL}},
59  {256, 0x08, {3906250ULL, 3906250ULL}},
60  {512, 0x07, {1953125ULL, 1953125ULL}},
61  {1024, 0x06, {976562ULL, 976563ULL}},
62  {2048, 0x05, {488281ULL, 488281ULL}},
63  {4096, 0x04, {244140ULL, 244141ULL}},
64  {8192, 0x03, {122070ULL, 122070ULL}},
65 };
66 
67 static uint8_t daysPerMonth[] = {31, 28, 31, 30, 31, 30,
68  31, 31, 30, 31, 30, 31};
69 
71 
72 void Rtc::addAlarm(Event *pEvent, size_t alarmSecs, size_t alarmUsecs)
73 {
75 
76  // Figure out when to trigger the alarm.
77  uint64_t target = m_TickCount;
78  uint64_t delta = alarmSecs * Time::Multiplier::Second;
79  delta += alarmUsecs * Time::Multiplier::Microsecond;
80  target += delta;
81  Alarm *pAlarm =
82  new Alarm(pEvent, target, Processor::information().getCurrentThread());
83  m_Alarms.pushBack(pAlarm);
84 }
85 
86 void Rtc::removeAlarm(Event *pEvent)
87 {
89 
90  for (List<Alarm *>::Iterator it = m_Alarms.begin(); it != m_Alarms.end();)
91  {
92  if ((*it)->m_pEvent == pEvent)
93  {
94  Alarm *pAlarm = *it;
95  it = m_Alarms.erase(it);
96  delete pAlarm;
97  }
98  else
99  {
100  ++it;
101  }
102  }
103 }
104 
105 size_t Rtc::removeAlarm(class Event *pEvent, bool bRetZero)
106 {
108 
109  size_t currTime = getTickCount();
110 
111  for (List<Alarm *>::Iterator it = m_Alarms.begin(); it != m_Alarms.end();
112  it++)
113  {
114  if ((*it)->m_pEvent == pEvent)
115  {
116  size_t ret = 0;
117  if (!bRetZero)
118  {
119  size_t alarmEndTime = (*it)->m_Time;
120 
121  // Is it later than the end of the alarm?
122  if (alarmEndTime < currTime)
123  ret = 0;
124  else
125  {
127  size_t diff = alarmEndTime - currTime;
128  ret = (diff / 1000) + 1;
129  }
130  }
131 
132  Alarm *pAlarm = *it;
133  m_Alarms.erase(it);
134  delete pAlarm;
135  return ret;
136  }
137  }
138 
139  return 0;
140 }
141 
142 bool Rtc::registerHandler(TimerHandler *handler)
143 {
144  // find a spare spot and install
145  size_t nHandler;
146  for (nHandler = 0; nHandler < MAX_TIMER_HANDLERS; nHandler++)
147  {
148  if (m_Handlers[nHandler] == 0)
149  {
150  m_Handlers[nHandler] = handler;
151  return true;
152  }
153  }
154 
155  // no room!
156  return false;
157 }
158 bool Rtc::unregisterHandler(TimerHandler *handler)
159 {
160  // find a spare spot and install
161  size_t nHandler;
162  for (nHandler = 0; nHandler < MAX_TIMER_HANDLERS; nHandler++)
163  {
164  if (m_Handlers[nHandler] == handler)
165  {
166  m_Handlers[nHandler] = 0;
167  return true;
168  }
169  }
170 
171  // not found
172  return false;
173 }
174 size_t Rtc::getYear()
175 {
176  return m_Year;
177 }
178 uint8_t Rtc::getMonth()
179 {
180  return m_Month;
181 }
183 {
184  return m_DayOfMonth;
185 }
187 {
188  static size_t monthnumbers[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5};
189 
190  // Calculate day of week
191  uint8_t dayOfWeek = m_DayOfMonth % 7;
192  dayOfWeek += monthnumbers[m_Month - 1];
193  dayOfWeek += ((m_Year % 100) + ((m_Year % 100) / 4)) % 7;
194  dayOfWeek -= ((m_Year / 100) % 4 - 3) * 2;
195  if (m_Month < 3)
196  dayOfWeek--;
197  dayOfWeek %= 7;
198 
199  return dayOfWeek;
200 }
201 uint8_t Rtc::getHour()
202 {
203  return m_Hour;
204 }
205 uint8_t Rtc::getMinute()
206 {
207  return m_Minute;
208 }
209 uint8_t Rtc::getSecond()
210 {
211  return m_Second;
212 }
214 {
215  return m_Nanosecond;
216 }
218 {
219  return getTickCountNano() / 1000ULL;
220 }
222 {
223  uint32_t edx, eax;
224  asm volatile("rdtsc" : "=d"(edx), "=a"(eax)::"memory");
225 
226  uint64_t tsc;
227  tsc = (static_cast<uint64_t>(edx) << 32UL) | eax;
228 
229  // calculate # ns since startup
230  uint64_t ns = (tsc - m_Tsc0) / m_TscTicksPerNanosecond;
231 
232  return ns;
233 }
235 {
236  NOTICE("Rtc::initialise1");
237 
238  // Allocate the I/O port range"CMOS"
239  if (m_IoPort.allocate(0x70, 2) == false)
240  return false;
241 
242  // No IRQ yet.
243  m_IrqId = 0;
244 
245  // initialise handlers
246  ByteSet(m_Handlers, 0, sizeof(TimerHandler *) * MAX_TIMER_HANDLERS);
247 
248  // Are the RTC values in the CMOS encoded in BCD (or binary)?
249  m_bBCD = (read(0x0B) & 0x04) != 0x04;
250 
251  // Read the time and date
252  if (m_bBCD == true)
253  {
254  m_Second = BCD_TO_BIN8(read(0x00));
255  m_Minute = BCD_TO_BIN8(read(0x02));
256  m_Hour = BCD_TO_BIN8(read(0x04));
257  m_DayOfMonth = BCD_TO_BIN8(read(0x07));
258  m_Month = BCD_TO_BIN8(read(0x08));
259  m_Year = BCD_TO_BIN8(read(0x32)) * 100 + BCD_TO_BIN8(read(0x09));
260  }
261  else
262  {
263  m_Second = read(0x00);
264  m_Minute = read(0x02);
265  m_Hour = read(0x04);
266  m_DayOfMonth = read(0x07);
267  m_Month = read(0x08);
268  m_Year = read(0x32) * 100 + read(0x09);
269  }
270 
271  // Find the initial rtc rate
272  uint8_t rateBits = 0x06;
273  for (size_t i = 0; i < 12; i++)
274  if (periodicIrqInfo[i].Hz == INITIAL_RTC_HZ)
275  {
277  rateBits = periodicIrqInfo[i].rateBits;
278  break;
279  }
280 
281  // Set the Rate for the periodic IRQ
282  uint8_t tmp = read(0x0A);
283  write(0x0A, (tmp & 0xF0) | rateBits);
284 
285  return true;
286 }
287 
289 {
290  NOTICE("Rtc::initialise2");
291 
292  // Register the irq
293  IrqManager &irqManager = *Machine::instance().getIrqManager();
294  m_IrqId = irqManager.registerIsaIrqHandler(8, this);
295  if (m_IrqId == 0)
296  return false;
297 
298  // Activate the IRQ
299  uint8_t statusb = read(0x0B);
300  write(0x0B, statusb | 0x40);
301  read(0x0C); // Some RTC chips need the interrupt status to be cleared after
302  // changing the control register.
303 
304  bool wasInterrupts = Processor::getInterrupts();
306 
307  // Calibrate against TSC (assumes constant TSC - need to check CPUID!)
308  uint64_t tsc0, tsc1;
309 
310  uint32_t edx, eax;
311  asm volatile("rdtsc" : "=d"(edx), "=a"(eax)::"memory");
312  tsc0 = (static_cast<uint64_t>(edx) << 32UL) | eax;
313 
314  m_TickCount = 0;
315 
316  // Burn some cycles.
317  for (size_t i = 0; i < 50; ++i)
318  {
320  }
321 
322  asm volatile("rdtsc" : "=d"(edx), "=a"(eax)::"memory");
323  tsc1 = (static_cast<uint64_t>(edx) << 32UL) | eax;
324 
325  uint64_t diff = tsc1 - tsc0;
327  NOTICE("TSC ticks/ns: " << m_TscTicksPerNanosecond);
328 
329  m_Tsc0 = tsc1;
330 
331  Processor::setInterrupts(wasInterrupts);
332 
333  return true;
334 }
335 
336 void Rtc::synchronise(bool tohw)
337 {
338  enableRtcUpdates(false);
339 
340  if (tohw)
341  {
342  // Write the time and date back
343  if (m_bBCD == true)
344  {
345  write(0x00, BIN_TO_BCD8(m_Second));
346  write(0x02, BIN_TO_BCD8(m_Minute));
347  write(0x04, BIN_TO_BCD8(m_Hour));
348  write(0x07, BIN_TO_BCD8(m_DayOfMonth));
349  write(0x08, BIN_TO_BCD8(m_Month));
350  write(0x09, BIN_TO_BCD8(m_Year % 100));
351  write(0x32, BIN_TO_BCD8(m_Year / 100));
352  }
353  else
354  {
355  write(0x00, m_Second);
356  write(0x02, m_Minute);
357  write(0x04, m_Hour);
358  write(0x07, m_DayOfMonth);
359  write(0x08, m_Month);
360  write(0x09, m_Year % 100);
361  write(0x32, m_Year / 100);
362  }
363  }
364  else
365  {
366  // Read the time and date
367  if (m_bBCD == true)
368  {
369  m_Second = BCD_TO_BIN8(read(0x00));
370  m_Minute = BCD_TO_BIN8(read(0x02));
371  m_Hour = BCD_TO_BIN8(read(0x04));
372  m_DayOfMonth = BCD_TO_BIN8(read(0x07));
373  m_Month = BCD_TO_BIN8(read(0x08));
374  m_Year = BCD_TO_BIN8(read(0x32)) * 100 + BCD_TO_BIN8(read(0x09));
375  }
376  else
377  {
378  m_Second = read(0x00);
379  m_Minute = read(0x02);
380  m_Hour = read(0x04);
381  m_DayOfMonth = read(0x07);
382  m_Month = read(0x08);
383  m_Year = read(0x32) * 100 + read(0x09);
384  }
385  }
386 
387  enableRtcUpdates(true);
388 }
390 {
391  // Deactivate the IRQ
392  uint8_t statusb = read(0x0B);
393  write(0x0B, statusb & ~0x40);
394 
395  synchronise(true);
396 
397  // Unregister the irq
398  IrqManager &irqManager = *Machine::instance().getIrqManager();
399  irqManager.unregisterHandler(m_IrqId, this);
400 
401  // Free the I/O port range
402  m_IoPort.free();
403 }
404 
406  : m_IoPort("CMOS"), m_IrqId(0), m_PeriodicIrqInfoIndex(0), m_bBCD(true),
407  m_Year(1970), m_Month(0), m_DayOfMonth(0), m_Hour(0), m_Minute(0),
409 {
410 }
411 
412 extern size_t g_FreePages;
413 extern size_t g_AllocedPages;
414 
415 bool Rtc::irq(irq_id_t number, InterruptState &state)
416 {
417  static size_t index = 0;
418  // Update the Tick Count
419  uint64_t delta = periodicIrqInfo[m_PeriodicIrqInfoIndex].ns[index];
420  index = (index == 0) ? 1 : 0;
421  uint64_t prevTickCount = m_TickCount;
422  m_TickCount += delta;
423  if (m_TickCount < prevTickCount)
424  {
425  WARNING("RTC: rolled over.");
427  }
428 
429  // Calculate the new time/date
430  m_Nanosecond += delta;
431 
432  // Check for alarms.
433  {
435  while (true)
436  {
437  bool bDispatched = false;
438  for (List<Alarm *>::Iterator it = m_Alarms.begin();
439  it != m_Alarms.end(); it++)
440  {
441  Alarm *pA = *it;
442  if (pA->m_Time <= m_TickCount)
443  {
444  pA->m_pThread->sendEvent(pA->m_pEvent);
445  m_Alarms.erase(it);
446  bDispatched = true;
447  delete pA;
448  break;
449  }
450  }
451  if (!bDispatched)
452  break;
453  }
454  }
455 
456  if (UNLIKELY(m_Nanosecond >= Time::Multiplier::Millisecond))
457  {
458  // Every millisecond, unblock any interrupts which were halted and halt
459  // any which need to be halted.
460  Machine::instance().getIrqManager()->tick();
461  }
462 
463  if (UNLIKELY(m_Nanosecond >= Time::Multiplier::Second))
464  {
465  ++m_Second;
466  m_Nanosecond -= Time::Multiplier::Second;
467 
468 #ifdef MEMORY_LOGGING_ENABLED
469  Serial *pSerial = Machine::instance().getSerial(1);
470  NormalStaticString memoryLogStr;
471  memoryLogStr += "Heap: ";
472  memoryLogStr += SlamAllocator::instance().heapPageCount() * 4;
473  memoryLogStr += "K\tPages: ";
474  memoryLogStr += (g_AllocedPages * 4096) / 1024;
475  memoryLogStr += "K\t Free: ";
476  memoryLogStr += (g_FreePages * 4096) / 1024;
477  memoryLogStr += "K\n";
478 
479  pSerial->write(memoryLogStr);
480 
481  // Memory snapshot of current processes.
482  for (size_t i = 0; i < Scheduler::instance().getNumProcesses(); ++i)
483  {
484  Process *pProcess = Scheduler::instance().getProcess(i);
485  LargeStaticString processListStr;
486 
487  ssize_t heapK = pProcess->getHeapUsage() / 1024;
488  ssize_t virtK = (pProcess->getVirtualPageCount() * 0x1000) / 1024;
489  ssize_t physK = (pProcess->getPhysicalPageCount() * 0x1000) / 1024;
490  ssize_t shrK = (pProcess->getSharedPageCount() * 0x1000) / 1024;
491 
492  processListStr.append("\tProcess #");
493  processListStr.append(pProcess->getId(), 10);
494  processListStr.append(" '");
495  processListStr.append(pProcess->description());
496  processListStr.append("' V=");
497  processListStr.append(virtK, 10);
498  processListStr.append("K P=");
499  processListStr.append(physK, 10);
500  processListStr.append("K S=");
501  processListStr.append(shrK, 10);
502  processListStr.append("K Heap=");
503  processListStr.append(heapK, 10);
504  processListStr.append("K\n");
505  pSerial->write(processListStr);
506  }
507 #endif
508 
509  if (UNLIKELY(m_Second == 60))
510  {
511  ++m_Minute;
512  m_Second = 0;
513 
514  if (UNLIKELY(m_Minute == 60))
515  {
516  ++m_Hour;
517  m_Minute = 0;
518 
519  if (UNLIKELY(m_Hour == 24))
520  {
521  ++m_DayOfMonth;
522  m_Hour = 0;
523 
524  // Are we in a leap year
525  bool isLeap = ((m_Year % 4) == 0) & (((m_Year % 100) != 0) |
526  ((m_Year % 400) == 0));
527 
528  if (UNLIKELY(
529  ((m_DayOfMonth > daysPerMonth[m_Month - 1]) &&
530  ((m_Month != 2) || isLeap == false)) ||
531  (m_DayOfMonth > (daysPerMonth[m_Month - 1] + 1))))
532  {
533  ++m_Month;
534  m_DayOfMonth = 1;
535 
536  if (UNLIKELY(m_Month > 12))
537  {
538  ++m_Year;
539  m_Month = 1;
540  }
541  }
542  }
543  }
544  }
545  }
546 
547  // Acknowledging the IRQ (within the CMOS)
548  read(0x0C);
549 
550  // call handlers
551  for (size_t nHandler = 0; nHandler < MAX_TIMER_HANDLERS; nHandler++)
552  {
553  // timer delta is in nanoseconds
554  if (m_Handlers[nHandler])
555  m_Handlers[nHandler]->timer(delta, state);
556  }
557 
558  return true;
559 }
560 
561 void Rtc::setIndex(uint8_t index)
562 {
563  uint8_t idx = m_IoPort.read8(0);
564  m_IoPort.write8((idx & 0x80) | (index & 0x7F), 0);
565 }
566 void Rtc::waitForUpdateCompletion(uint8_t index)
567 {
568  if (index <= 9 || index == 50)
569  {
570  setIndex(0x0A);
571  while ((m_IoPort.read8(1) & 0x80) == 0x80)
572  ;
573  }
574 }
575 void Rtc::enableRtcUpdates(bool enable)
576 {
577  // Write the index
578  setIndex(0x0B);
579 
580  // Update the status register
581  uint8_t statusA = m_IoPort.read8(1);
582  m_IoPort.write8((statusA & 0x80) | (enable ? 0 : (1 << 7)), 1);
583 }
584 uint8_t Rtc::read(uint8_t index)
585 {
586  // Wait until the RTC Update is completed
588 
589  // Write the index
590  setIndex(index);
591 
592  // Read the data at that index
593  return m_IoPort.read8(1);
594 }
595 void Rtc::write(uint8_t index, uint8_t value)
596 {
597  // Wait until the RTC Update is completed
599 
600  // Write the index
601  setIndex(index);
602 
603  // Write the data to that index
604  m_IoPort.write8(value, 1);
605 }
uint8_t m_DayOfMonth
Definition: Rtc.h:138
virtual void addAlarm(class Event *pEvent, size_t alarmSecs, size_t alarmUsecs=0)
Definition: Rtc.cc:72
IoPort m_IoPort
Definition: Rtc.h:122
uint64_t m_Nanosecond
Definition: Rtc.h:146
void write(uint8_t index, uint8_t value)
Definition: Rtc.cc:595
uint64_t m_TscTicksPerNanosecond
Definition: Rtc.h:194
virtual Serial * getSerial(size_t n)=0
void waitForUpdateCompletion(uint8_t index)
Definition: Rtc.cc:566
static bool getInterrupts()
ssize_t getHeapUsage() const
Definition: Process.h:372
size_t m_Year
Definition: Rtc.h:134
virtual uint8_t getHour()
Definition: Rtc.cc:201
size_t getId()
Definition: Process.h:108
virtual uint8_t getDayOfWeek()
Definition: Rtc.cc:186
uint8_t rateBits
Definition: Rtc.h:157
virtual uint64_t getTickCount()
Definition: Rtc.cc:217
virtual void synchronise(bool tohw=false)
Definition: Rtc.cc:336
uint8_t m_Second
Definition: Rtc.h:144
void uninitialise()
Definition: Rtc.cc:389
virtual uint8_t read8(size_t offset=0)
static ProcessorInformation & information()
Definition: Processor.cc:45
void enableRtcUpdates(bool enable)
Definition: Rtc.cc:575
virtual size_t getYear()
Definition: Rtc.cc:174
bool initialise1() INITIALISATION_ONLY
Definition: Rtc.cc:234
void free()
Definition: IoPort.cc:49
void setIndex(uint8_t index)
Definition: Rtc.cc:561
#define WARNING(text)
Definition: Log.h:78
bool sendEvent(Event *pEvent)
Definition: Thread.cc:529
static periodicIrqInfo_t periodicIrqInfo[12]
Definition: Rtc.h:163
virtual uint64_t getNanosecond()
Definition: Rtc.cc:213
#define NOTICE(text)
Definition: Log.h:74
virtual uint8_t getMinute()
Definition: Rtc.cc:205
::Iterator< T, node_t > Iterator
Definition: List.h:71
bool m_bBCD
Definition: Rtc.h:131
virtual uint8_t getDayOfMonth()
Definition: Rtc.cc:182
uint64_t ns[2]
Definition: Rtc.h:159
irq_id_t m_IrqId
Definition: Rtc.h:125
size_t getNumProcesses()
Definition: Scheduler.cc:140
virtual void unregisterHandler(irq_id_t Id, IrqHandler *handler)=0
static Scheduler & instance()
Definition: Scheduler.h:48
bool allocate(io_port_t ioPort, size_t size)
Definition: IoPort.cc:34
static void haltUntilInterrupt()
Process * getProcess(size_t n)
Definition: Scheduler.cc:149
virtual void timer(uint64_t delta, InterruptState &state)=0
static void setInterrupts(bool bEnable)
size_t m_PeriodicIrqInfoIndex
Definition: Rtc.h:128
virtual bool irq(irq_id_t number, InterruptState &state)
Definition: Rtc.cc:415
virtual uint8_t getSecond()
Definition: Rtc.cc:209
virtual uint8_t getMonth()
Definition: Rtc.cc:178
uint64_t m_TickCount
Definition: Rtc.h:149
TimerHandler * m_Handlers[MAX_TIMER_HANDLERS]
Definition: Rtc.h:169
Definition: Event.h:48
Spinlock m_Lock
Definition: Rtc.h:191
virtual irq_id_t registerIsaIrqHandler(uint8_t irq, IrqHandler *handler, bool bEdge=false)=0
List< Alarm * > m_Alarms
Definition: Rtc.h:189
LargeStaticString & description()
Definition: Process.h:114
Rtc() INITIALISATION_ONLY
Definition: Rtc.cc:405
bool initialise2() INITIALISATION_ONLY
Definition: Rtc.cc:288
static Rtc m_Instance
Definition: Rtc.h:166
virtual void removeAlarm(class Event *pEvent)
Definition: Rtc.cc:86
uint8_t m_Hour
Definition: Rtc.h:140
uint64_t m_Tsc0
Definition: Rtc.h:197
virtual void tick()
Definition: IrqManager.cc:25
Definition: Rtc.h:42
uint8_t read(uint8_t index)
Definition: Rtc.cc:584
uint8_t m_Month
Definition: Rtc.h:136
uint8_t m_Minute
Definition: Rtc.h:142
virtual void write8(uint8_t value, size_t offset=0)
virtual uint64_t getTickCountNano()
Definition: Rtc.cc:221