| Commit | Line | Data |
|---|---|---|
| c6289b98 AP |
1 | #include <sys/conf.h> |
| 2 | #include <sys/param.h> | |
| 3 | #include <sys/systm.h> | |
| 4 | #include <sys/malloc.h> | |
| 5 | #include <sys/kernel.h> | |
| 6 | #include <sys/module.h> | |
| 7 | #include <sys/sysctl.h> | |
| 8 | #include <sys/device.h> | |
| 9 | #include <sys/lock.h> | |
| 10 | #include <sys/selinfo.h> | |
| 11 | #include <sys/poll.h> | |
| 12 | #include <sys/uio.h> | |
| 13 | #include <sys/thread.h> | |
| 14 | #include <sys/thread2.h> | |
| 15 | #include <sys/hotplug.h> | |
| 16 | ||
| 17 | #define HOTPLUG_MAXEVENTS 16 | |
| 18 | ||
| 19 | #define CDEV_MAJOR 82 | |
| 20 | ||
| 21 | static d_open_t hotplugopen; | |
| 22 | static d_close_t hotplugclose; | |
| 23 | static d_read_t hotplugread; | |
| 24 | static d_poll_t hotplugpoll; | |
| 25 | ||
| 26 | static struct dev_ops hotplug_ops = { | |
| 27 | { "hotplug", CDEV_MAJOR, 0 }, | |
| 28 | .d_open = hotplugopen, | |
| 29 | .d_close = hotplugclose, | |
| 30 | .d_read = hotplugread, | |
| 31 | .d_poll = hotplugpoll, | |
| 32 | }; | |
| 33 | ||
| 34 | struct hotplug_event_info { | |
| 35 | struct hotplug_event *he; | |
| 36 | TAILQ_ENTRY(hotplug_event_info) hei_link; | |
| 37 | }; | |
| 38 | ||
| 39 | TAILQ_HEAD(hpq, hotplug_event_info); | |
| 40 | ||
| 41 | static struct hotplug_softc | |
| 42 | { | |
| 43 | cdev_t dev; | |
| 44 | struct lock lock; | |
| 45 | int opened; | |
| 46 | int qcount; | |
| 47 | struct hpq queue; | |
| 48 | struct selinfo sel; | |
| e3bf5370 AP |
49 | void (*old_devfs_node_added)(struct hotplug_device *hpdev); |
| 50 | void (*old_devfs_node_removed)(struct hotplug_device *hpdev); | |
| c6289b98 AP |
51 | } hpsc; |
| 52 | ||
| e3bf5370 AP |
53 | extern void (*devfs_node_added)(struct hotplug_device *hpdev); |
| 54 | extern void (*devfs_node_removed)(struct hotplug_device *hpdev); | |
| c6289b98 | 55 | |
| e3bf5370 AP |
56 | void hotplug_devfs_node_added(struct hotplug_device *hpdev); |
| 57 | void hotplug_devfs_node_removed(struct hotplug_device *hpdev); | |
| c6289b98 AP |
58 | |
| 59 | static int hotplug_get_event(struct hotplug_event *he); | |
| 60 | static int hotplug_put_event(struct hotplug_event *he); | |
| 61 | ||
| 62 | static int hotplug_uninit(void); | |
| 63 | static int hotplug_init(void); | |
| 64 | ||
| 65 | static int | |
| 66 | hotplugopen(struct dev_open_args *ap) | |
| 67 | { | |
| 68 | if (hpsc.opened) | |
| 69 | return (EBUSY); | |
| 70 | hpsc.opened = 1; | |
| 71 | return 0; | |
| 72 | } | |
| 73 | ||
| 74 | static int | |
| 75 | hotplugclose(struct dev_close_args *ap) | |
| 76 | { | |
| 77 | hpsc.opened = 0; | |
| 78 | lockmgr(&hpsc.lock, LK_EXCLUSIVE); | |
| 79 | wakeup(&hpsc); | |
| 80 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 81 | return 0; | |
| 82 | } | |
| 83 | ||
| 84 | static int | |
| 85 | hotplugpoll(struct dev_poll_args *ap) | |
| 86 | { | |
| 87 | int revents = 0; | |
| 88 | ||
| 89 | lockmgr(&hpsc.lock, LK_EXCLUSIVE); | |
| 90 | if (ap->a_events & (POLLIN | POLLRDNORM)) { | |
| 91 | if (!TAILQ_EMPTY(&hpsc.queue)) | |
| 92 | revents = ap->a_events & (POLLIN | POLLRDNORM); | |
| 93 | else | |
| 94 | selrecord(curthread, &hpsc.sel); | |
| 95 | } | |
| 96 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 97 | ||
| 98 | ap->a_events = revents; | |
| 99 | return (0); | |
| 100 | } | |
| 101 | ||
| 102 | int | |
| 103 | hotplug_get_event(struct hotplug_event *he) | |
| 104 | { | |
| 105 | struct hotplug_event_info *hei; | |
| 106 | ||
| 107 | /* shouldn't get there */ | |
| 108 | if(TAILQ_EMPTY(&hpsc.queue)) | |
| 109 | return EINVAL; | |
| 110 | hpsc.qcount--; | |
| 111 | /* we are under hotplugread() lock here */ | |
| 112 | hei = TAILQ_FIRST(&hpsc.queue); | |
| 113 | memcpy(he, hei->he, sizeof(struct hotplug_event)); | |
| 114 | TAILQ_REMOVE(&hpsc.queue, hei, hei_link); | |
| 115 | kfree(hei->he, M_DEVBUF); | |
| 116 | kfree(hei, M_DEVBUF); | |
| 117 | return (0); | |
| 118 | } | |
| 119 | ||
| 120 | static int | |
| 121 | hotplugread(struct dev_read_args *ap) | |
| 122 | { | |
| 123 | struct uio *uio = ap->a_uio; | |
| 124 | struct hotplug_event *he; | |
| 125 | int rv = EINVAL; | |
| 126 | ||
| 127 | lockmgr(&hpsc.lock, LK_EXCLUSIVE); | |
| 128 | while(TAILQ_EMPTY(&hpsc.queue)) { | |
| 129 | tsleep_interlock(&hpsc, PCATCH); | |
| 130 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 131 | rv = tsleep(&hpsc, PCATCH | PINTERLOCKED, "hotplug", 0); | |
| 132 | if(rv) { | |
| 133 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 134 | return (rv); | |
| 135 | } | |
| 136 | } | |
| 137 | he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK); | |
| 138 | if(hotplug_get_event(he) == 0) { | |
| 139 | rv = uiomove((caddr_t)he, sizeof(struct hotplug_event), uio); | |
| 140 | kfree(he, M_DEVBUF); | |
| 141 | } | |
| 142 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 143 | return (rv); | |
| 144 | } | |
| 145 | ||
| 146 | static int | |
| 147 | hotplug_put_event(struct hotplug_event *he) | |
| 148 | { | |
| 149 | struct hotplug_event_info *hei = NULL; | |
| 150 | ||
| 151 | if (hpsc.qcount == HOTPLUG_MAXEVENTS && hpsc.opened) { | |
| 152 | kprintf("hotplug: event lost, queue full\n"); | |
| 153 | return (1); | |
| 154 | } | |
| 155 | hei = kmalloc(sizeof(struct hotplug_event_info), M_DEVBUF, M_WAITOK); | |
| 156 | hei->he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK); | |
| 157 | memcpy(hei->he, he, sizeof(struct hotplug_event)); | |
| 158 | lockmgr(&hpsc.lock, LK_EXCLUSIVE); | |
| 159 | TAILQ_INSERT_TAIL(&hpsc.queue, hei, hei_link); | |
| 160 | hpsc.qcount++; | |
| 161 | wakeup(&hpsc); | |
| 162 | lockmgr(&hpsc.lock, LK_RELEASE); | |
| 163 | selwakeup(&hpsc.sel); | |
| 164 | return (0); | |
| 165 | } | |
| 166 | ||
| 167 | void | |
| e3bf5370 | 168 | hotplug_devfs_node_added(struct hotplug_device *hpdev) { |
| c6289b98 AP |
169 | struct hotplug_event he; |
| 170 | u_int class; | |
| 171 | char *name; | |
| 172 | ||
| e3bf5370 | 173 | if(!hpdev->dev || !hpsc.opened) |
| c6289b98 | 174 | return; |
| e3bf5370 AP |
175 | class = hpdev->dev->si_ops->head.flags; |
| 176 | name = hpdev->name; | |
| c6289b98 AP |
177 | he.he_type = HOTPLUG_DEVAT; |
| 178 | he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL))); | |
| 179 | strlcpy(he.he_devname, name, sizeof(he.he_devname)); | |
| 180 | hotplug_put_event(&he); | |
| 181 | } | |
| 182 | ||
| 183 | void | |
| e3bf5370 | 184 | hotplug_devfs_node_removed(struct hotplug_device *hpdev) { |
| c6289b98 AP |
185 | struct hotplug_event he; |
| 186 | u_int class; | |
| 187 | char *name; | |
| 188 | ||
| e3bf5370 | 189 | if(!hpdev->dev || !hpsc.opened) |
| c6289b98 | 190 | return; |
| e3bf5370 AP |
191 | class = hpdev->dev->si_ops->head.flags; |
| 192 | name = hpdev->name; | |
| c6289b98 AP |
193 | he.he_type = HOTPLUG_DEVDT; |
| 194 | he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL))); | |
| 195 | strlcpy(he.he_devname, name, sizeof(he.he_devname)); | |
| 196 | hotplug_put_event(&he); | |
| 197 | } | |
| 198 | ||
| 199 | static int | |
| 200 | hotplug_init() | |
| 201 | { | |
| 202 | hpsc.dev = make_dev(&hotplug_ops, 0, UID_ROOT, GID_WHEEL, 0600, "hotplug"); | |
| 203 | hpsc.qcount = 0; | |
| 204 | lockinit(&hpsc.lock, "hotplug mtx", 0, 0); | |
| 205 | TAILQ_INIT(&hpsc.queue); | |
| 206 | /* setup handlers */ | |
| 207 | hpsc.old_devfs_node_added = devfs_node_added; | |
| 208 | hpsc.old_devfs_node_removed = devfs_node_removed; | |
| 209 | devfs_node_added = hotplug_devfs_node_added; | |
| 210 | devfs_node_removed = hotplug_devfs_node_removed; | |
| 211 | return 0; | |
| 212 | } | |
| 213 | ||
| 214 | static int | |
| 215 | hotplug_uninit() | |
| 216 | { | |
| 217 | struct hotplug_event_info *hei; | |
| 218 | ||
| 219 | if(hpsc.opened) | |
| 220 | return EBUSY; | |
| 221 | devfs_node_added = hpsc.old_devfs_node_added; | |
| 222 | devfs_node_removed = hpsc.old_devfs_node_removed; | |
| 223 | /* Free the entire tail queue. */ | |
| 224 | while ((hei = TAILQ_FIRST(&hpsc.queue))) { | |
| 225 | TAILQ_REMOVE(&hpsc.queue, hei, hei_link); | |
| 226 | kfree(hei->he, M_DEVBUF); | |
| 227 | kfree(hei, M_DEVBUF); | |
| 228 | } | |
| 229 | ||
| 230 | /* The tail queue should now be empty. */ | |
| 231 | if (!TAILQ_EMPTY(&hpsc.queue)) | |
| 232 | kprintf("hotplug: queue not empty!\n"); | |
| 233 | destroy_dev(hpsc.dev); | |
| 234 | return 0; | |
| 235 | } | |
| 236 | ||
| 237 | static int | |
| 238 | hotplug_modevh(struct module *m, int what, void *arg __unused) | |
| 239 | { | |
| 240 | int error; | |
| 241 | ||
| 242 | switch (what) { | |
| 243 | case MOD_LOAD: | |
| 244 | error = hotplug_init(); | |
| 245 | break; | |
| 246 | case MOD_UNLOAD: | |
| 247 | error = hotplug_uninit(); | |
| 248 | break; | |
| 249 | default: | |
| 250 | error = EINVAL; | |
| 251 | break; | |
| 252 | } | |
| 253 | return (error); | |
| 254 | } | |
| 255 | ||
| 256 | static moduledata_t hotplug_mod = { | |
| 257 | "hotplug", | |
| 258 | hotplug_modevh, | |
| 259 | NULL, | |
| 260 | }; | |
| 261 | ||
| 262 | DECLARE_MODULE(hotplug, hotplug_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); |