Skip to content

Instantly share code, notes, and snippets.

@u1735067
Last active March 11, 2021 15:28
Show Gist options
  • Save u1735067/de45d552372a9296abbbbe407ae52180 to your computer and use it in GitHub Desktop.
Save u1735067/de45d552372a9296abbbbe407ae52180 to your computer and use it in GitHub Desktop.
Properly set-up i2c RTC ds1307 on ArchLinux
echo "dtoverlay=i2c-rtc,ds3231" >> /boot/config.txt
cat <<EOF > /etc/udev/rules.d/55-rtc-i2c.rules
#/lib/udev/rules.d/50-udev-default.rules:SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
#/lib/udev/rules.d/50-udev-default.rules:SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
# I2C RTC, when added and not the source of the sys clock (kernel), is used
ACTION=="add", SUBSYSTEMS=="i2c", SUBSYSTEM=="rtc", KERNEL=="rtc0", ATTR{hctosys}=="0", \\
RUN+="/sbin/hwclock '--rtc=\$root/\$name' --hctosys", \\
RUN+="/sbin/logger --tag systemd-udevd 'System clock set from i2c hardware clock \$name (\$attr{name})'"
EOF
[root@alarmpi alarm]# journalctl -b 0
[..]
Feb 01 01:10:26 alarmpi systemd[1]: System time before build time, advancing clock.
Feb 06 22:53:46 alarmpi systemd[1]: Time has been changed
[..]
Feb 06 22:53:46 alarmpi systemd[1]: Reached target System Initialization.
Feb 06 22:53:46 alarmpi systemd[1]: Reached target Sockets.
Feb 06 22:53:46 alarmpi systemd[1]: Reached target Basic System.
[..]
Feb 01 01:10:31 alarmpi systemd-timesyncd[213]: System clock time unset or jumped backwards, restoring from recorded timestamp: Tue 2018-02-06 22:53:46 UTC
Feb 06 22:53:46 alarmpi systemd[1]: Time has been changed
[..]
Feb 06 22:53:46 alarmpi kernel: rtc-ds1307 1-0068: rtc core: registered ds3231 as rtc0
[..]
Feb 06 22:53:46 alarmpi systemd[1]: Reached target System Time Synchronized.
[..]
Feb 06 22:53:47 alarmpi systemd[1]: Reached target Timers.
Feb 06 22:54:11 alarmpi systemd[1]: Time has been changed
Feb 06 22:54:11 alarmpi systemd-udevd[224]: System clock set from i2c hardware clock rtc0 (ds3231)
[..]
Feb 06 22:54:11 alarmpi systemd[1]: Reached target Network.
[..]
Feb 06 22:54:12 alarmpi systemd[1]: Reached target Login Prompts.
[..]
Feb 06 22:54:12 alarmpi systemd[1]: Reached target Multi-User System.
Feb 06 22:54:12 alarmpi systemd[1]: Reached target Graphical Interface.
Feb 06 22:56:00 alarmpi systemd[240]: Reached target Timers.
Feb 06 22:56:00 alarmpi systemd[240]: Reached target Paths.
[..]
Feb 06 22:56:00 alarmpi systemd[240]: Reached target Sockets.
Feb 06 22:56:00 alarmpi systemd[240]: Reached target Basic System.
Feb 06 22:56:00 alarmpi systemd[240]: Reached target Default.

You can find cheap ds3231 i2c RTC module, like https://www.aliexpress.com/item/1pcs-SAMIORE-ROBOT-DS3231-Precision-RTC-Module-Memory-Module/32828162429.html but it needs set-up first. By the way, it connects on pins 1, 3, 5, (7), 9. See https://pinout.xyz/pinout/i2c.

As the ds3231 driver is compiled as a module (rtc-ds1307), and not built-in, the RTC_HCTOSYS kernel config option which controls https://github.com/torvalds/linux/blob/master/drivers/rtc/hctosys.c becomes useless: modules are loaded after the late_initcall() hook (https://stackoverflow.com/questions/18605653/module-init-vs-core-initcall-vs-early-initcall).

