Attached is a diff that implements device cloning to allow the
/dev/hotplug device to be cloned (to allow multiple concurrent readers).

Rationale:

Currently, iscsid/iscsictl cannot see when the connections it initiates
results in a device (disk) being attached. Recently, Claudio Jeker
committed a diff I wrote to block "iscsictl reload" until all
connections have been finalized (either connecting successfully, or
notified of failure). This fixes an issue I encountered where iscsi
mounts in /etc/fstab would not be ready at boot time, and would result
in the machine going into single user mode when fsck would try to access
them.

The committed diff fixes my original problem. However, I've now
encountered another one. On one of my machines (far slower than the
first one), the devices themselves are much slower to attach - this
leads to the same race condition as before, as "iscsictl reload" is only
aware that the connections have been initiated, not whether the drives
themselves are actually attached.

What I am hoping here is that I can use hotplug to monitor for device
attach events, and match those to the targets we expect to be attaching
from iscsi. Obviously, we can't just use the hotplug device as is
because potentialy we'd be competing with hotplugd.

I am wondering if this is the right solution...some quick thoughts i'd
love to hear feedback on:

1. Kind of feels like I'm conflating what hotplug is for - this isn't
really "hotplugging" per se. This specific use-case is assuming the
volumes are listed in /etc/fstab.

2. Based on 1, would it be more appropriate to create a separate device
with it's own semantics (potentially very different from /dev/hotplug)
that lets us do this...

Inspiration for this diff was drawn from Joshua Stein [1], seeminly he
had a use-case that was somewhat similar to mine at one point but it
doesn't look like this was ever committed. Nor can I find any discussion
on it.

[1]: https://jcs.org/2018/08/31/surface_go

Feedback greatly welcomed.

Thanks,

Ash

diff --git a/sys/dev/hotplug.c b/sys/dev/hotplug.c
index e1e7bad95c9..522939f7f7a 100644
--- a/sys/dev/hotplug.c
+++ b/sys/dev/hotplug.c
@@ -25,15 +25,24 @@
 #include <sys/fcntl.h>
 #include <sys/hotplug.h>
 #include <sys/ioctl.h>
+#include <sys/malloc.h>
 #include <sys/poll.h>
 #include <sys/vnode.h>
 
 #define HOTPLUG_MAXEVENTS	64
 
-static int opened;
+struct hotplug_dev {
+	int unit;
+	int evqueue_head;
+	struct selinfo sel_info;
+
+	LIST_ENTRY(hotplug_dev) dev_list;
+};
+
+LIST_HEAD(, hotplug_dev) hotplug_dev_list;
+static int evqueue_head;
 static struct hotplug_event evqueue[HOTPLUG_MAXEVENTS];
-static int evqueue_head, evqueue_tail, evqueue_count;
-static struct selinfo hotplug_sel;
+
 
 void filt_hotplugrdetach(struct knote *);
 int  filt_hotplugread(struct knote *, long);
@@ -45,21 +54,40 @@ const struct filterops hotplugread_filtops = {
 	.f_event	= filt_hotplugread,
 };
 
+#define EVQUEUE_PREV(p) (p == 0 ? HOTPLUG_MAXEVENTS : p - 1)
 #define EVQUEUE_NEXT(p) (p == HOTPLUG_MAXEVENTS - 1 ? 0 : p + 1)
+#define EMPTY_EVENT(ev) (ev.he_type == HOTPLUG_EMPTY)
+#define EVENT_IS_HEAD(p) (p == evqueue_head)
+#define HEAD_EVENT evqueue[evqueue_head]
 
 
 int hotplug_put_event(struct hotplug_event *);
-int hotplug_get_event(struct hotplug_event *);
+int hotplug_get_event(struct hotplug_dev *, struct hotplug_event *);
 
 void hotplugattach(int);
+struct hotplug_dev * hotplug_device_lookup(int);
 
 void
 hotplugattach(int count)
 {
-	opened = 0;
-	evqueue_head = 0;
-	evqueue_tail = 0;
-	evqueue_count = 0;
+	int i;
+
+	for (i = 0; i < HOTPLUG_MAXEVENTS; ++i)
+		evqueue[i].he_type = HOTPLUG_EMPTY;
+
+	LIST_INIT(&hotplug_dev_list);
+}
+
+struct hotplug_dev *
+hotplug_device_lookup(int unit)
+{
+	struct hotplug_dev *d;
+
+	LIST_FOREACH(d, &hotplug_dev_list, dev_list)
+	    if (d->unit == unit)
+		    return d;
+
+	return (NULL);
 }
 
 void
