All of lore.kernel.org
 help / color / mirror / Atom feed
* rtc,x86: CMOS RTC fixes
@ 2021-09-12 12:42 Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH RESEND 1/7] rtc-cmos: take rtc_lock while reading from CMOS Mateusz Jończyk
                   ` (6 more replies)
  0 siblings, 7 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, x86, H. Peter Anvin, Alessandro Zummo,
	Alexandre Belloni

Hello,

This patch series fixes some issues in the RTC CMOS handling code.

First three patches fix bugs in drivers/rtc:
1. Fix rtc_lock usage in cmos_set_alarm() in rtc-cmos.c.
2. Do not touch RTC alarm registers when the RTC update is in progress. 
   (On some Intel chipsets, this causes bogus values being read or writes to
   fail silently.)
3. Fix presence check of the RTC CMOS: the clock was misdetected as broken on
   one of my systems.

Patches 1 and 2 are Cced stable. I'm going to  submit patch 3 manually
to stable after some regression testing in master.

Next three patches contain fixes for arch/x86/rtc.c: duplicate code removal
and a renaming of a function.

The final patch reverts a buggy commit - to prevent a possible deadlock at
resume from suspend.

I'm also considering whether function x86_wallclock_init() in
arch/x86/kernel/x86_init.c should call mc146818_does_rtc_work() to make sure
that the CMOS RTC is present and behaves itself. This might be useful.

Tested on top of v5.14.2, on two systems.

This is my first patch series, so please review carefully.

Greetings,
Mateusz

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: x86@kernel.org
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH RESEND 1/7] rtc-cmos: take rtc_lock while reading from CMOS
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 2/7] rtc-cmos: dont touch alarm registers during update Mateusz Jończyk
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Nobuhiro Iwamatsu, Alessandro Zummo,
	Alexandre Belloni, stable

Reading from the CMOS involves writing to the index register and then
reading from the data register. Therefore access to the CMOS has to be
serialized with rtc_lock. This invocation of CMOS_READ was not
serialized, which could cause trouble when other code is accessing CMOS
at the same time.

Use spin_lock_irq() like the rest of the function.

Nothing in kernel modifies the RTC_DM_BINARY bit, so there could be a
separate pair of spin_lock_irq() / spin_unlock_irq() before doing the
math.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Reviewed-by: Nobuhiro Iwamatsu <iwamatsu@nigauri.org>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: stable@vger.kernel.org
---
 drivers/rtc/rtc-cmos.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index 670fd8a2970e..2cd0fe728ab2 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -463,7 +463,10 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
 	min = t->time.tm_min;
 	sec = t->time.tm_sec;
 
+	spin_lock_irq(&rtc_lock);
 	rtc_control = CMOS_READ(RTC_CONTROL);
+	spin_unlock_irq(&rtc_lock);
+
 	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
 		/* Writing 0xff means "don't care" or "match all".  */
 		mon = (mon <= 12) ? bin2bcd(mon) : 0xff;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/7] rtc-cmos: dont touch alarm registers during update
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH RESEND 1/7] rtc-cmos: take rtc_lock while reading from CMOS Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:44   ` [TEST PATCH] rtc-cmos: cmos_read_alarm bug demonstration Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH v2 3/7] rtc-mc146818-lib: fix RTC presence check Mateusz Jończyk
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Alessandro Zummo, Alexandre Belloni, stable

Some Intel chipsets disconnect the time and date RTC registers when the
clock update is in progress: during this time reads may return bogus
values and writes fail silently. This includes the RTC alarm registers.
[1]

cmos_read_alarm() and cmos_set_alarm() did not take account for that,
which caused alarm time reads to sometimes return bogus values. This can
be shown with a test patch that I am attaching to this patch series.
Setting the alarm clock also probably did fail sometimes.

To make this patch suitable for inclusion in stable kernels, I'm using a
simple method for avoiding the RTC update cycle. This method is used in
mach_set_rtc_mmss() in arch/x86/kernel/rtc.c. A more elaborate algorithm
- as in mc146818_get_time() in drivers/rtc/rtc-mc146818-lib.c - would be
too complcated for stable. [2]

cmos_wait_for_uip_clear() has the rtc_lock taken while waiting for the
UIP bit to become clear. This should be harmless as during the UIP the RTC
cannot be read from anyway. mach_get_cmos_time() in arch/x86/kernel/rtc.c
does things the same way.