So the kernel cannot set the time from the RTC (device is recognized too late). And systemd don't touch the time set by the kernel (not it's job):

As neither the kernel nor systemd handle this out-of-the-box, and as ArchLinux doesn't embed messy script to handle that, you have to take care of this.

There's many solutions for this on Internet, many are wrong (main reason might be they're outdated) or overcomplicated:

  • you can't really use a service: too early it'll fail because the device isn't present yet, too late you'll miss the opportunity to set the clock early
  • you don't need to init the device using dtoverlay
  • you don't need a service for this one-time command, and even less a script called by whatever
  • you don't need to edit system files (like /etc/init.d/hwclock.sh or /lib/udev/hwclock-set .. hello raspbian users)

The first inspiration for this solution is http://blog.fraggod.net/2015/11/25/replacing-built-in-rtc-with-i2c-battery-backed-one-on-beaglebone-black-from-boot.html, which is the right solution if you want to have the time-sync.target "System Time Synchronized" target respected.

In the end I found https://gist.github.com/h0tw1r3/11191428/ and https://gist.github.com/Lahorde/2bc5e4a3b69fc6ca5797 but they're still not as simple as one config.txt line & one udev rule.

Some useful links:

[root@alarmpi alarm]# udevadm info -a /dev/rtc0

  looking at device '/devices/platform/soc/20804000.i2c/i2c-1/1-0068/rtc/rtc0':
    KERNEL=="rtc0"
    SUBSYSTEM=="rtc"
    DRIVER==""
    ATTR{date}=="2018-02-06"
    ATTR{hctosys}=="0"
    ATTR{max_user_freq}=="64"
    ATTR{name}=="ds3231"
    ATTR{since_epoch}=="1517960429"
    ATTR{time}=="23:40:29"

  looking at parent device '/devices/platform/soc/20804000.i2c/i2c-1/1-0068':
    KERNELS=="1-0068"
    SUBSYSTEMS=="i2c"
    DRIVERS=="rtc-ds1307"
    ATTRS{name}=="ds3231"

  looking at parent device '/devices/platform/soc/20804000.i2c/i2c-1':
    KERNELS=="i2c-1"
    SUBSYSTEMS=="i2c"
    DRIVERS==""
    ATTRS{name}=="bcm2835 I2C adapter"

  looking at parent device '/devices/platform/soc/20804000.i2c':
    KERNELS=="20804000.i2c"
    SUBSYSTEMS=="platform"
    DRIVERS=="i2c-bcm2835"
    ATTRS{driver_override}=="(null)"

  looking at parent device '/devices/platform/soc':
    KERNELS=="soc"
    SUBSYSTEMS=="platform"
    DRIVERS==""
    ATTRS{driver_override}=="(null)"
