Discussion:
unaddressed issues around tty_open_by_driver
Okash Khawaja
2017-06-25 09:21:26 UTC
Permalink
Hi,

Raising this here but I think we will ultimately discuss this on a wider
mailing list.

There are three issues I can see with the way we open tty from speakup
(and others will also have similar problems if they use
tty_open_by_kernel).

1. user space opens tty which is already opened by kernel
2. kernel opens tty that is already opened by user
3. user space opens tty kernel has stopped using

First two may be addressed by checking flags before opening and
setting and unsetting those flags after opening and before closing.

Third one is interesting. I haven't investigated it yet, but it seems to
throw a kernel oops from tty_ldisc_reinit. I have attached relevant bit
from kernel log - please see from line at 08:37:03 onwards. I ran `echo
foo > /dev/ttyUSB0` after unloading speakup and speakup_apollo which
were loaded with dev=ttyUSB0.

We can avoid 2 and 3 above by calling tty_open_by_driver only when
speakup is built into kernel and not when loaded as module. Which of
course means we need to work out what to do when loading speakup as
module.

Thanks,
Okash
Samuel Thibault
2017-06-26 23:38:35 UTC
Permalink
Post by Okash Khawaja
Third one is interesting. I haven't investigated it yet, but it seems to
throw a kernel oops from tty_ldisc_reinit.
So it's probably some missing cleanups in the speakup code, which just
needs fixing :)
Post by Okash Khawaja
We can avoid 2 and 3 above by calling tty_open_by_driver only when
speakup is built into kernel and not when loaded as module.
That looks odd to me :)

Samuel
Okash Khawaja
2017-06-27 09:44:45 UTC
Permalink
Hi,
Post by Samuel Thibault
Post by Okash Khawaja
Third one is interesting. I haven't investigated it yet, but it seems to
throw a kernel oops from tty_ldisc_reinit.
So it's probably some missing cleanups in the speakup code, which just
needs fixing :)
Yes, there are a couple of things. The kernel oops comes from ldisc.
Because we call tty_ldisc_release on module exit but don't set
tty->ldiscs[N_SPEAKUP] to NULL, the next time that tty is opened, it
tries to use N_SPEAKUP ldisc whose memory was released in
tty_ldisc_release. Calling tty_ldisc_unregister resolves it by setting
tty->ldiscs[N_SPEAKUP] to NULL.

Next, I think, we need to restore tty->termios.c_line to the ldisc
before speakup was loaded, because that's what is used when opening tty
[1]. Locally, I have done that by caching original ldisc and then
calling tty_set_termios_ldisc on module exit which resolves that issue.
However, in kernel tty_set_termios_ldisc is static function and will
need to be exported if we want to call it.

Finally, there is tty->count mismatch. On module exit, we don't
decrement tty->count which was incremented by tty_open_by_driver. I
don't know yet how to fix that.

So in summary, we need above changes, and also the flags to prevent
contention between user and kernel space.
Post by Samuel Thibault
Post by Okash Khawaja
We can avoid 2 and 3 above by calling tty_open_by_driver only when
speakup is built into kernel and not when loaded as module.
That looks odd to me :)
Yes it does :) Not very familiar with kernel side use cases yet. My
thinking was to use different way of acquiring tty_struct when loaded as
module, because then we will have the device file. Probably not very
elegant.

Thanks,
Okash

[1]
http://elixir.free-electrons.com/linux/latest/source/drivers/tty/tty_io.c#L1484
Samuel Thibault
2017-06-27 09:50:18 UTC
Permalink
Post by Okash Khawaja
Next, I think, we need to restore tty->termios.c_line to the ldisc
before speakup was loaded, because that's what is used when opening tty
[1]. Locally, I have done that by caching original ldisc and then
calling tty_set_termios_ldisc on module exit which resolves that issue.
However, in kernel tty_set_termios_ldisc is static function and will
need to be exported if we want to call it.
Mmm, it looks odd to be doing such kind of hackery. It'd probably be
good to check how other line disciplines are doing.

Samuel
Okash Khawaja
2017-06-27 10:20:34 UTC
Permalink
Post by Samuel Thibault
Post by Okash Khawaja
Next, I think, we need to restore tty->termios.c_line to the ldisc
before speakup was loaded, because that's what is used when opening tty
[1]. Locally, I have done that by caching original ldisc and then
calling tty_set_termios_ldisc on module exit which resolves that issue.
However, in kernel tty_set_termios_ldisc is static function and will
need to be exported if we want to call it.
Mmm, it looks odd to be doing such kind of hackery. It'd probably be
good to check how other line disciplines are doing.
Other ldiscs don't call tty_set_ldisc. They seem to rely on user space
setting the ldisc and hence to clean up also. They just register ldisc
on module init and unregister it on module exit.