@@ -87,59 +115,79 @@ hotplug_device_detach(enum devclass class, char *name)
 int
 hotplug_put_event(struct hotplug_event *he)
 {
-	if (evqueue_count == HOTPLUG_MAXEVENTS && opened) {
-		printf("hotplug: event lost, queue full\n");
-		return (1);
-	}
+	struct hotplug_dev *d;
 
-	evqueue[evqueue_head] = *he;
+	HEAD_EVENT = *he;
 	evqueue_head = EVQUEUE_NEXT(evqueue_head);
-	if (evqueue_count == HOTPLUG_MAXEVENTS)
-		evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
-	else 
-		evqueue_count++;
+	HEAD_EVENT.he_type = HOTPLUG_EMPTY;
+
+	LIST_FOREACH(d, &hotplug_dev_list, dev_list) {
+		if (EVENT_IS_HEAD(d->evqueue_head))
+			d->evqueue_head = EVQUEUE_NEXT(evqueue_head);
+		selwakeup(&d->sel_info);
+	}
+
+
 	wakeup(&evqueue);
-	selwakeup(&hotplug_sel);
 	return (0);
 }
 
 int
-hotplug_get_event(struct hotplug_event *he)
+hotplug_get_event(struct hotplug_dev *d, struct hotplug_event *he)
 {
-	int s;
+	int s = splbio();
 
-	if (evqueue_count == 0)
-		return (1);
+	if (!EMPTY_EVENT(evqueue[d->evqueue_head])) {
+		*he = evqueue[d->evqueue_head];
+		d->evqueue_head = EVQUEUE_NEXT(d->evqueue_head);
+		splx(s);
+		return (0);
+	}
 
-	s = splbio();
-	*he = evqueue[evqueue_tail];
-	evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
-	evqueue_count--;
 	splx(s);
-	return (0);
+	return (1);
 }
 
 int
 hotplugopen(dev_t dev, int flag, int mode, struct proc *p)
 {
-	if (minor(dev) != 0)
-		return (ENXIO);
-	if ((flag & FWRITE))
+	struct hotplug_dev *d;
+	int i;
+
+	if (flag & FWRITE)
 		return (EPERM);
-	if (opened)
+
+	if ((d = hotplug_device_lookup(minor(dev))) != NULL)
 		return (EBUSY);
-	opened = 1;
+
+	d = malloc(sizeof(*d), M_DEVBUF, M_WAITOK|M_ZERO);
+	d->unit = minor(dev);
+
+	/*
+	 * Set the queue head to the earliest non-empty
+	 * event. So the new device can read the whole queue.
+	 */
+	i = EVQUEUE_PREV(evqueue_head);
+	while (!EMPTY_EVENT(evqueue[i]) && !EVENT_IS_HEAD(i))
+		i = EVQUEUE_PREV(evqueue_head);
+
+	d->evqueue_head = EVQUEUE_NEXT(i);
+	LIST_INSERT_HEAD(&hotplug_dev_list, d, dev_list);
+
 	return (0);
 }
 
 int
 hotplugclose(dev_t dev, int flag, int mode, struct proc *p)
 {
-	struct hotplug_event he;
+	struct hotplug_dev *d;
 
-	while (hotplug_get_event(&he) == 0)
-		continue;
-	opened = 0;
+	d = hotplug_device_lookup(minor(dev));
+	if (d == NULL)
+		return (EINVAL);
+
+	LIST_REMOVE(d, dev_list);
+	free(d, M_DEVBUF, sizeof(*d));
 	return (0);
 }
 
