Hi,

I have tested the read and set operations, they seemd fine. RTC has
automatic leap year compensation, it's quite fun to see the effect.
I still have a few questions:

- Day of year is not stored in RTC[0], though we can calculate it when
  we are reading RTC based on year, month, and day of month. Should we do it?

- Is there an optimal value for
  ports_manage_port_operations_multithread()?

[0]:http://bitsavers.trailing-edge.com/pdf/ibm/pc/at/1502494_PC_AT_Technical_Reference_Mar84.pdf
 1-46

---
 Makefile          |   3 +-
 hurd/pioctl.defs  |  52 +++++++++++
 hurd/rtc.h        |  44 +++++++++
 rtc/Makefile      |  39 ++++++++
 rtc/main.c        | 114 ++++++++++++++++++++++++
 rtc/mig-mutate.h  |  24 +++++
 rtc/pioctl-ops.c  | 223 ++++++++++++++++++++++++++++++++++++++++++++++
 sutils/MAKEDEV.sh |   4 +-
 8 files changed, 501 insertions(+), 2 deletions(-)
 create mode 100644 hurd/pioctl.defs
 create mode 100644 hurd/rtc.h
 create mode 100644 rtc/Makefile
 create mode 100644 rtc/main.c
 create mode 100644 rtc/mig-mutate.h
 create mode 100644 rtc/pioctl-ops.c

diff --git a/Makefile b/Makefile
index 4d848221..9d9e33c3 100644
--- a/Makefile
+++ b/Makefile
@@ -47,7 +47,8 @@ prog-subdirs = auth proc exec term \
               init \
               devnode \
               eth-multiplexer \
-              shutdown
+              shutdown \
+              rtc
 
 ifeq ($(HAVE_LIBRUMP),yes)
 prog-subdirs += rumpdisk