I haven't looked at _exactly_ happens when something like N_MOUSE is
removed, so will do that now.
Samuel Thibault
2017-06-27 10:27:14 UTC
Permalink
Post by Okash Khawaja
Post by Samuel Thibault
Post by Okash Khawaja
Next, I think, we need to restore tty->termios.c_line to the ldisc
before speakup was loaded, because that's what is used when opening tty
[1]. Locally, I have done that by caching original ldisc and then
calling tty_set_termios_ldisc on module exit which resolves that issue.
However, in kernel tty_set_termios_ldisc is static function and will
need to be exported if we want to call it.
Mmm, it looks odd to be doing such kind of hackery. It'd probably be
good to check how other line disciplines are doing.
Other ldiscs don't call tty_set_ldisc. They seem to rely on user space
setting the ldisc and hence to clean up also. They just register ldisc
on module init and unregister it on module exit.
Ah, right. So we need to do what the unregistration triggered by
userland achieves, ok.

So yes, it could make sense to have to unstaticify some functions.

Samuel
Okash Khawaja
2017-06-28 08:51:55 UTC
Permalink
Hi,
Post by Samuel Thibault
Post by Okash Khawaja
Post by Samuel Thibault
Post by Okash Khawaja
Next, I think, we need to restore tty->termios.c_line to the ldisc
before speakup was loaded, because that's what is used when opening tty
[1]. Locally, I have done that by caching original ldisc and then
calling tty_set_termios_ldisc on module exit which resolves that issue.
However, in kernel tty_set_termios_ldisc is static function and will
need to be exported if we want to call it.
Mmm, it looks odd to be doing such kind of hackery. It'd probably be
good to check how other line disciplines are doing.
Other ldiscs don't call tty_set_ldisc. They seem to rely on user space
setting the ldisc and hence to clean up also. They just register ldisc
on module init and unregister it on module exit.
Ah, right. So we need to do what the unregistration triggered by
userland achieves, ok.
So yes, it could make sense to have to unstaticify some functions.
Regarding tty->count mismatch, it seems like several things depend on
tty->count and manipulating it won't be straightforward. More generally,
existing code to open and close tty is designed to be triggered from user
space. I think some of the problems mentioned above are there because we
are trying to overlaod existing user space oriented code with kernel
space.

Rather than dealing with subtleties of changing this code to allow
kernel access to tty, I suggest we create a separate kernel api which is
clean and simple. That way, we also avoid increasing complexity of
existing code. Here are broad lines of what I am suggesting.

Two functions:

struct tty_struct *tty_kopen(dev_t device);
void tty_kclose(struct tty_struct *tty, int idx);

And a tty->flag:

TTY_KOPENED to indicate that this tty_struct is in use by kernel.

tty_kopen will be a subset of tty_open_by_driver that we currently use
to open tty from kernel, i.e. it will only take the path that is valid
for opening from kernel. It will also check for and set TTY_KOPENED
flag.

tty_kclose will be similar to tty_release_struct. It will clear
TTY_KOPENED flag.

tty_open method in tty_io.c will check for TTY_KOPENED and return
-EBUSY if it is set.

So the idea is to make kopen of tty exclusive and protect that
exclusivity with TTY_KOPENED flag. That way we don't have to worry about
complexities, races etc of the regular user-space oriented tty code.

Thanks,
Okash
Okash Khawaja
2017-06-28 20:27:56 UTC
Permalink
Post by Okash Khawaja
So the idea is to make kopen of tty exclusive and protect that
exclusivity with TTY_KOPENED flag. That way we don't have to worry about
complexities, races etc of the regular user-space oriented tty code.
Here's what I mean. I've done some rudimentary testing. It seems to
clean up alright, allowing ttyUSB0 or ttyS0 to be reused by user space
programs. It also prevents user program from using the tty held by
speakup while speakup is loaded.

We don't need tty_kclose though because we can instead call
tty_release_struct which will free up the tty, removing the need to
clear TTY_KOPENED flag.

---
drivers/staging/speakup/spk_ttyio.c | 6 ++-
drivers/tty/tty_io.c | 56 ++++++++++++++++++++++++++++++++++--
include/linux/tty.h | 7 +---
3 files changed, 61 insertions(+), 8 deletions(-)

--- a/drivers/staging/speakup/spk_ttyio.c
+++ b/drivers/staging/speakup/spk_ttyio.c
@@ -164,7 +164,7 @@ static int spk_ttyio_initialise_ldisc(st
if (ret)
return ret;

- tty = tty_open_by_driver(dev, NULL, NULL);
+ tty = tty_kopen(dev);
if (IS_ERR(tty))
return PTR_ERR(tty);

@@ -300,7 +300,9 @@ void spk_ttyio_release(void)

tty_ldisc_flush(speakup_tty);
tty_unlock(speakup_tty);
- tty_ldisc_release(speakup_tty);
+ tty_release_struct(speakup_tty, speakup_tty->index);
+ if (tty_unregister_ldisc(N_SPEAKUP))
+ pr_warn("speakup: failed to unregister line discipline N_SPEAKUP\n");
}
EXPORT_SYMBOL_GPL(spk_ttyio_release);