@@ -148,12 +196,16 @@ hotplugread(dev_t dev, struct uio *uio, int flags)
 {
 	struct hotplug_event he;
 	int error;
+	struct hotplug_dev *d = hotplug_device_lookup(minor(dev));
+
+	if (d == NULL)
+		return (ENXIO);
 
 	if (uio->uio_resid != sizeof(he))
 		return (EINVAL);
 
 again:
-	if (hotplug_get_event(&he) == 0)
+	if (hotplug_get_event(d, &he) == 0)
 		return (uiomove(&he, sizeof(he), uio));
 	if (flags & IO_NDELAY)
 		return (EAGAIN);
@@ -167,6 +219,12 @@ again:
 int
 hotplugioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
 {
+	struct hotplug_dev *d;
+
+	d = hotplug_device_lookup(minor(dev));
+	if (d == NULL)
+		return (ENXIO);
+
 	switch (cmd) {
 	case FIOASYNC:
 		/* ignore */
@@ -184,12 +242,16 @@ int
 hotplugpoll(dev_t dev, int events, struct proc *p)
 {
 	int revents = 0;
+	struct hotplug_dev *d;
+
+	if ((d = hotplug_device_lookup(minor(dev))) == NULL)
+		return (POLLERR);
 
 	if (events & (POLLIN | POLLRDNORM)) {
-		if (evqueue_count > 0)
+		if (!EMPTY_EVENT(evqueue[EVQUEUE_NEXT(d->evqueue_head)]))
 			revents |= events & (POLLIN | POLLRDNORM);
 		else
-			selrecord(p, &hotplug_sel);
+			selrecord(p, &d->sel_info);
 	}
 
 	return (revents);
@@ -199,11 +261,15 @@ int
 hotplugkqfilter(dev_t dev, struct knote *kn)
 {
 	struct klist *klist;
+	struct hotplug_dev *d;
 	int s;
 
+	if ((d = hotplug_device_lookup(minor(dev))) == NULL)
+		return (EINVAL);
+
 	switch (kn->kn_filter) {
 	case EVFILT_READ:
-		klist = &hotplug_sel.si_note;
+		klist = &d->sel_info.si_note;
 		kn->kn_fop = &hotplugread_filtops;
 		break;
 	default:
@@ -211,6 +277,7 @@ hotplugkqfilter(dev_t dev, struct knote *kn)
 	}
 
 	s = splbio();
+	kn->kn_hook = d;
 	klist_insert_locked(klist, kn);
 	splx(s);
 	return (0);
@@ -219,17 +286,21 @@ hotplugkqfilter(dev_t dev, struct knote *kn)
 void
 filt_hotplugrdetach(struct knote *kn)
 {
+	struct hotplug_dev *d;
 	int s;
 
+	d = kn->kn_hook;
 	s = splbio();
-	klist_remove_locked(&hotplug_sel.si_note, kn);
+	klist_remove_locked(&d->sel_info.si_note, kn);
 	splx(s);
 }
 
 int
 filt_hotplugread(struct knote *kn, long hint)
 {
-	kn->kn_data = evqueue_count;
+	struct hotplug_dev *d = kn->kn_hook;
+	if (EMPTY_EVENT(evqueue[d->evqueue_head]))
+		return (0);
 
-	return (evqueue_count > 0);
+	return (1);
 }
diff --git a/sys/sys/conf.h b/sys/sys/conf.h
index 5783e5244d2..bccf1dd0437 100644
--- a/sys/sys/conf.h
+++ b/sys/sys/conf.h
@@ -437,7 +437,7 @@ extern struct cdevsw cdevsw[];
 	dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read), \
 	(dev_type_write((*))) enodev, dev_init(c,n,ioctl), \
 	(dev_type_stop((*))) enodev, 0, dev_init(c,n,poll), \
-	(dev_type_mmap((*))) enodev, 0, 0, dev_init(c,n,kqfilter) }
+	(dev_type_mmap((*))) enodev, 0, D_CLONE, dev_init(c,n,kqfilter) }
 
 /* open, close, ioctl */
 #define cdev_gpio_init(c,n) { \
diff --git a/sys/sys/hotplug.h b/sys/sys/hotplug.h
index f2da3f6cfd8..90695e9d024 100644
--- a/sys/sys/hotplug.h
+++ b/sys/sys/hotplug.h
@@ -23,6 +23,7 @@
  * attachment and detachment notifications.
  */
 
+#define HOTPLUG_EMPTY           0x00    /* empty event          */
 #define HOTPLUG_DEVAT		0x01	/* device attached	*/
 #define HOTPLUG_DEVDT		0x02	/* device detached	*/
 

Reply via email to