Skip to contentSkip to conversation

3.8-rc regression with pps-ldisc due to 70ece7a731

PATCH Superseded George Spelvin 3 participants 11 messages Feb 8, 2013 – Feb 9, 2013

Subsystems (3)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover 3.8-rc regression with pps-ldisc due to 70ece7a731

The standard N_TTY line discipline used to not use the tty->disc_data field, so N_PPS felt free to use it. That has now changed, requiring that N_PPS use a different method to find its private data.

(In the current, buggy, state, N_PPS follows a wild pointer and explodes in an interrupt hander as soon as a pulse actually arrives.)

Strictly speaking, only patches 5 and 6 are the bugfix proper; the rest are various cleanups enabled by the looser coupling of the line disciplines.

George Spelvin (4):
pps: Additional cleanups in uart_handle_dcd_change
pps: Add pps_lookup_dev() function
pps: Use pps_lookup_dev to reduce ldisc coupling tty/tty_ldisc.c: use test_and_clear_bit in tty_ldisc_close

Peter Hurley (3):
pps: Decouple N_PPS from N_TTY
pps: Don't crash the machine when exiting will do
tty: Remove ancient hardpps()

 drivers/pps/clients/pps-ldisc.c     | 33 +++++++++++++++++++++++----------
 drivers/pps/pps.c                   | 35 +++++++++++++++++++++++++++++++++++
 drivers/staging/speakup/selection.c |  1 +
 drivers/tty/amiserial.c             |  5 -----
 drivers/tty/n_tty.c                 |  3 ++-
 drivers/tty/serial/serial_core.c    | 24 +++++++-----------------
 drivers/tty/tty_buffer.c            |  1 +
 drivers/tty/tty_ldisc.c             |  3 +--
 include/linux/pps_kernel.h          | 17 ++++++++++++++---
 include/linux/serial_core.h         |  1 -
 include/linux/tty_ldisc.h           | 11 ++++-------
 11 files changed, 88 insertions(+), 46 deletions(-)
Signature · 1 line
1.8.1.2
#1 lore raw

PATCH 1/7 pps: Decouple N_PPS from N_TTY

The PPS (Pulse-Per-Second) line discipline has developed a number of unhealthy attachments to core tty data and functions, ultimately leading to its breakage.

This first part eliminates the timestamp parameter from the dcd_change ldisc method. This reduces header file linkage and makes the extension more generic, and the timestamp read is delayed only slightly, from just before the ldisc->ops->dcd_change method call to just after.

Fix attendant build breakage in
drivers/tty/n_tty.c
drivers/tty/tty_buffer.c
drivers/staging/speakup/selection.c

Cc William Hubbs <[email protected]>
Cc Chris Brannon <[email protected]>
Cc Kirk Reiser <[email protected]>
Cc Samuel Thibault <[email protected]>
---
 drivers/pps/clients/pps-ldisc.c     |  8 +++++---
 drivers/staging/speakup/selection.c |  1 +
 drivers/tty/n_tty.c                 |  3 ++-
 drivers/tty/serial/serial_core.c    |  7 +++----
 drivers/tty/tty_buffer.c            |  1 +
 include/linux/serial_core.h         |  1 -
 include/linux/tty_ldisc.h           | 11 ++++-------
 7 files changed, 16 insertions(+), 16 deletions(-)
drivers/pps/clients/pps-ldisc.c +5 -3
index 79451f2..27d7ca1 100644
@@ -28,15 +28,17 @@
 
 #define PPS_TTY_MAGIC		0x0001
 