[1] 7th Generation Intel ® Processor Family I/O for U/Y Platforms [...]
Datasheet, Volume 1 of 2 (Intel's Document Number: 334658-006)
Page 208
https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/7th-and-8th-gen-core-family-mobile-u-y-processor-lines-i-o-datasheet-vol-1.pdf
        "If a RAM read from the ten time and date bytes is attempted
        during an update cycle, the value read do not necessarily
        represent the true contents of those locations. Any RAM writes
        under the same conditions are ignored.'

[2] I'm going to submit a unification patch for a later kernel release -
prefer to see this in stable.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: stable@vger.kernel.org
---
 drivers/rtc/rtc-cmos.c | 38 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index 2cd0fe728ab2..643433d984ab 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -248,6 +248,31 @@ static int cmos_set_time(struct device *dev, struct rtc_time *t)
 	return mc146818_set_time(t);
 }
 
+/* Some Intel chipsets disconnect the alarm registers when the clock update is
+ * in progress - during this time reads return bogus values and writes may fail
+ * silently. See for example "7th Generation Intel® Processor Family I/O for
+ * U/Y Platforms [...] Datasheet", section 27.7.1
+ *
+ * Check the UIP bit to prevent this, waiting for max 10ms for it to become
+ * clear.
+ *
+ * This function has to be called with rtc_lock taken.
+ */
+static int cmos_wait_for_uip_clear(struct device *dev)
+{
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		if ((CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) == 0)
+			return 0;
+		udelay(100);
+	}
+
+	dev_warn_ratelimited(dev, "UIP bit is stuck, cannot access RTC registers\n");
+
+	return 1;
+}
+
 static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
 {
 	struct cmos_rtc	*cmos = dev_get_drvdata(dev);
@@ -257,12 +282,17 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
 	if (!is_valid_irq(cmos->irq))
 		return -EIO;
 
+	spin_lock_irq(&rtc_lock);
+
+	if (cmos_wait_for_uip_clear(dev)) {
+		spin_unlock_irq(&rtc_lock);
+		return -EIO;
+	}
+
 	/* Basic alarms only support hour, minute, and seconds fields.
 	 * Some also support day and month, for alarms up to a year in
 	 * the future.
 	 */
-
-	spin_lock_irq(&rtc_lock);
 	t->time.tm_sec = CMOS_READ(RTC_SECONDS_ALARM);
 	t->time.tm_min = CMOS_READ(RTC_MINUTES_ALARM);
 	t->time.tm_hour = CMOS_READ(RTC_HOURS_ALARM);
@@ -477,6 +507,10 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
 	}
 
 	spin_lock_irq(&rtc_lock);
+	if (cmos_wait_for_uip_clear(dev)) {
+		spin_unlock_irq(&rtc_lock);
+		return -EIO;
+	}
 
 	/* next rtc irq must not be from previous alarm setting */
 	cmos_irq_disable(cmos, RTC_AIE);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 3/7] rtc-mc146818-lib: fix RTC presence check
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH RESEND 1/7] rtc-cmos: take rtc_lock while reading from CMOS Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 2/7] rtc-cmos: dont touch alarm registers during update Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 4/7] rtc-mc146818-lib: reduce RTC_UIP polling period Mateusz Jończyk
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Thomas Gleixner, Alessandro Zummo,
	Alexandre Belloni

To prevent an infinite loop in mc146818_get_time(),
commit 211e5db19d15 ("rtc: mc146818: Detect and handle broken RTCs")
added a check for RTC availability. Together with a later fix, it
checked if bit 6 in register 0x0d is cleared. This, however, caused a
false negative on a motherboard with an AMD SB710 southbridge; according
to the specification [1], bit 6 of register 0x0d of this chipset is a
scratchbit.

This caused a regression in Linux 5.11 - the RTC was determined broken
by the kernel and not used by rtc-cmos.c [3].

As a better alternative, check whether the UIP ("Update-in-progress")
bit is set for longer then 10ms. If that is the case, then apparently
the RTC is either absent (and all register reads return 0xff) or broken.
Also limit the number of loop iterations in mc146818_get_time() to 10.

In a previous approach to this problem, I implemented a check whether
the RTC_HOURS register contains a value <= 24. This, however, sometimes
did not work correctly on my Intel Kaby Lake laptop. According to
Intel's documentation [2], "the time and date RAM locations (0-9) are
disconnected from the external bus" during the update cycle so reading
this register without checking the UIP bit is incorrect.

[1] AMD SB700/710/750 Register Reference Guide, page 308,
https://developer.amd.com/wordpress/media/2012/10/43009_sb7xx_rrg_pub_1.00.pdf

