On Sun, Nov 17, 2024 at 3:34 PM Zhaoming Luo <zhming...@163.com> wrote:
> Hi,

Hi,

looks nice to me overall, thanks for working on this! I cannot judge
the actual driver part, so here are some nitpicks about not following
the GNU code style :D I will try to build and test it later.

By the way: apparently gnumach already has some sort of an rtc driver
(at i386/i386at/rtc.c). What do we do about that?

> diff --git a/hurd/rtc.h b/hurd/rtc.h
> new file mode 100644
> index 00000000..6516e86c
> --- /dev/null
> +++ b/hurd/rtc.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;
> +};

Nitpick: please see how struct definitions should be formatted
according to the GNU code style [0].

[0]: https://www.gnu.org/prep/standards/html_node/Formatting.html#Formatting

> diff --git a/rtc/main.c b/rtc/main.c
> new file mode 100644
> index 00000000..daefb95d
> --- /dev/null
> +++ b/rtc/main.c

> +static const struct argp rtc_argp =
> +{ options, parse_opt, 0, "RTC device" };

This would probably be a good place to spell out that RTC stands for
real time clock?

> +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");

It's probably a good idea to do this before you reply to the parent,
so you don't end up saying "I'm ready!" and then immediately exit with
an error.

> +error_t
> +trivfs_goaway (struct trivfs_control *fsys, int flags)
> +{
> +  exit (0);
> +}

Do you need to do anything to the RTC hardware before you exit to
leave it in a good/well-defined state? Especially if other threads are
accessing it at the same time? (Perhaps not, I don't know.)

> 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;

These should be #define's (or enum members if you prefer), rather than
mutable runtime memory in .data, no?

> +static inline unsigned long cmos_read(unsigned long reg)
> +{
> +  outb_p(reg, clock_ctl_addr);
> +  return inb_p(clock_data_addr);
> +}

GNU style please:

static inline unsigned long
cmos_read (unsigned long reg)
{
  outb_p (reg, clock_ctl_addr);
  return inb_p (clock_data_addr);
}

also I believe outb_p's first argument is an 'unsigned char', so why
unsigned long here?

> +
> +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);

You could perhaps write it as (1 << 7) instead of 0x80 then, not that
it matters.

> +}
> +
> +
> +/* 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;
> +      }
> +    }

GNU code style nitpicks again:
* all local variables should be declared before the actual statements
(like the cred checks),
* braces placement,
* single space between the function name and the opening paren.

(and same for all of the code below)

> +    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);

Can these writes somehow fail, and if so, can you detect it and report
the error? (I've no idea.)

> +
> +  return KERN_SUCCESS;
> +}

Sergey

Reply via email to