-static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status,
-				struct pps_event_time *ts)
+static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status)
 {
+	struct pps_event_time ts;
 	struct pps_device *pps = (struct pps_device *)tty->disc_data;
 
+	pps_get_ts(&ts);
+
 	BUG_ON(pps == NULL);
 
 	/* Now do the PPS event report */
-	pps_event(pps, ts, status ? PPS_CAPTUREASSERT :
+	pps_event(pps, &ts, status ? PPS_CAPTUREASSERT :
 			PPS_CAPTURECLEAR, NULL);
 
 	dev_dbg(pps->dev, "PPS %s at %lu\n",
drivers/staging/speakup/selection.c +1 -0
index 0612df0..2aa2237 100644
@@ -2,6 +2,7 @@
 #include <linux/consolemap.h>
 #include <linux/interrupt.h>
 #include <linux/sched.h>
+#include <linux/device.h> /* for dev_warn */
 #include <linux/selection.h>
 
 #include "speakup.h"
drivers/tty/n_tty.c +2 -1
index 19083ef..ed66b20 100644
@@ -49,6 +49,7 @@
 #include <linux/file.h>
 #include <linux/uaccess.h>
 #include <linux/module.h>
+#include <linux/ratelimit.h>
 
 
 /* number of characters left in xmit buffer before select has we have room */
@@ -2188,7 +2189,7 @@ struct tty_ldisc_ops tty_ldisc_N_TTY = {
  *	n_tty_inherit_ops	-	inherit N_TTY methods
  *	@ops: struct tty_ldisc_ops where to save N_TTY methods
  *
- *	Used by a generic struct tty_ldisc_ops to easily inherit N_TTY
+ *	Enables a 'subclass' line discipline to 'inherit' N_TTY
  *	methods.
  */
 
drivers/tty/serial/serial_core.c +3 -4
index 2c7230a..8b6bff5 100644
@@ -2718,13 +2718,14 @@ void uart_handle_dcd_change(struct uart_port *uport, unsigned int status)
 	struct uart_state *state = uport->state;
 	struct tty_port *port = &state->port;
 	struct tty_ldisc *ld = NULL;
-	struct pps_event_time ts;
 	struct tty_struct *tty = port->tty;
 
 	if (tty)
 	        ld = tty_ldisc_ref(tty);
-	if (ld && ld->ops->dcd_change)
+	if (ld && ld->ops->dcd_change) {
 		pps_get_ts(&ts);
+		ld->ops->dcd_change(tty, status);
+	}
 
 	uport->icount.dcd++;
 #ifdef CONFIG_HARD_PPS
@@ -2739,8 +2740,6 @@ void uart_handle_dcd_change(struct uart_port *uport, unsigned int status)
 			tty_hangup(tty);
 	}
 
-	if (ld && ld->ops->dcd_change)
-		ld->ops->dcd_change(tty, status, &ts);
 	if (ld)
 		tty_ldisc_deref(ld);
 }
drivers/tty/tty_buffer.c +1 -0
index 45d9161..1bdd896 100644
@@ -16,6 +16,7 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/module.h>
+#include <linux/ratelimit.h>
 
 /**
  *	tty_buffer_free_all		-	free buffers used by a tty
include/linux/serial_core.h +0 -1
index c6690a2..a5f1da9 100644
@@ -29,7 +29,6 @@
 #include <linux/tty.h>
 #include <linux/mutex.h>
 #include <linux/sysrq.h>
-#include <linux/pps_kernel.h>
 #include <uapi/linux/serial_core.h>
 
 struct uart_port;
include/linux/tty_ldisc.h +4 -7
index fb79dd8d..455a0d7 100644
@@ -100,16 +100,14 @@
  *	seek to perform this action quickly but should wait until
  *	any pending driver I/O is completed.
  *
- * void (*dcd_change)(struct tty_struct *tty, unsigned int status,
- *			struct pps_event_time *ts)
+ * void (*dcd_change)(struct tty_struct *tty, unsigned int status)
  *
- *	Tells the discipline that the DCD pin has changed its status and
- *	the relative timestamp. Pointer ts cannot be NULL.
+ *	Tells the discipline that the DCD pin has changed its status.
+ *	Used exclusively by the N_PPS (Pulse-Per-Second) line discipline.
  */
 
 #include <linux/fs.h>
 #include <linux/wait.h>
-#include <linux/pps_kernel.h>
 #include <linux/wait.h>
 
 struct tty_ldisc_ops {
@@ -144,8 +142,7 @@ struct tty_ldisc_ops {
 	void	(*receive_buf)(struct tty_struct *, const unsigned char *cp,
 			       char *fp, int count);
 	void	(*write_wakeup)(struct tty_struct *);
-	void	(*dcd_change)(struct tty_struct *, unsigned int,
-				struct pps_event_time *);
+	void	(*dcd_change)(struct tty_struct *, unsigned int);
 
 	struct  module *owner;
 	
Signature · 1 line
1.8.1.2
#2 lore raw

PATCH 2/7 pps: Additional cleanups in uart_handle_dcd_change

In reply to George Spelvin

An extension of the previous commit, there is no semantic change here, just 5 fewer lines of source code.

---
 drivers/tty/serial/serial_core.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)
drivers/tty/serial/serial_core.c +6 -11
index 8b6bff5..b3a204b 100644
@@ -2715,16 +2715,14 @@ EXPORT_SYMBOL(uart_match_port);
  */
 void uart_handle_dcd_change(struct uart_port *uport, unsigned int status)
 {
-	struct uart_state *state = uport->state;
-	struct tty_port *port = &state->port;
-	struct tty_ldisc *ld = NULL;
+	struct tty_port *port = &uport->state->port;
 	struct tty_struct *tty = port->tty;
+	struct tty_ldisc *ld = tty ? tty_ldisc_ref(tty) : NULL;
 
-	if (tty)
-	        ld = tty_ldisc_ref(tty);
-	if (ld && ld->ops->dcd_change) {
-		pps_get_ts(&ts);
-		ld->ops->dcd_change(tty, status);
+	if (ld) {
+		if (ld->ops->dcd_change)
+			ld->ops->dcd_change(tty, status);
+		tty_ldisc_deref(ld);
 	}
 
 	uport->icount.dcd++;
@@ -2739,9 +2737,6 @@ void uart_handle_dcd_change(struct uart_port *uport, unsigned int status)
 		else if (tty)
 			tty_hangup(tty);
 	}
-
-	if (ld)
-		tty_ldisc_deref(ld);
 }
 EXPORT_SYMBOL_GPL(uart_handle_dcd_change);
 
Signature · 1 line
1.8.1.2
#3 lore raw

PATCH 3/7 pps: Don't crash the machine when exiting will do

In reply to George Spelvin

PPS is not really the must-have subsystem that warrants crashing the machine if the ldisc interface is broken.

---
 drivers/pps/clients/pps-ldisc.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
drivers/pps/clients/pps-ldisc.c +7 -1
index 27d7ca1..e5a702a 100644
@@ -25,6 +25,7 @@
 #include <linux/serial_core.h>
 #include <linux/tty.h>
 #include <linux/pps_kernel.h>
+#include <linux/bug.h>
 
 #define PPS_TTY_MAGIC		0x0001
 
@@ -35,7 +36,12 @@ static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status)
 
 	pps_get_ts(&ts);
 
-	BUG_ON(pps == NULL);
+	/*
+	 * This should never fail, but the ldisc locking is very
+	 * convoluted, so don't crash just in case.
+	 */
+	if (WARN_ON_ONCE(pps == NULL))
+		return;
 
 	/* Now do the PPS event report */
 	pps_event(pps, &ts, status ? PPS_CAPTUREASSERT :
Signature · 1 line
1.8.1.2
#4 lore raw

PATCH 5/7 pps: Add pps_lookup_dev() function

In reply to George Spelvin

The PPS serial line discipline wants to attach a PPS device to a tty without changing the tty code to add a struct pps_device * pointer.

Since the number of PPS devices in a typical system is generally very low (n=1 is by far the most common), it's practical to search the entire list of allocated pps devices. (We capture the timestamp before the lookup, so the timing isn't affected.)

It is a bit ugly that this function, which is part of the in-kernel PPS API, has to be in pps.c as opposed to kapi,c, but that's not something that affects users.

---
 drivers/pps/pps.c          | 35 +++++++++++++++++++++++++++++++++++
 include/linux/pps_kernel.h | 17 ++++++++++++++---
 2 files changed, 49 insertions(+), 3 deletions(-)
drivers/pps/pps.c +35 -0
index 2420d5a..9a87565 100644
@@ -357,6 +357,41 @@ void pps_unregister_cdev(struct pps_device *pps)
 }
 
 /*
+ * Look up a pps device by magic cookie.
+ * The cookie is usually a pointer to some enclosing device, but this
+ * code doesn't care; you should never be dereferencing it.
+ *
+ * This is a bit of a kludge that is currently used only by the PPS
+ * serial line discipline.  It may need to be tweaked when a second user
+ * is found.
+ *
+ * There is no function interface for setting the lookup_cookie field.
+ * It's initialized to NULL when the pps device is created, and if a
+ * client wants to use it, just fill it in afterward.
+ *
+ * A very important usage note is that the list used is the same as the
+ * list of assigned minor numbers.  Thus, a pps device can be looked up
+ * after pps_unregister_source time if there are open file handles preventing
+ * it from being deallocated.
+ *
+ * To ensure that this does not lead to duplicate cookies (which Would
+ * Be Bad), also set the cookie to NULL before unregistering.
+ */
+struct pps_device *pps_lookup_dev(void const *cookie)
+{
+	struct pps_device *pps;
+	unsigned id;
+
+	rcu_read_lock();
+	idr_for_each_entry(&pps_idr, pps, id)
+		if (cookie == pps->lookup_cookie)
+			break;
+	rcu_read_unlock();
+	return pps;
+}
+EXPORT_SYMBOL(pps_lookup_dev);
+
+/*
  * Module stuff
  */
 
include/linux/pps_kernel.h +14 -3
index 0cc45ae..7db3eb9 100644
@@ -43,7 +43,7 @@ struct pps_source_info {
 			int event, void *data);	/* PPS echo function */
 
 	struct module *owner;
-	struct device *dev;
+	struct device *dev;		/* Parent device for device_create */
 };
 
 struct pps_event_time {
@@ -69,6 +69,7 @@ struct pps_device {
 	wait_queue_head_t queue;		/* PPS event queue */
 
 	unsigned int id;			/* PPS source unique ID */
+	void const *lookup_cookie;		/* pps_lookup_dev only */
 	struct cdev cdev;
 	struct device *dev;
 	struct fasync_struct *async_queue;	/* fasync method */
@@ -82,16 +83,26 @@ struct pps_device {
 extern struct device_attribute pps_attrs[];
 
 /*
+ * Internal functions.
+ *
+ * These are not actually part of the exported API, but this is a
+ * convenient header file to put them in.
+ */
+
+extern int pps_register_cdev(struct pps_device *pps);
+extern void pps_unregister_cdev(struct pps_device *pps);
+
+/*
  * Exported functions
  */
 
 extern struct pps_device *pps_register_source(
 		struct pps_source_info *info, int default_params);
 extern void pps_unregister_source(struct pps_device *pps);
-extern int pps_register_cdev(struct pps_device *pps);
-extern void pps_unregister_cdev(struct pps_device *pps);
 extern void pps_event(struct pps_device *pps,
 		struct pps_event_time *ts, int event, void *data);
+/* Look up a pps device by magic cookie */
+struct pps_device *pps_lookup_dev(void const *cookie);
 
 static inline void timespec_to_pps_ktime(struct pps_ktime *kt,
 		struct timespec ts)
Signature · 1 line
1.8.1.2
#5 lore raw

PATCH 4/7 tty: Remove ancient hardpps()

In reply to George Spelvin

hardpps() functionality is provided through the N_PPS line discipline now. The new function signature was added in commit 025b40ab (2011-01-12). There was no previous macro or function hardpps(), at least since before the initial commit of v2.6.12 in 2005. It's unlikely this code has been compiled since.

---
 drivers/tty/amiserial.c          | 5 -----
 drivers/tty/serial/serial_core.c | 4 ----
 2 files changed, 9 deletions(-)
drivers/tty/amiserial.c +0 -5
index 9d7d00c..ba2e09e 100644
@@ -394,11 +394,6 @@ static void check_modem_status(struct serial_state *info)
 			icount->dsr++;
 		if (dstatus & SER_DCD) {
 			icount->dcd++;
-#ifdef CONFIG_HARD_PPS
-			if ((port->flags & ASYNC_HARDPPS_CD) &&
-			    !(status & SER_DCD))
-				hardpps();
-#endif
 		}
 		if (dstatus & SER_CTS)
 			icount->cts++;
drivers/tty/serial/serial_core.c +0 -4
index b3a204b..02be179 100644
@@ -2726,10 +2726,6 @@ void uart_handle_dcd_change(struct uart_port *uport, unsigned int status)
 	}
 
 	uport->icount.dcd++;
-#ifdef CONFIG_HARD_PPS
-	if ((uport->flags & UPF_HARDPPS_CD) && status)
-		hardpps();
-#endif
 
 	if (port->flags & ASYNC_CHECK_CD) {
 		if (status)
Signature · 1 line
1.8.1.2
#6 lore raw

PATCH 6/7 pps: Use pps_lookup_dev to reduce ldisc coupling

In reply to George Spelvin

Now that N_TTY uses tty->disc_data for its private data, 'subclass' ldiscs cannot use ->disc_data for their own private data. (This is a regression is v3.8-rc1)

Use pps_lookup_dev to associate the tty with the pps source instead.

---
 drivers/pps/clients/pps-ldisc.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
drivers/pps/clients/pps-ldisc.c +11 -6
index e5a702a..5c22c5f 100644
@@ -32,10 +32,12 @@
 static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status)
 {
 	struct pps_event_time ts;
-	struct pps_device *pps = (struct pps_device *)tty->disc_data;
+	struct pps_device *pps;
 
 	pps_get_ts(&ts);
 
+	pps = pps_lookup_dev(tty);
+
 	/*
 	 * This should never fail, but the ldisc locking is very
 	 * convoluted, so don't crash just in case.
@@ -75,9 +77,9 @@ static int pps_tty_open(struct tty_struct *tty)
 		pr_err("cannot register PPS source \"%s\"\n", info.path);
 		return -ENOMEM;
 	}
-	tty->disc_data = pps;
+	pps->lookup_cookie = tty;
 
-	/* Should open N_TTY ldisc too */
+	/* Now open the base class N_TTY ldisc */
 	ret = alias_n_tty_open(tty);
 	if (ret < 0) {
 		pr_err("cannot open tty ldisc \"%s\"\n", info.path);
@@ -89,7 +91,7 @@ static int pps_tty_open(struct tty_struct *tty)
 	return 0;
 
 err_unregister:
-	tty->disc_data = NULL;
+	pps->lookup_cookie = NULL;
 	pps_unregister_source(pps);
 	return ret;
 }
@@ -98,12 +100,15 @@ static void (*alias_n_tty_close)(struct tty_struct *tty);
 
 static void pps_tty_close(struct tty_struct *tty)
 {
-	struct pps_device *pps = (struct pps_device *)tty->disc_data;
+	struct pps_device *pps = pps_lookup_dev(tty);
 
 	alias_n_tty_close(tty);
 
-	tty->disc_data = NULL;
+	if (WARN_ON(!pps))
+		return;
+
 	dev_info(pps->dev, "removed\n");
+	pps->lookup_cookie = NULL;
 	pps_unregister_source(pps);
 }
 
Signature · 1 line
1.8.1.2
#7 lore raw

PATCH 7/7 tty/tty_ldisc.c: use test_and_clear_bit in tty_ldisc_close

In reply to George Spelvin

We have a function to test and clear a bit in one step, so use it.

---
 drivers/tty/tty_ldisc.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/tty/tty_ldisc.c +1 -2
index c578229..4606ab9 100644
@@ -453,8 +453,7 @@ static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
 
 static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
 {
-	WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
-	clear_bit(TTY_LDISC_OPEN, &tty->flags);
+	WARN_ON(!test_and_clear_bit(TTY_LDISC_OPEN, &tty->flags));
 	if (ld->ops->close)
 		ld->ops->close(tty);
 }
Signature · 1 line
1.8.1.2
maintainer
#8 lore raw
In reply to George Spelvin
Quoted 10 lines · George Spelvin

The standard N_TTY line discipline used to not use the tty->disc_data field, so N_PPS felt free to use it. That has now changed, requiring that N_PPS use a different method to find its private data.

(In the current, buggy, state, N_PPS follows a wild pointer and explodes in an interrupt hander as soon as a pulse actually arrives.)

Strictly speaking, only patches 5 and 6 are the bugfix proper; the rest are various cleanups enabled by the looser coupling of the line disciplines.

As this is a breakage in 3.8-rc1, is there any way to pull out patches 5 and 6 in a format that I can apply them now, or in a way that I can get them into 3.8.1 so that this gets fixed? I can't apply all of these to 3.8-final now, as it's way too late.

thanks,

greg k-h

#9 lore raw
Quoted 3 lines

As this is a breakage in 3.8-rc1, is there any way to pull out patches 5 and 6 in a format that I can apply them now, or in a way that I can get I can't apply all of these to 3.8-final now, as it's way too late.

I'll reorder them for you. But Philipp will be bitterly disappointed. :-)

The other cleanups are far smaller and lower risk than the infrastructure added in #5; I was just being OCD about committing the "least patchable unit".

#10 lore raw
In reply to Greg KH

As a followup, testing my fixes has revealed an old bug in the PPS driver that I'm trying to figure out how to solve.

Basically, pps_unregister_cdev does

        device_destroy(pps_class, pps->dev->devt);
        cdev_del(&pps->cdev);

And device_destroy ends up calling pps->dev->release, which is pps_device_destruct, which does

        mutex_lock(&pps_idr_lock);
        idr_remove(&pps_idr, pps->id);
        mutex_unlock(&pps_idr_lock);

        kfree(dev);
        kfree(pps);

Now the problem is that the kfree(pps) happens *before* the cdev_del(&pps->cdev) call, which is Not Good.

I'm trying to figure out The Right Thing to do in this case and include a fix for that, too. It's not a regression, but it is a fairly serious bug.

Advice gratefully received, but I'll figure it out on my own, if not. The most obvious kludge is to wrap the pps_unregister_cdev operations in device_get/device_put, to force the release callback to be delayed until later.

I'm hoping for something prettier, though. The other option I'm thinking about is to move one or both deallocations to the (currently stub) pps_cdev_release function.