[2] 7th Generation Intel ® Processor Family I/O for U/Y Platforms [...] Datasheet
Volume 1 of 2, page 209
Intel's Document Number: 334658-006,
https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/7th-and-8th-gen-core-family-mobile-u-y-processor-lines-i-o-datasheet-vol-1.pdf

[3] Functions in arch/x86/kernel/rtc.c apparently were using it.

Fixes: 211e5db19d15 ("rtc: mc146818: Detect and handle broken RTCs")
Fixes: ebb22a059436 ("rtc: mc146818: Dont test for bit 0-5 in Register D")
Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>

---
 drivers/rtc/rtc-cmos.c         | 10 ++++------
 drivers/rtc/rtc-mc146818-lib.c | 34 ++++++++++++++++++++++++++++++----
 include/linux/mc146818rtc.h    |  1 +
 3 files changed, 35 insertions(+), 10 deletions(-)

v2: Tweak commit description, remove "Cc: stable" (I'll send it manually
after more regression testing).

diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index 643433d984ab..f353a41dfe8c 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -833,16 +833,14 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
 
 	rename_region(ports, dev_name(&cmos_rtc.rtc->dev));
 
-	spin_lock_irq(&rtc_lock);
-
-	/* Ensure that the RTC is accessible. Bit 6 must be 0! */
-	if ((CMOS_READ(RTC_VALID) & 0x40) != 0) {
-		spin_unlock_irq(&rtc_lock);
-		dev_warn(dev, "not accessible\n");
+	if (!mc146818_does_rtc_work()) {
+		dev_warn(dev, "broken or not accessible\n");
 		retval = -ENXIO;
 		goto cleanup1;
 	}
 
+	spin_lock_irq(&rtc_lock);
+
 	if (!(flags & CMOS_RTC_FLAGS_NOFREQ)) {
 		/* force periodic irq to CMOS reset default of 1024Hz;
 		 *
diff --git a/drivers/rtc/rtc-mc146818-lib.c b/drivers/rtc/rtc-mc146818-lib.c
index dcfaf09946ee..9175e11baf26 100644
--- a/drivers/rtc/rtc-mc146818-lib.c
+++ b/drivers/rtc/rtc-mc146818-lib.c
@@ -8,10 +8,35 @@
 #include <linux/acpi.h>
 #endif
 
+/*
+ * If the UIP (Update-in-progress) bit of the RTC is set for more then
+ * 10ms, the RTC apparently is broken or not present.
+ */
+unsigned int mc146818_does_rtc_work(void)
+{
+	int i;
+	unsigned char val;
+	unsigned long flags;
+
+	for (i = 0; i < 20; i++) {
+		spin_lock_irqsave(&rtc_lock, flags);
+		val = CMOS_READ(RTC_FREQ_SELECT);
+		spin_unlock_irqrestore(&rtc_lock, flags);
+
+		if ((val & RTC_UIP) == 0)
+			return 1;
+
+		udelay(500);
+	}
+
+	return 0;
+}
+
 unsigned int mc146818_get_time(struct rtc_time *time)
 {
 	unsigned char ctrl;
 	unsigned long flags;
+	unsigned int iter_count = 0;
 	unsigned char century = 0;
 	bool retry;
 
@@ -20,13 +45,14 @@ unsigned int mc146818_get_time(struct rtc_time *time)
 #endif
 
 again:
-	spin_lock_irqsave(&rtc_lock, flags);
-	/* Ensure that the RTC is accessible. Bit 6 must be 0! */
-	if (WARN_ON_ONCE((CMOS_READ(RTC_VALID) & 0x40) != 0)) {
-		spin_unlock_irqrestore(&rtc_lock, flags);
+	if (iter_count > 10) {
+		pr_err_ratelimited("Unable to read current time from RTC\n");
 		memset(time, 0xff, sizeof(*time));
 		return 0;
 	}
+	iter_count++;
+
+	spin_lock_irqsave(&rtc_lock, flags);
 
 	/*
 	 * Check whether there is an update in progress during which the
diff --git a/include/linux/mc146818rtc.h b/include/linux/mc146818rtc.h
index 0661af17a758..046a03df1e56 100644
--- a/include/linux/mc146818rtc.h
+++ b/include/linux/mc146818rtc.h
@@ -123,6 +123,7 @@ struct cmos_rtc_board_info {
 #define RTC_IO_EXTENT_USED      RTC_IO_EXTENT
 #endif /* ARCH_RTC_LOCATION */
 
+unsigned int mc146818_does_rtc_work(void);
 unsigned int mc146818_get_time(struct rtc_time *time);
 int mc146818_set_time(struct rtc_time *time);
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/7] rtc-mc146818-lib: reduce RTC_UIP polling period
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
                   ` (2 preceding siblings ...)
  2021-09-12 12:42 ` [PATCH v2 3/7] rtc-mc146818-lib: fix RTC presence check Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 5/7] x86/rtc: mach_get_cmos_time - rm duplicated code Mateusz Jończyk
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Alessandro Zummo, Alexandre Belloni,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
	H. Peter Anvin

A shorter period makes mc146818_get_time() more similar to
mach_get_cmos_time() in arch/x86/kernel/rtc.c, which performs the same
function, but is busy waiting for the RTC_UIP bit to clear.

Waiting 1ms every time is not necessary, for example on some AMD boxes
the RTC_UIP bit is documented as being high for around 270 microseconds
in some cases [1], which agreed with experiments on an SB710
southbridge. So 100us seems optimal.

[1] AMD SB700/710/750 Register Reference Guide, page 307,
https://developer.amd.com/wordpress/media/2012/10/43009_sb7xx_rrg_pub_1.00.pdf

        "SB700 A12: The UIP high pulse is 270 μS Typical when SS on SRC
        clock is OFF and 100μ min when SRC SS is ON." [sic]

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: x86@kernel.org
Cc: "H. Peter Anvin" <hpa@zytor.com>
---
 drivers/rtc/rtc-mc146818-lib.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/rtc/rtc-mc146818-lib.c b/drivers/rtc/rtc-mc146818-lib.c
index 9175e11baf26..d1c42b0ef662 100644
--- a/drivers/rtc/rtc-mc146818-lib.c
+++ b/drivers/rtc/rtc-mc146818-lib.c
@@ -45,7 +45,7 @@ unsigned int mc146818_get_time(struct rtc_time *time)
 #endif
 
 again:
-	if (iter_count > 10) {
+	if (iter_count > 100) {
 		pr_err_ratelimited("Unable to read current time from RTC\n");
 		memset(time, 0xff, sizeof(*time));
 		return 0;
@@ -57,7 +57,7 @@ unsigned int mc146818_get_time(struct rtc_time *time)
 	/*
 	 * Check whether there is an update in progress during which the
 	 * readout is unspecified. The maximum update time is ~2ms. Poll
-	 * every msec for completion.
+	 * every 100 usec for completion.
 	 *
 	 * Store the second value before checking UIP so a long lasting NMI
 	 * which happens to hit after the UIP check cannot make an update
@@ -67,7 +67,7 @@ unsigned int mc146818_get_time(struct rtc_time *time)
 
 	if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) {
 		spin_unlock_irqrestore(&rtc_lock, flags);
-		mdelay(1);
+		udelay(100);
 		goto again;
 	}
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 5/7] x86/rtc: mach_get_cmos_time - rm duplicated code
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
                   ` (3 preceding siblings ...)
  2021-09-12 12:42 ` [PATCH 4/7] rtc-mc146818-lib: reduce RTC_UIP polling period Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 6/7] x86/rtc: rename mach_set_rtc_mmss Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ" Mateusz Jończyk
  6 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, x86, H. Peter Anvin

There are functions in drivers/rtc/rtc-mc146818-lib.c that handle
reading from / writing to the CMOS RTC clock. mach_get_cmos_time() in
arch/x86/kernel/rtc.c did not use them and was mostly a duplicate of
mc146818_get_time(). Modify mach_get_cmos_time() to use
mc146818_get_time() and remove the duplicated code.

mach_get_cmos_time() used a different algorithm than
mc146818_get_time(), but these functions are equivalent. The major
differences are:

- mc146818_get_time() is better refined: it was updated in
  commit 05a0302c3548 ("rtc: mc146818: Prevent reading garbage")
  to take account of various edge conditions,

- when the UIP ("Update in progress") bit of the RTC is set,
  mach_get_cmos_time() was busy waiting with cpu_relax() while
  mc146818_get_time() is now using udelay(100) in every loop iteration,

- mach_get_cmos_time() assumed that the RTC year must be >= 2000, which
  may not be true on some old boxes with a dead battery.

The RTC writing counterpart, mach_set_rtc_mmss() is already using
mc146818_get_time() from drivers/rtc. This was done in
        commit 3195ef59cb42 ("x86: Do full rtc synchronization with ntp")
It appears that mach_get_cmos_time() was simply forgotten.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: x86@kernel.org
Cc: "H. Peter Anvin" <hpa@zytor.com>
---
 arch/x86/kernel/rtc.c | 55 +++++--------------------------------------
 1 file changed, 6 insertions(+), 49 deletions(-)

diff --git a/arch/x86/kernel/rtc.c b/arch/x86/kernel/rtc.c
index 586f718b8e95..bd170362edef 100644
--- a/arch/x86/kernel/rtc.c
+++ b/arch/x86/kernel/rtc.c
@@ -20,15 +20,12 @@
 /*
  * This is a special lock that is owned by the CPU and holds the index
  * register we are working with.  It is required for NMI access to the
- * CMOS/RTC registers.  See include/asm-i386/mc146818rtc.h for details.
+ * CMOS/RTC registers.  See arch/x86/include/asm/mc146818rtc.h for details.
  */
 volatile unsigned long cmos_lock;
 EXPORT_SYMBOL(cmos_lock);
 #endif /* CONFIG_X86_32 */
 
-/* For two digit years assume time is always after that */
-#define CMOS_YEARS_OFFS 2000
-
 DEFINE_SPINLOCK(rtc_lock);
 EXPORT_SYMBOL(rtc_lock);
 
@@ -62,8 +59,7 @@ int mach_set_rtc_mmss(const struct timespec64 *now)
 
 void mach_get_cmos_time(struct timespec64 *now)
 {
-	unsigned int status, year, mon, day, hour, min, sec, century = 0;
-	unsigned long flags;
+	struct rtc_time tm;
 
 	/*
 	 * If pm_trace abused the RTC as storage, set the timespec to 0,
@@ -74,51 +70,12 @@ void mach_get_cmos_time(struct timespec64 *now)
 		return;
 	}
 
-	spin_lock_irqsave(&rtc_lock, flags);
-
-	/*
-	 * If UIP is clear, then we have >= 244 microseconds before
-	 * RTC registers will be updated.  Spec sheet says that this
-	 * is the reliable way to read RTC - registers. If UIP is set
-	 * then the register access might be invalid.
-	 */
-	while ((CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))
-		cpu_relax();
-
-	sec = CMOS_READ(RTC_SECONDS);
-	min = CMOS_READ(RTC_MINUTES);
-	hour = CMOS_READ(RTC_HOURS);
-	day = CMOS_READ(RTC_DAY_OF_MONTH);
-	mon = CMOS_READ(RTC_MONTH);
-	year = CMOS_READ(RTC_YEAR);
-
-#ifdef CONFIG_ACPI
-	if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID &&
-	    acpi_gbl_FADT.century)
-		century = CMOS_READ(acpi_gbl_FADT.century);
-#endif
-
-	status = CMOS_READ(RTC_CONTROL);
-	WARN_ON_ONCE(RTC_ALWAYS_BCD && (status & RTC_DM_BINARY));
-
-	spin_unlock_irqrestore(&rtc_lock, flags);
-
-	if (RTC_ALWAYS_BCD || !(status & RTC_DM_BINARY)) {
-		sec = bcd2bin(sec);
-		min = bcd2bin(min);
-		hour = bcd2bin(hour);
-		day = bcd2bin(day);
-		mon = bcd2bin(mon);
-		year = bcd2bin(year);
+	if (!mc146818_get_time(&tm)) {
+		now->tv_sec = now->tv_nsec = 0;
+		return;
 	}
 
-	if (century) {
-		century = bcd2bin(century);
-		year += century * 100;
-	} else
-		year += CMOS_YEARS_OFFS;
-
-	now->tv_sec = mktime64(year, mon, day, hour, min, sec);
+	now->tv_sec = rtc_tm_to_time64(&tm);
 	now->tv_nsec = 0;
 }
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 6/7] x86/rtc: rename mach_set_rtc_mmss
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
                   ` (4 preceding siblings ...)
  2021-09-12 12:42 ` [PATCH 5/7] x86/rtc: mach_get_cmos_time - rm duplicated code Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-12 12:42 ` [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ" Mateusz Jończyk
  6 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, x86, H. Peter Anvin

Once upon a time, before
commit 3195ef59cb42 ("x86: Do full rtc synchronization with ntp")
in 2013, the function set only the minute and seconds registers
of the CMOS RTC. This is no longer true, so rename the function to
mach_set_cmos_time.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: x86@kernel.org
Cc: "H. Peter Anvin" <hpa@zytor.com>
---
 arch/x86/include/asm/mc146818rtc.h | 2 +-
 arch/x86/kernel/rtc.c              | 4 ++--
 arch/x86/kernel/x86_init.c         | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/mc146818rtc.h b/arch/x86/include/asm/mc146818rtc.h
index 97198001e567..6115bb3d5795 100644
--- a/arch/x86/include/asm/mc146818rtc.h
+++ b/arch/x86/include/asm/mc146818rtc.h
@@ -95,7 +95,7 @@ static inline unsigned char current_lock_cmos_reg(void)
 unsigned char rtc_cmos_read(unsigned char addr);
 void rtc_cmos_write(unsigned char val, unsigned char addr);
 
-extern int mach_set_rtc_mmss(const struct timespec64 *now);
+extern int mach_set_cmos_time(const struct timespec64 *now);
 extern void mach_get_cmos_time(struct timespec64 *now);
 
 #define RTC_IRQ 8
diff --git a/arch/x86/kernel/rtc.c b/arch/x86/kernel/rtc.c
index bd170362edef..17aa575a2c38 100644
--- a/arch/x86/kernel/rtc.c
+++ b/arch/x86/kernel/rtc.c
@@ -30,13 +30,13 @@ DEFINE_SPINLOCK(rtc_lock);
 EXPORT_SYMBOL(rtc_lock);
 
 /*
- * In order to set the CMOS clock precisely, set_rtc_mmss has to be
+ * In order to set the CMOS clock precisely, mach_set_cmos_time has to be
  * called 500 ms after the second nowtime has started, because when
  * nowtime is written into the registers of the CMOS clock, it will
  * jump to the next second precisely 500 ms later. Check the Motorola
  * MC146818A or Dallas DS12887 data sheet for details.
  */
-int mach_set_rtc_mmss(const struct timespec64 *now)
+int mach_set_cmos_time(const struct timespec64 *now)
 {
 	unsigned long long nowtime = now->tv_sec;
 	struct rtc_time tm;
diff --git a/arch/x86/kernel/x86_init.c b/arch/x86/kernel/x86_init.c
index 8b395821cb8d..8b597c39cf3a 100644
--- a/arch/x86/kernel/x86_init.c
+++ b/arch/x86/kernel/x86_init.c
@@ -133,7 +133,7 @@ struct x86_platform_ops x86_platform __ro_after_init = {
 	.calibrate_cpu			= native_calibrate_cpu_early,
 	.calibrate_tsc			= native_calibrate_tsc,
 	.get_wallclock			= mach_get_cmos_time,
-	.set_wallclock			= mach_set_rtc_mmss,
+	.set_wallclock			= mach_set_cmos_time,
 	.iommu_shutdown			= iommu_shutdown_noop,
 	.is_untracked_pat_range		= is_ISA_range,
 	.nmi_init			= default_nmi_init,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ"
  2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
                   ` (5 preceding siblings ...)
  2021-09-12 12:42 ` [PATCH 6/7] x86/rtc: rename mach_set_rtc_mmss Mateusz Jończyk
@ 2021-09-12 12:42 ` Mateusz Jończyk
  2021-09-14  8:02   ` Ville Syrjälä
  6 siblings, 1 reply; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:42 UTC (permalink / raw)
  To: linux-rtc
  Cc: Mateusz Jończyk, Alessandro Zummo, Alexandre Belloni,
	Ville Syrjälä,
	Xiaofei Tan

Revert
commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
and add a comment.

As described in a previous
commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
from February 2020:

        cmos_interrupt() isn't always called from hardirq context, so
        we must use spin_lock_irqsave() & co.

Indeed, cmos_interrupt() is called from cmos_check_wkalrm(), which is
called from cmos_resume() - apparently not in an interrupt context.

A later
commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
did not take account of this and changed spin_lock_irqsave() to spin_lock().
This may cause a deadlock as quoted in the body of
commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
mentioned earlier.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Cc: Xiaofei Tan <tanxiaofei@huawei.com>
---
 drivers/rtc/rtc-cmos.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index c24465f7bed4..508fba8746a1 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -741,10 +741,14 @@ static struct cmos_rtc	cmos_rtc;
 
 static irqreturn_t cmos_interrupt(int irq, void *p)
 {
+	unsigned long	flags;
 	u8		irqstat;
 	u8		rtc_control;
 
-	spin_lock(&rtc_lock);
+	/* cmos_interrupt() may be called from cmos_check_wkalrm() not in
+	 * interrupt context, so using spin_lock_irqsave() is required
+	 */
+	spin_lock_irqsave(&rtc_lock, flags);
 
 	/* When the HPET interrupt handler calls us, the interrupt
 	 * status is passed as arg1 instead of the irq number.  But
@@ -778,7 +782,7 @@ static irqreturn_t cmos_interrupt(int irq, void *p)
 			hpet_mask_rtc_irq_bit(RTC_AIE);
 		CMOS_READ(RTC_INTR_FLAGS);
 	}
-	spin_unlock(&rtc_lock);
+	spin_unlock_irqrestore(&rtc_lock, flags);
 
 	if (is_intr(irqstat)) {
 		rtc_update_irq(p, 1, irqstat);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [TEST PATCH] rtc-cmos: cmos_read_alarm bug demonstration
  2021-09-12 12:42 ` [PATCH 2/7] rtc-cmos: dont touch alarm registers during update Mateusz Jończyk
@ 2021-09-12 12:44   ` Mateusz Jończyk
  0 siblings, 0 replies; 11+ messages in thread
From: Mateusz Jończyk @ 2021-09-12 12:44 UTC (permalink / raw)
  To: linux-rtc; +Cc: Mateusz Jończyk, Alessandro Zummo, Alexandre Belloni

Before my commit "rtc-cmos: dont touch alarm registers during update",
applying this patch and reading the CMOS time like so:

        while true; do cat /sys/class/rtc/rtc0/time ; sleep 0.5; done

produces errors in dmesg.

Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>

---
 drivers/rtc/rtc-cmos.c | 61 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c
index f353a41dfe8c..c24465f7bed4 100644
--- a/drivers/rtc/rtc-cmos.c
+++ b/drivers/rtc/rtc-cmos.c
@@ -43,6 +43,9 @@
 #include <linux/dmi.h>
 #endif
 
+#include <linux/ktime.h>
+#include <linux/timekeeping.h>
+
 /* this is for "generic access to PC-style RTC" using CMOS_READ/CMOS_WRITE */
 #include <linux/mc146818rtc.h>
 
@@ -220,6 +223,8 @@ static inline void cmos_write_bank2(unsigned char val, unsigned char addr)
 
 /*----------------------------------------------------------------*/
 
+static void cmos_read_alarm_uip_debugging(struct device *dev);
+
 static int cmos_read_time(struct device *dev, struct rtc_time *t)
 {
 	/*
@@ -234,6 +239,8 @@ static int cmos_read_time(struct device *dev, struct rtc_time *t)
 	 * That'll make Y3K compatility (year > 2070) easy!
 	 */
 	mc146818_get_time(t);
+
+	cmos_read_alarm_uip_debugging(dev);
 	return 0;
 }
 
@@ -348,6 +355,60 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
 	return 0;
 }
 
+static void cmos_read_alarm_uip_debugging(struct device *dev)
+{
+	unsigned long	flags;
+	unsigned char	rtc_uip_baseline, rtc_uip;
+	struct rtc_wkalrm t_baseline, t;
+	ktime_t time_start, time_end;
+	int i;
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	rtc_uip_baseline = CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP;
+	spin_unlock_irqrestore(&rtc_lock, flags);
+
+	cmos_read_alarm(dev, &t_baseline);
+
+	time_start = ktime_get();
+
+	for (i = 0; i < 2000; i++) {
+		spin_lock_irqsave(&rtc_lock, flags);
+		rtc_uip = CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP;
+		spin_unlock_irqrestore(&rtc_lock, flags);
+
+		cmos_read_alarm(dev, &t);
+
+		if (t_baseline.time.tm_sec != t.time.tm_sec) {
+			dev_err(dev,
+				"Inconsistent alarm tm_sec value: %d != %d (RTC_UIP = %d; %d)\n",
+				t_baseline.time.tm_sec,
+				t.time.tm_sec,
+				rtc_uip_baseline, rtc_uip);
+		}
+		if (t_baseline.time.tm_min != t.time.tm_min) {
+			dev_err(dev,
+				"Inconsistent alarm tm_min value: %d != %d (RTC_UIP = %d; %d)\n",
+				t_baseline.time.tm_min,
+				t.time.tm_min,
+				rtc_uip_baseline, rtc_uip);
+		}
+		if (t_baseline.time.tm_hour != t.time.tm_hour) {
+			dev_err(dev,
+				"Inconsistent alarm tm_hour value: %d != %d (RTC_UIP = %d; %d)\n",
+				t_baseline.time.tm_hour,
+				t.time.tm_hour,
+				rtc_uip_baseline, rtc_uip);
+		}
+
+	}
+
+	time_end = ktime_get();
+
+	pr_info_ratelimited("%s: loop executed in %lld ns\n",
+			__func__, ktime_to_ns(ktime_sub(time_end, time_start)));
+}
+
+
 static void cmos_checkintr(struct cmos_rtc *cmos, unsigned char rtc_control)
 {
 	unsigned char	rtc_intr;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ"
  2021-09-12 12:42 ` [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ" Mateusz Jończyk
@ 2021-09-14  8:02   ` Ville Syrjälä
  2021-09-14  8:22     ` Alexandre Belloni
  0 siblings, 1 reply; 11+ messages in thread
From: Ville Syrjälä @ 2021-09-14  8:02 UTC (permalink / raw)
  To: Mateusz Jończyk
  Cc: linux-rtc, Alessandro Zummo, Alexandre Belloni, Xiaofei Tan

On Sun, Sep 12, 2021 at 02:42:14PM +0200, Mateusz Jończyk wrote:
> Revert
> commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
> and add a comment.
> 
> As described in a previous
> commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
> from February 2020:
> 
>         cmos_interrupt() isn't always called from hardirq context, so
>         we must use spin_lock_irqsave() & co.
> 
> Indeed, cmos_interrupt() is called from cmos_check_wkalrm(), which is
> called from cmos_resume() - apparently not in an interrupt context.
> 
> A later
> commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
> did not take account of this and changed spin_lock_irqsave() to spin_lock().
> This may cause a deadlock as quoted in the body of
> commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
> mentioned earlier.

This regression was supposed to be fixed by
https://lore.kernel.org/linux-rtc/20210305122140.28774-1-chris@chris-wilson.co.uk/

What happened to that one? I don't see it in Linus's tree...

-- 
Ville Syrjälä
Intel

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ"
  2021-09-14  8:02   ` Ville Syrjälä
@ 2021-09-14  8:22     ` Alexandre Belloni
  0 siblings, 0 replies; 11+ messages in thread
From: Alexandre Belloni @ 2021-09-14  8:22 UTC (permalink / raw)
  To: Ville Syrjälä
  Cc: Mateusz Jończyk, linux-rtc, Alessandro Zummo, Xiaofei Tan

On 14/09/2021 11:02:55+0300, Ville Syrjälä wrote:
> On Sun, Sep 12, 2021 at 02:42:14PM +0200, Mateusz Jończyk wrote:
> > Revert
> > commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
> > and add a comment.
> > 
> > As described in a previous
> > commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
> > from February 2020:
> > 
> >         cmos_interrupt() isn't always called from hardirq context, so
> >         we must use spin_lock_irqsave() & co.
> > 
> > Indeed, cmos_interrupt() is called from cmos_check_wkalrm(), which is
> > called from cmos_resume() - apparently not in an interrupt context.
> > 
> > A later
> > commit 6950d046eb6e ("rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ")
> > did not take account of this and changed spin_lock_irqsave() to spin_lock().
> > This may cause a deadlock as quoted in the body of
> > commit 66e4f4a9cc38 ("rtc: cmos: Use spin_lock_irqsave() in cmos_interrupt()")
> > mentioned earlier.
> 
> This regression was supposed to be fixed by
> https://lore.kernel.org/linux-rtc/20210305122140.28774-1-chris@chris-wilson.co.uk/
> 
> What happened to that one? I don't see it in Linus's tree...
> 

Yes, that's exactly what I was wondering when seeing that patch and it
still sits in my rtc-fixes branch that I forgot to send to Linus...

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2021-09-14  8:22 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-12 12:42 rtc,x86: CMOS RTC fixes Mateusz Jończyk
2021-09-12 12:42 ` [PATCH RESEND 1/7] rtc-cmos: take rtc_lock while reading from CMOS Mateusz Jończyk
2021-09-12 12:42 ` [PATCH 2/7] rtc-cmos: dont touch alarm registers during update Mateusz Jończyk
2021-09-12 12:44   ` [TEST PATCH] rtc-cmos: cmos_read_alarm bug demonstration Mateusz Jończyk
2021-09-12 12:42 ` [PATCH v2 3/7] rtc-mc146818-lib: fix RTC presence check Mateusz Jończyk
2021-09-12 12:42 ` [PATCH 4/7] rtc-mc146818-lib: reduce RTC_UIP polling period Mateusz Jończyk
2021-09-12 12:42 ` [PATCH 5/7] x86/rtc: mach_get_cmos_time - rm duplicated code Mateusz Jończyk
2021-09-12 12:42 ` [PATCH 6/7] x86/rtc: rename mach_set_rtc_mmss Mateusz Jończyk
2021-09-12 12:42 ` [PATCH 7/7] Revert "rtc: cmos: Replace spin_lock_irqsave with spin_lock in hard IRQ" Mateusz Jończyk
2021-09-14  8:02   ` Ville Syrjälä
2021-09-14  8:22     ` Alexandre Belloni

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.