diff --git a/hurd/pioctl.defs b/hurd/pioctl.defs
new file mode 100644
index 00000000..307d9ee9
--- /dev/null
+++ b/hurd/pioctl.defs
@@ -0,0 +1,52 @@
+/* Definitions for /dev/rtc ioctls
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of the GNU Hurd.
+
+   The GNU Hurd is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   The GNU Hurd is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+/* Ioctl group 'p'; the subsystem is derived from calculations in
+   hurd/ioctls.defs. */
+subsystem pioctl 140000;
+
+#include <hurd/ioctl_types.defs>
+
+import <hurd/rtc.h>;
+
+#ifdef PIOCTL_IMPORTS
+PIOCTL_IMPORTS
+#endif
+
+INTR_INTERFACE
+
+/* This is the arg for a struct rtc_time as specified by the
+   definition of _IOT_rtc_time in $(hurd)/hurd/rtc.h. */
+type rtc_time_t = struct[9] of int;
+
+/* TODO: adding other ioctl calls for /dev/rtc in mc146818rtc.h */
+skip; skip; skip; skip; /* 0 1 2 3 */
+skip; skip; skip; skip; /* 4 5 6 7 */
+skip; /* 8 */
+
+/* 9 RTC_RD_TIME */
+routine pioctl_rtc_rd_time (
+       reqport: io_t;
+       out tm: rtc_time_t);
+
+/* 10 RTC_SET_TIME */
+routine pioctl_rtc_set_time (
+       reqport: io_t;
+       tm: rtc_time_t);
diff --git a/hurd/rtc.h b/hurd/rtc.h
new file mode 100644
index 00000000..6516e86c
--- /dev/null
+++ b/hurd/rtc.h
@@ -0,0 +1,44 @@
+/* GNU Hurd RTC interface
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of the GNU Hurd.
+
+   The GNU Hurd is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   The GNU Hurd is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+#ifndef _RTC_H
+#define _RTC_H 1
+
+#include <hurd/ioctl.h>
+
+struct rtc_time {
+  int tm_sec;
+  int tm_min;
+  int tm_hour;
+  int tm_mday;
+  int tm_mon;
+  int tm_year;
+  int tm_wday;
+  int tm_yday;
+  int tm_isdst;
+};
+typedef struct rtc_time rtc_time_t;
+
+#define _IOT_rtc_time _IOT(_IOTS(int),9,0,0,0,0)
+
+#define RTC_RD_TIME    _IOR('p', 0x09, struct rtc_time) /* Read RTC time   */
+#define RTC_SET_TIME   _IOW('p', 0x0a, struct rtc_time) /* Set RTC time    */
+
+#endif /* rtc.h */
diff --git a/rtc/Makefile b/rtc/Makefile
new file mode 100644
index 00000000..e84d9c14
--- /dev/null
+++ b/rtc/Makefile
@@ -0,0 +1,39 @@
+#   Makefile for rtc server
+#
+#   Copyright (C) 2024 Free Software Foundation, Inc.
+#
+#   This file is part of the GNU Hurd.
+#
+#   The GNU Hurd is free software; you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as
+#   published by the Free Software Foundation; either version 2, or (at
+#   your option) any later version.
+#
+#   The GNU Hurd is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#   General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+dir := rtc
+makemode := server
+
+SRCS = main.c pioctl-ops.c
+MIGSRCS = pioctlServer.c
+
+OBJS = main.o pioctlServer.o pioctl-ops.o
+
+HURDLIBS = trivfs shouldbeinlibc ports
+
+target = rtc
+
+include ../Makeconf
+
+MIGCOMSFLAGS += -prefix rtc_
+mig-sheader-prefix = rtc_
+pioctl-MIGSFLAGS = -imacros $(srcdir)/mig-mutate.h
+
+rtc_pioctl_S.h pioctlServer.c: mig-mutate.h
diff --git a/rtc/main.c b/rtc/main.c
new file mode 100644
index 00000000..daefb95d
--- /dev/null
+++ b/rtc/main.c
@@ -0,0 +1,114 @@
+/* A translator for accessing rtc
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of the GNU Hurd.
+
+   The GNU Hurd is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   The GNU Hurd is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+#include <version.h>
+
+#include <error.h>
+#include <stdbool.h>
+#include <argp.h>
+#include <hurd/trivfs.h>
+#include <hurd/ports.h>
+#include <hurd/rtc.h>
+#include <sys/io.h>
+
+#include "rtc_pioctl_S.h"
+
+const char *argp_program_version = STANDARD_HURD_VERSION (rtc);
+
+static struct trivfs_control *rtccntl;
+
+int trivfs_fstype = FSTYPE_DEV;
+int trivfs_fsid = 0;
+int trivfs_support_read = 1;
+int trivfs_support_write = 0;
+int trivfs_support_exec = 0;
+int trivfs_allow_open = O_READ | O_WRITE;
+
+static const struct argp_option options[] =
+{
+  {0}
+};
+
+/* TODO: adding option */
+static error_t
+parse_opt (int opt, char *arg, struct argp_state *state)
+{
+  return ARGP_ERR_UNKNOWN;
+}
+
+static const struct argp rtc_argp =
+{ options, parse_opt, 0, "RTC device" };
+
+static int
+demuxer (mach_msg_header_t *inp, mach_msg_header_t *outp)
+{
+  mig_routine_t routine;
+  if ((routine = rtc_pioctl_server_routine (inp)) ||
+      (routine = NULL, trivfs_demuxer (inp, outp)))
+    {
+      if (routine)
+        (*routine) (inp, outp);
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
+int
+main (int argc, char **argv)
+{
+  error_t err;
+  mach_port_t bootstrap;
+
+  argp_parse (&rtc_argp, argc, argv, 0, 0, 0);
+
+  task_get_bootstrap_port (mach_task_self (), &bootstrap);
+  if (bootstrap == MACH_PORT_NULL)
+    error (1, 0, "Must be started as a translator");
+
+  /* Reply to our parent */
+  err = trivfs_startup (bootstrap, O_NORW, NULL, NULL, NULL, NULL, &rtccntl);
+  mach_port_deallocate (mach_task_self (), bootstrap);
+  if (err)
+    error (1, err, "trivfs_startup failed");
+
+  /* Request for permission to do i/o on port numbers 0x70 and 0x71 for
+     accessing RTC registers. */
+  err = ioperm (0x70, 2, true);
+  if (err)
+    error (1, err, "Request IO permission failed");
+
+  /* Launch. */
+  ports_manage_port_operations_multithread (rtccntl->pi.bucket, demuxer,
+                                           30 * 1000, 2 * 60 * 1000, 0);
+
+  return 0;
+}
+
+void
+trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
+{
+}
+
+error_t
+trivfs_goaway (struct trivfs_control *fsys, int flags)
+{
+  exit (0);
+}
diff --git a/rtc/mig-mutate.h b/rtc/mig-mutate.h
new file mode 100644
index 00000000..ddead5be
--- /dev/null
+++ b/rtc/mig-mutate.h
@@ -0,0 +1,24 @@
+/* Type translation for rtc operations
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of the GNU Hurd.
+
+   The GNU Hurd is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   The GNU Hurd is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+#define IO_INTRAN trivfs_protid_t trivfs_begin_using_protid (io_t)
+#define IO_INTRAN_PAYLOAD trivfs_protid_t trivfs_begin_using_protid_payload
+#define IO_DESTRUCTOR trivfs_end_using_protid (trivfs_protid_t)
+#define PIOCTL_IMPORTS import "../libtrivfs/mig-decls.h";
diff --git a/rtc/pioctl-ops.c b/rtc/pioctl-ops.c
new file mode 100644
index 00000000..c33403c8
--- /dev/null
+++ b/rtc/pioctl-ops.c
@@ -0,0 +1,223 @@
+/* Server side implementation for rtc server
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of the GNU Hurd.
+
+   The GNU Hurd is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   The GNU Hurd is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */
+
+/* This implementation is largely based on sys-utils/hwclock-cmos.c from
+   util-linux */
+
+/* A struct tm has int fields (it is defined in POSIX)
+   tm_sec      0-59, 60 or 61 only for leap seconds
+   tm_min      0-59
+   tm_hour     0-23
+   tm_mday     1-31
+   tm_mon      0-11
+   tm_year     number of years since 1900
+   tm_wday     0-6, 0=Sunday
+   tm_yday     0-365
+   tm_isdst    >0: yes, 0: no, <0: unknown */
+
+#include "rtc_pioctl_S.h"
+#include <hurd/rtc.h>
+#include <hurd/hurd_types.h>
+#include <sys/io.h>
+#include <stdbool.h>
+
+/* Conversions to and from RTC internal format */
+#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
+#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10)
+
+/* POSIX uses 1900 as epoch for a struct tm, and 1970 for a time_t. */
+#define TM_EPOCH 1900
+
+static unsigned short clock_ctl_addr = 0x70;
+static unsigned short clock_data_addr = 0x71;
+
+static inline unsigned long cmos_read(unsigned long reg)
+{
+  outb_p(reg, clock_ctl_addr);
+  return inb_p(clock_data_addr);
+}
+
+static inline void cmos_write(unsigned long reg, unsigned long val)
+{
+  outb(reg, clock_ctl_addr);
+  outb(val, clock_data_addr);
+}
+
+
+static inline int cmos_clock_busy(void)
+{
+  return
+    /* poll bit 7 (UIP) of Control Register A */
+    (cmos_read(10) & 0x80);
+}
+
+
+/* 9 RTC_RD_TIME -- Read RTC time */
+kern_return_t
+rtc_S_pioctl_rtc_rd_time (struct trivfs_protid *cred, struct rtc_time *tm)
+{
+  if (!cred)
+    return EOPNOTSUPP;
+  if (!(cred->po->openmodes & O_READ))
+    return EBADF;
+
+  unsigned char status = 0;
+  unsigned char pmbit = 0;
+
+  /* When we wait for 100 ms (it takes too long), we exit with error */
+  int time_passed_in_milliseconds = 0;
+  bool read_rtc_successfully = false;
+  while (time_passed_in_milliseconds < 100) {
+    if (!cmos_clock_busy()) {
+      tm->tm_sec = cmos_read(0);
+      tm->tm_min = cmos_read(2);
+      tm->tm_hour = cmos_read(4);
+      tm->tm_wday = cmos_read(6);
+      tm->tm_mday = cmos_read(7);
+      tm->tm_mon = cmos_read(8);
+      tm->tm_year = cmos_read(9);
+      status = cmos_read(11);
+      /* Unless the clock changed while we were reading, consider this
+         a good clock read. */
+      if (tm->tm_sec == cmos_read(0)) {
+       read_rtc_successfully = true;
+       break;
+      }
+    }
+    usleep (1000);
+    time_passed_in_milliseconds++;
+  }
+
+  if (!read_rtc_successfully)
+    return EBUSY;
+
+  if (!(status & 0x04)) { /* BCD mode - the default */
+    BCD_TO_BIN(tm->tm_sec);
+    BCD_TO_BIN(tm->tm_min);
+    pmbit = (tm->tm_hour & 0x80);
+    tm->tm_hour &= 0x7f;
+    BCD_TO_BIN(tm->tm_hour);
+    BCD_TO_BIN(tm->tm_wday);
+    BCD_TO_BIN(tm->tm_mday);
+    BCD_TO_BIN(tm->tm_mon);
+    BCD_TO_BIN(tm->tm_year);
+  }
+
+  /* We don't use the century byte of the Hardware Clock since we
+     don't know its address (usually 50 or 55). Here, we follow the
+     advice of the X/Open Base Working Group: "if century is not
+     specified, then values in the range [69-99] refer to years in the
+     twentieth century (1969 to 1999 inclusive), and values in the
+     range [00-68] refer to years in the twenty-first century (2000 to
+     2068 inclusive)." */
+  tm->tm_wday -= 1;
+  tm->tm_mon -= 1;
+  if (tm->tm_year < 69)
+    tm->tm_year += 100;
+  if (pmbit) {
+    tm->tm_hour += 12;
+    if (tm->tm_hour == 24)
+      tm->tm_hour = 0;
+  }
+
+  /* don't know whether it's daylight */
+  tm->tm_isdst = -1;
+
+  return KERN_SUCCESS;
+}
+
+/* 10 RTC_SET_TIME -- Set RTC time */
+kern_return_t
+rtc_S_pioctl_rtc_set_time (struct trivfs_protid *cred, struct rtc_time tm)
+{
+  if (!cred)
+    return EOPNOTSUPP;
+  if (!(cred->po->openmodes & O_WRITE))
+    return EBADF;
+
+  unsigned char save_control, save_freq_select, pmbit = 0;
+
+  /* CMOS byte 10 (clock status register A) has 3 bitfields:
+    bit 7: 1 if data invalid, update in progress (read-only bit)
+            (this is raised 224 us before the actual update starts)
+     6-4    select base frequency
+            010: 32768 Hz time base (default)
+            111: reset
+            all other combinations are manufacturer-dependent
+            (e.g.: DS1287: 010 = start oscillator, anything else = stop)
+     3-0    rate selection bits for interrupt
+            0000 none (may stop RTC)
+            0001, 0010 give same frequency as 1000, 1001
+            0011 122 microseconds (minimum, 8192 Hz)
+            .... each increase by 1 halves the frequency, doubles the period
+            1111 500 milliseconds (maximum, 2 Hz)
+            0110 976.562 microseconds (default 1024 Hz) */
+
+  save_control = cmos_read(11);        /* tell the clock it's being set */
+  cmos_write(11, (save_control | 0x80));
+  save_freq_select = cmos_read(10);   /* stop and reset prescaler */
+  cmos_write(10, (save_freq_select | 0x70));
+
+  tm.tm_year %= 100;
+  tm.tm_mon += 1;
+  tm.tm_wday += 1;
+
+  if (!(save_control & 0x02)) {        /* 12hr mode; the default is 24hr mode 
*/
+    if (tm.tm_hour == 0)
+      tm.tm_hour = 24;
+    if (tm.tm_hour > 12) {
+      tm.tm_hour -= 12;
+      pmbit = 0x80;
+    }
+  }
+
+  if (!(save_control & 0x04)) {   /* BCD mode - the default */
+    BIN_TO_BCD(tm.tm_sec);
+    BIN_TO_BCD(tm.tm_min);
+    BIN_TO_BCD(tm.tm_hour);
+    BIN_TO_BCD(tm.tm_wday);
+    BIN_TO_BCD(tm.tm_mday);
+    BIN_TO_BCD(tm.tm_mon);
+    BIN_TO_BCD(tm.tm_year);
+  }
+
+  cmos_write(0, tm.tm_sec);
+  cmos_write(2, tm.tm_min);
+  cmos_write(4, tm.tm_hour | pmbit);
+  cmos_write(6, tm.tm_wday);
+  cmos_write(7, tm.tm_mday);
+  cmos_write(8, tm.tm_mon);
+  cmos_write(9, tm.tm_year);
+
+  /* The Linux kernel sources, linux/arch/i386/kernel/time.c, have the
+  following comment:
+
+  The following flags have to be released exactly in this order,
+  otherwise the DS12887 (popular MC146818A clone with integrated
+  battery and quartz) will not reset the oscillator and will not
+  update precisely 500 ms later. You won't find this mentioned in
+  the Dallas Semiconductor data sheets, but who believes data
+  sheets anyway ... -- Markus Kuhn */
+  cmos_write(11, save_control);
+  cmos_write(10, save_freq_select);
+
+  return KERN_SUCCESS;
+}
diff --git a/sutils/MAKEDEV.sh b/sutils/MAKEDEV.sh
index c3d7d112..c1ef4f92 100644
--- a/sutils/MAKEDEV.sh
+++ b/sutils/MAKEDEV.sh
@@ -120,10 +120,12 @@ mkdev() {
        ;;
 
       std)
-       mkdev console tty random urandom null zero full fd time mem klog shm
+       mkdev console tty random urandom null zero full fd time mem klog shm rtc
        ;;
       console|com[0-9])
        st $I root 600 c /hurd/term ${DEVDIR}/$I device $I;;
+      rtc)
+       st $I root 644 c /hurd/rtc;;
       vcs)
         st $I root 600 d /hurd/console;;
       tty[1-9][0-9]|tty[1-9])
-- 
2.47.0


Reply via email to