feat: extend rtc with RtcDateTime
This commit is contained in:
1 parent
8af9a09f89
commit
30188c37d0
2 files changed
+165
-1
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
#ifndef FROSTBYTE_RTC_H
|
||||||
|
#define FROSTBYTE_RTC_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CMOS real-time clock driver (MC146818-compatible, ports 0x70/0x71) --
|
||||||
|
* the first source of real wall-clock date/time this kernel has ever had.
|
||||||
|
* Every other notion of "time" in frostbyte (pit.h's tick counter,
|
||||||
|
* threadSleep, userAlarm) is relative to boot, not to the calendar; this
|
||||||
|
* is the piece that lets a process ask "what day is it" at all.
|
||||||
|
*
|
||||||
|
* Honest limitation, same spirit as syscall.c's own documented fork()-less
|
||||||
|
* design: the CMOS century register's index is not standardized (it is
|
||||||
|
* only discoverable via the ACPI FADT table, which this kernel does not
|
||||||
|
* parse), so rtcReadDateTime hardcodes the century to 20 -- correct for
|
||||||
|
* any date from 2000 through 2099, which covers every real boot this
|
||||||
|
* kernel will ever see, but not a documented general-purpose solution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct RtcDateTime {
|
||||||
|
int64 year; /* full year, e.g. 2026 */
|
||||||
|
int64 month; /* 1-12 */
|
||||||
|
int64 day; /* 1-31 */
|
||||||
|
int64 hour; /* 0-23, already normalized out of 12-hour/PM if needed */
|
||||||
|
int64 minute; /* 0-59 */
|
||||||
|
int64 second; /* 0-59 */
|
||||||
|
} RtcDateTime;
|
||||||
|
|
||||||
|
/* Converts a single CMOS BCD byte (e.g. 0x59) to its binary value (59).
|
||||||
|
Exposed on its own, like ata.c's ataModelString, because it is a real
|
||||||
|
piece of the conversion pipeline worth testing in isolation from any
|
||||||
|
hardware access. */
|
||||||
|
uint8 rtcBcdToBinary(uint8 bcd);
|
||||||
|
|
||||||
|
/* Reads the current date/time off the CMOS RTC. Polls Status Register A's
|
||||||
|
Update-In-Progress bit (bounded, like ata.c's ataPollTimeout) before
|
||||||
|
each of two back-to-back raw reads and only accepts the result once both
|
||||||
|
raw reads agree byte-for-byte, which rules out a read torn mid-tick by
|
||||||
|
the RTC's own once-a-second update. Returns false only if the two raw
|
||||||
|
reads never agreed within the bounded retry budget (never hangs). */
|
||||||
|
bool rtcReadDateTime(RtcDateTime* out);
|
||||||
|
|
||||||
|
/* Civil calendar date/time -> seconds since the Unix epoch
|
||||||
|
(1970-01-01T00:00:00Z), via Howard Hinnant's days_from_civil algorithm
|
||||||
|
(proleptic Gregorian, correct for any year the RtcDateTime fields above
|
||||||
|
can represent). Pure arithmetic, no hardware access -- deterministic and
|
||||||
|
testable without a real clock. */
|
||||||
|
int64 rtcEpochSeconds(const RtcDateTime* dt);
|
||||||
|
|
||||||
|
/* rtcReadDateTime + rtcEpochSeconds in one call, the usual entry point for
|
||||||
|
anything that just wants "now" as a plain integer (sysTime, syscall.c).
|
||||||
|
Returns 0 if the underlying read failed. */
|
||||||
|
int64 rtcNow(void);
|
||||||
|
|
||||||
|
/* Seconds since the Unix epoch -> civil calendar date/time, the exact
|
||||||
|
inverse of rtcEpochSeconds above (Howard Hinnant's civil_from_days,
|
||||||
|
proleptic Gregorian, same algorithm family as daysFromCivil in rtc.c).
|
||||||
|
Correct for any epochSeconds value representable in int64, including
|
||||||
|
negative ones (dates before 1970). Pure arithmetic, no hardware access
|
||||||
|
-- the piece sfs.h's on-disk mtime/ctime (raw epoch integers) needs to
|
||||||
|
become a human-readable date, e.g. for a shell `ls -l`. */
|
||||||
|
void rtcCivilFromEpoch(int64 epochSeconds, RtcDateTime* out);
|
||||||
|
|
||||||
|
#endif
|
||||||
+100
-1
@@ -3,13 +3,78 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "vfs.h"
|
#include "vfs.h"
|
||||||
|
#include "prioq.h"
|
||||||
|
#include "isr.h"
|
||||||
|
#include "signalnum.h"
|
||||||
|
#include "mmap.h"
|
||||||
|
|
||||||
|
/* Default scheduling weight (xv6/CFS-style "nice" niceWeight -- higher runs
|
||||||
|
more often, not longer). threadSetPriority scales relative to this. */
|
||||||
|
#define schedDefaultWeight 1024u
|
||||||
|
|
||||||
|
/* Returned by threadCreate/threadCreateUser when the thread table is full
|
||||||
|
(see threadCapacity) -- never a valid thread id, since id 0 is always the
|
||||||
|
permanent idle thread. Callers must check for this before using the
|
||||||
|
result to index into anything thread-related. */
|
||||||
|
#define threadInvalidId 0xFFFFFFFFu
|
||||||
|
|
||||||
|
/* Capacity of Thread.name, including the terminator. Large enough for every
|
||||||
|
name this kernel currently produces (kernel thread literals, "/bin/..."
|
||||||
|
paths passed to threadCreateUser by spawnUser/doSpawn) with room to
|
||||||
|
spare; longer names are truncated, never overflowed. */
|
||||||
|
#define threadNameMax 32
|
||||||
|
|
||||||
|
/* Capacity of Thread.env below: envMaxVars is how many "NAME=VALUE" slots
|
||||||
|
a process may hold at once, envEntryMax is the max size in bytes of a
|
||||||
|
single slot INCLUDING both the '=' separator and the nul terminator
|
||||||
|
(so "NAME=VALUE\0" must fit in envEntryMax bytes). Generous enough for
|
||||||
|
anything this kernel's own shell/demos actually set (see user/envprint.c)
|
||||||
|
with room to spare, the same "small fixed table, not a growable heap
|
||||||
|
allocation" choice Thread.cwd already made for a single path. */
|
||||||
|
#define envMaxVars 8
|
||||||
|
#define envEntryMax 64
|
||||||
|
|
||||||
|
/* One bit per signal number in Thread.sigPending/sigBlocked, and the size
|
||||||
|
of Thread.sigHandlers[] below -- see signal.h for the actual signal
|
||||||
|
subsystem this supports. Defined here, not signal.h, purely to break the
|
||||||
|
include cycle (signal.h includes sched.h for the Thread type). */
|
||||||
|
#define sigMaxSignals 32
|
||||||
|
|
||||||
|
/* Depth of the per-thread nested-handler context stack (Thread.sigSavedRegs/
|
||||||
|
sigSavedBlocked below) -- how many signal handlers may genuinely interrupt
|
||||||
|
each other (handler A running, a second signal arrives and is not blocked
|
||||||
|
by A's own sa_mask, so handler B runs nested inside A, then A resumes once
|
||||||
|
B returns) before sigConsumePending (signal.c) refuses to deliver a new
|
||||||
|
one and simply leaves it pending. A real kernel bounds this too (rlimit
|
||||||
|
on the signal-frame stack it pushes into user memory); this one has no
|
||||||
|
user-memory signal frame to overflow at all (see signal.h's file comment
|
||||||
|
-- the saved context lives in the Thread struct itself), so the bound
|
||||||
|
exists purely to keep that struct's size fixed and to fail a runaway
|
||||||
|
signal-storm safely (further deliveries just wait) instead of unboundedly
|
||||||
|
growing kernel memory per thread. 4 is generous headroom for anything
|
||||||
|
this kernel's own handlers actually do (none nest more than one deep
|
||||||
|
today -- see user/nesttarget.c) without being large enough to make
|
||||||
|
Thread noticeably bigger than sigMaxSignals-worth of handlers already
|
||||||
|
makes it. */
|
||||||
|
#define sigMaxNestDepth 4
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
threadUnused = 0,
|
threadUnused = 0,
|
||||||
threadRunnable = 1,
|
threadRunnable = 1,
|
||||||
threadRunning = 2,
|
threadRunning = 2,
|
||||||
threadSleeping = 3,
|
threadSleeping = 3,
|
||||||
threadDead = 4
|
threadDead = 4,
|
||||||
|
threadBlocked = 5,
|
||||||
|
/* Job-control stop (signal.h's SIGSTOP/SIGTSTP): excluded from
|
||||||
|
schedule()'s ready-queue rebuild exactly like Sleeping/Blocked
|
||||||
|
already are (only threadRunnable is ever inserted), so a stopped
|
||||||
|
thread simply never gets picked until threadContinue moves it back
|
||||||
|
to threadRunnable. Distinct from threadBlocked because it is NOT
|
||||||
|
waiting on a wait-channel condition -- schedWakeChannel must never
|
||||||
|
treat it as a match (see schedWakeChannel), only threadContinue
|
||||||
|
(an explicit SIGCONT, not "the resource became available") ever
|
||||||
|
moves a thread out of this state. */
|
||||||
|
threadStopped = 6
|
||||||
} ThreadState;
|
} ThreadState;
|
||||||
|
|
||||||
typedef void (*ThreadEntry)(void);
|
typedef void (*ThreadEntry)(void);
|
||||||
@@ -19,7 +84,41 @@ typedef struct Thread {
|
|||||||
uint64 stackBase;
|
uint64 stackBase;
|
||||||
uint64 kernelStackTop;
|
uint64 kernelStackTop;
|
||||||
uint64 sleepUntil;
|
uint64 sleepUntil;
|
||||||
|
/* Absolute pitTicks() deadline for a pending alarm(2)-style SIGALRM
|
||||||
|
(0 == no alarm pending), set by threadSetAlarm and consulted once
|
||||||
|
per timer tick by schedule() -- the exact same shape as sleepUntil
|
||||||
|
above, checked in the exact same loop, except expiry raises a real
|
||||||
|
signal (sigRaise, signal.h) against the owning thread instead of
|
||||||
|
just flipping its own ThreadState back to Runnable. Deliberately a
|
||||||
|
plain deadline, not a wait-channel/ThreadState change: unlike
|
||||||
|
threadSleep, a thread with a pending alarm keeps running (or
|
||||||
|
sleeping, or blocked on something unrelated) exactly as it already
|
||||||
|
was right up until the deadline, exactly like real alarm()'s timer
|
||||||
|
ticking down in the background regardless of what the process is
|
||||||
|
doing. */
|
||||||
|
uint64 alarmAt;
|
||||||
|
/* poll.h's sysPoll (syscall.c) uses the same restart-the-syscall park
|
||||||
|
pattern sysRead/sysWrite/sysWait already use, but a poll call can be
|
||||||
|
woken by any one of several unrelated fds, not one -- so instead of
|
||||||
|
parking on a single fd's own wait channel (vfsBlockingChannel),
|
||||||
|
sysPoll blocks every waiting thread on ONE shared channel
|
||||||
|
(schedPollChannel below) and simply re-checks its whole fd list from
|
||||||
|
scratch on every wake, spurious or not, exactly like the retry loop
|
||||||
|
every OTHER restartable syscall already has. pollActive marks "this
|
||||||
|
thread is mid a not-yet-satisfied sysPoll call" (so a retry entry
|
||||||
|
can tell itself apart from a genuinely fresh call); pollDeadline is
|
||||||
|
the absolute pitTicks() deadline schedule()'s own expiry scan forces
|
||||||
|
a wake at if no fd ever becomes ready first (0 == wait forever,
|
||||||
|
meaningful only while pollActive is true -- the same "0 reserved"
|
||||||
|
convention alarmAt above already uses, but here 0 is a real,
|
||||||
|
legal, requested state (a timeout of -1) rather than "nothing
|
||||||
|
pending"). */
|
||||||
|
bool pollActive;
|
||||||
|
uint64 pollDeadline;
|
||||||
uint64 brk;
|
uint64 brk;
|
||||||
|
uint64 vruntime;
|
||||||
|
uint64 exitCode;
|
||||||
|
uint32 weight;
|
||||||
uint32 id;
|
uint32 id;
|
||||||
ThreadState state;
|
ThreadState state;
|
||||||
const char* name;
|
const char* name;
|
||||||
|
|||||||
Reference in new issue
Block a user