--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -1786,6 +1786,53 @@ static struct tty_driver *tty_lookup_dri
}

/**
+ * tty_kopen - open a tty device for kernel
+ * @device: dev_t of device to open
+ *
+ * Opens tty exclusively for kernel. Performs the driver lookup,
+ * makes sure it's not already opened and performs the first-time
+ * tty initialization.
+ *
+ * Returns the locked initialized &tty_struct
+ *
+ * Claims the global tty_mutex to serialize:
+ * - concurrent first-time tty initialization
+ * - concurrent tty driver removal w/ lookup
+ * - concurrent tty removal from driver table
+ */
+struct tty_struct *tty_kopen(dev_t device)
+{
+ struct tty_struct *tty;
+ struct tty_driver *driver = NULL;
+ int index = -1;
+
+ mutex_lock(&tty_mutex);
+ driver = tty_lookup_driver(device, NULL, &index);
+ if (IS_ERR(driver)) {
+ mutex_unlock(&tty_mutex);
+ return ERR_CAST(driver);
+ }
+
+ /* check whether we're reopening an existing tty */
+ tty = tty_driver_lookup_tty(driver, NULL, index);
+ if (IS_ERR(tty))
+ goto out;
+
+ if (tty) {
+ tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */
+ tty = ERR_PTR(-EBUSY);
+ } else { /* Returns with the tty_lock held for now */
+ tty = tty_init_dev(driver, index);
+ set_bit(TTY_KOPENED, &tty->flags);
+ }
+out:
+ mutex_unlock(&tty_mutex);
+ tty_driver_kref_put(driver);
+ return tty;
+}
+EXPORT_SYMBOL(tty_kopen);
+
+/**
* tty_open_by_driver - open a tty device
* @device: dev_t of device to open
* @inode: inode of device file
@@ -1801,7 +1848,7 @@ static struct tty_driver *tty_lookup_dri
* - concurrent tty driver removal w/ lookup
* - concurrent tty removal from driver table
*/
-struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
+static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
struct file *filp)
{
struct tty_struct *tty;
@@ -1824,6 +1871,12 @@ struct tty_struct *tty_open_by_driver(de
}

if (tty) {
+ if (test_bit(TTY_KOPENED, &tty->flags)) {
+ tty_kref_put(tty);
+ mutex_unlock(&tty_mutex);
+ tty = ERR_PTR(-EBUSY);
+ goto out;
+ }
mutex_unlock(&tty_mutex);
retval = tty_lock_interruptible(tty);
tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */
@@ -1846,7 +1899,6 @@ out:
tty_driver_kref_put(driver);
return tty;
}
-EXPORT_SYMBOL_GPL(tty_open_by_driver);

/**
* tty_open - open a tty device
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -362,6 +362,7 @@ struct tty_file_private {
#define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */
#define TTY_HUPPED 18 /* Post driver->hangup() */
#define TTY_LDISC_HALTED 22 /* Line discipline is halted */
+#define TTY_KOPENED 23 /* TTY exclusively opened by kernel */

/* Values for tty->flow_change */
#define TTY_THROTTLE_SAFE 1
@@ -399,8 +400,7 @@ extern struct tty_struct *get_current_tt
/* tty_io.c */
extern int __init tty_init(void);
extern const char *tty_name(const struct tty_struct *tty);
-extern struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,
- struct file *filp);
+extern struct tty_struct *tty_kopen(dev_t device);
extern int tty_dev_name_to_number(const char *name, dev_t *number);
#else
static inline void tty_kref_put(struct tty_struct *tty)
@@ -422,8 +422,7 @@ static inline int __init tty_init(void)
{ return 0; }
static inline const char *tty_name(const struct tty_struct *tty)
{ return "(none)"; }
-static inline struct tty_struct *tty_open_by_driver(dev_t device,
- struct inode *inode, struct file *filp)
+static inline struct tty_struct *tty_kopen(dev_t device)
{ return NULL; }
static inline int tty_dev_name_to_number(const char *name, dev_t *number)
{ return -ENOTSUPP; }
Okash Khawaja
2017-06-29 08:59:30 UTC
Permalink
Post by Okash Khawaja
Here's what I mean. I've done some rudimentary testing. It seems to
clean up alright, allowing ttyUSB0 or ttyS0 to be reused by user space
programs. It also prevents user program from using the tty held by
speakup while speakup is loaded.
We don't need tty_kclose though because we can instead call
tty_release_struct which will free up the tty, removing the need to
clear TTY_KOPENED flag.
Thinking of raising this on wider mailing list - basically same that we
submitted the patch for exporting tty_open_by_driver to. If that makes
sense.

Thanks,
Okash

Loading...