# To respect time-sync.target,
# inspired by http://blog.fraggod.net/2015/11/25/replacing-built-in-rtc-with-i2c-battery-backed-one-on-beaglebone-black-from-boot.html
echo "dtoverlay=i2c-rtc,ds3231" >> /boot/config.txt
cat <<EOF > /etc/udev/rules.d/55-rtc-i2c.rules
#/lib/udev/rules.d/50-udev-default.rules:SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
#/lib/udev/rules.d/50-udev-default.rules:SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
# I2C RTC, when added and not the source of the sys clock (kernel), is used ;
# separate name to ensure rtc-i2c.service will not be run on anything else
ACTION=="add", SUBSYSTEMS=="i2c", SUBSYSTEM=="rtc", KERNEL=="rtc0", ATTR{hctosys}=="0", SYMLINK+="rtc_i2c", TAG+="systemd"
EOF
cat <<EOF > /etc/systemd/system/rtc-i2c.service
[Unit]
ConditionCapability=CAP_SYS_TIME
ConditionVirtualization=!container
DefaultDependencies=no
Wants=dev-rtc_i2c.device
After=dev-rtc_i2c.device
Before=systemd-timesyncd.service ntpd.service chrony.service
[Service]
Type=oneshot
CapabilityBoundingSet=CAP_SYS_TIME
PrivateTmp=yes
ProtectSystem=full
ProtectHome=yes
DeviceAllow=/dev/rtc_i2c rw
DevicePolicy=closed
ExecStart=/sbin/hwclock --rtc=/dev/rtc_i2c --hctosys
ExecStartPost=/sbin/logger --tag rtc-i2c 'System clock set from i2c hardware clock'
[Install]
WantedBy=time-sync.target
EOF
systemctl daemon-reload
systemctl enable rtc-i2c.service
[root@alarmpi alarm]# journalctl -b 0
[..]
Feb 01 01:10:30 alarmpi systemd[1]: Reached target Local File Systems.
[..]
Feb 01 01:10:31 alarmpi kernel: rtc-ds1307 1-0068: rtc core: registered ds3231 as rtc0
Feb 01 01:10:31 alarmpi systemd[1]: Found device /dev/rtc_i2c.
Feb 01 01:10:31 alarmpi systemd[1]: Starting rtc-i2c.service...
Feb 07 00:41:02 alarmpi systemd[1]: Time has been changed
Feb 07 00:41:02 alarmpi rtc-i2c[234]: System clock set from i2c hardware clock
Feb 07 00:41:02 alarmpi systemd[1]: Started rtc-i2c.service.
Feb 07 00:41:02 alarmpi systemd[1]: Starting Network Time Synchronization...
Feb 07 00:41:02 alarmpi systemd[1]: Started Network Time Synchronization.
Feb 07 00:41:02 alarmpi systemd[1]: Reached target System Time Synchronized.
Feb 07 00:41:02 alarmpi systemd[1]: Reached target System Initialization.
[..]
Feb 07 00:41:02 alarmpi systemd[1]: Reached target Sockets.
[..]
Feb 07 00:41:02 alarmpi systemd[1]: Reached target Timers.
Feb 07 00:41:02 alarmpi systemd[1]: Reached target Basic System.
[..]
Feb 07 00:41:03 alarmpi systemd[1]: Reached target Network.
[..]
Feb 07 00:41:04 alarmpi systemd[1]: Reached target Login Prompts.
[..]
Feb 07 00:41:04 alarmpi systemd[1]: Reached target Multi-User System.
Feb 07 00:41:05 alarmpi systemd[1]: Reached target Graphical Interface.
[..]
Feb 07 00:41:20 alarmpi systemd[262]: Reached target Timers.
Feb 07 00:41:20 alarmpi systemd[262]: Reached target Paths.
[..]
Feb 07 00:41:20 alarmpi systemd[262]: Reached target Sockets.
Feb 07 00:41:20 alarmpi systemd[262]: Reached target Basic System.
Feb 07 00:41:20 alarmpi systemd[262]: Reached target Default.

udev rule

Feb 07 00:55:53 alarmpi systemd[1]: Startup finished in 7.980s (kernel) + 10.302s (userspace) = 18.283s.
Feb 07 00:58:11 alarmpi systemd[1]: Startup finished in 7.979s (kernel) + 10.308s (userspace) = 18.287s.
Feb 07 01:02:55 alarmpi systemd[1]: Startup finished in 8.082s (kernel) + 10.309s (userspace) = 18.391s.
Feb 07 01:04:51 alarmpi systemd[1]: Startup finished in 7.951s (kernel) + 10.692s (userspace) = 18.644s.
Feb 07 01:07:24 alarmpi systemd[1]: Startup finished in 7.941s (kernel) + 10.520s (userspace) = 18.461s.

udev rule + service for target

Feb 07 00:41:05 alarmpi systemd[1]: Startup finished in 8.004s (kernel) + 11.484s (userspace) = 19.489s.
Feb 07 01:10:28 alarmpi systemd[1]: Startup finished in 7.974s (kernel) + 11.565s (userspace) = 19.540s.
Feb 07 01:12:15 alarmpi systemd[1]: Startup finished in 7.981s (kernel) + 10.790s (userspace) = 18.771s.
Feb 07 01:13:47 alarmpi systemd[1]: Startup finished in 7.975s (kernel) + 10.894s (userspace) = 18.869s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment