1 /*        $NetBSD: sysmon.c,v 1.32 2022/03/28 12:33:21 riastradh Exp $          */
2 
3 /*-
4  * Copyright (c) 2000 Zembu Labs, Inc.
5  * All rights reserved.
6  *
7  * Author: Jason R. Thorpe <thorpej@zembu.com>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *        This product includes software developed by Zembu Labs, Inc.
20  * 4. Neither the name of Zembu Labs nor the names of its employees may
21  *    be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY ZEMBU LABS, INC. ``AS IS'' AND ANY EXPRESS
25  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAR-
26  * RANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DIS-
27  * CLAIMED.  IN NO EVENT SHALL ZEMBU LABS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 /*
37  * Clearing house for system monitoring hardware.  We currently
38  * handle environmental sensors, watchdog timers, and power management.
39  */
40 
41 #include <sys/cdefs.h>
42 __KERNEL_RCSID(0, "$NetBSD: sysmon.c,v 1.32 2022/03/28 12:33:21 riastradh Exp $");
43 
44 #include <sys/param.h>
45 #include <sys/conf.h>
46 #include <sys/errno.h>
47 #include <sys/fcntl.h>
48 #include <sys/callout.h>
49 #include <sys/kernel.h>
50 #include <sys/systm.h>
51 #include <sys/proc.h>
52 #include <sys/module.h>
53 #include <sys/mutex.h>
54 #include <sys/device.h>
55 #include <sys/once.h>
56 
57 #include <dev/sysmon/sysmonvar.h>
58 
59 dev_type_open(sysmonopen);
60 dev_type_close(sysmonclose);
61 dev_type_ioctl(sysmonioctl);
62 dev_type_read(sysmonread);
63 dev_type_poll(sysmonpoll);
64 dev_type_kqfilter(sysmonkqfilter);
65 
66 const struct cdevsw sysmon_cdevsw = {
67           .d_open = sysmonopen,
68           .d_close = sysmonclose,
69           .d_read = sysmonread,
70           .d_write = nowrite,
71           .d_ioctl = sysmonioctl,
72           .d_stop = nostop,
73           .d_tty = notty,
74           .d_poll = sysmonpoll,
75           .d_mmap = nommap,
76           .d_kqfilter = sysmonkqfilter,
77           .d_discard = nodiscard,
78           .d_flag = D_OTHER | D_MPSAFE
79 };
80 
81 static int          sysmon_modcmd(modcmd_t, void *);
82 static int          sm_init_once(void);
83 
84 /*
85  * Info about our minor "devices"
86  */
87 static struct sysmon_opvec    *sysmon_opvec_table[] = { NULL, NULL, NULL };
88 static int                              sysmon_refcnt[] = { 0, 0, 0 };
89 static const char             *sysmon_mod[] = { "sysmon_envsys",
90                                                               "sysmon_wdog",
91                                                               "sysmon_power" };
92 static kmutex_t sysmon_minor_mtx;
93 
94 #ifdef _MODULE
95 static bool         sm_is_attached;
96 #endif
97 
98 ONCE_DECL(once_sm);
99 
100 /*
101  * sysmon_attach_minor
102  *
103  *        Attach a minor device for wdog, power, or envsys.  Manage a
104  *        reference count so we can prevent the device from being
105  *        detached if there are still users with the minor device opened.
106  *
107  *        If the opvec argument is NULL, this is a request to detach the
108  *        minor device - make sure the refcnt is zero!
109  */
110 int
sysmon_attach_minor(int minor,struct sysmon_opvec * opvec)111 sysmon_attach_minor(int minor, struct sysmon_opvec *opvec)
112 {
113           int ret;
114 
115           mutex_enter(&sysmon_minor_mtx);
116           if (opvec) {
117                     if (sysmon_opvec_table[minor] == NULL) {
118                               sysmon_refcnt[minor] = 0;
119                               sysmon_opvec_table[minor] = opvec;
120                               ret = 0;
121                     } else
122                               ret = EEXIST;
123           } else {
124                     if (sysmon_refcnt[minor] == 0) {
125                               sysmon_opvec_table[minor] = NULL;
126                               ret = 0;
127                     } else
128                               ret = EBUSY;
129           }
130 
131           mutex_exit(&sysmon_minor_mtx);
132           return ret;
133 }
134 
135 /*
136  * sysmonopen:
137  *
138  *        Open the system monitor device.
139  */
140 int
sysmonopen(dev_t dev,int flag,int mode,struct lwp * l)141 sysmonopen(dev_t dev, int flag, int mode, struct lwp *l)
142 {
143           int error;
144 
145           mutex_enter(&sysmon_minor_mtx);
146 
147           switch (minor(dev)) {
148           case SYSMON_MINOR_ENVSYS:
149           case SYSMON_MINOR_WDOG:
150           case SYSMON_MINOR_POWER:
151                     if (sysmon_opvec_table[minor(dev)] == NULL) {
152                               mutex_exit(&sysmon_minor_mtx);
153                               error = module_autoload(sysmon_mod[minor(dev)],
154                                   MODULE_CLASS_DRIVER);
155                               if (error)
156                                         return error;
157                               mutex_enter(&sysmon_minor_mtx);
158                               if (sysmon_opvec_table[minor(dev)] == NULL) {
159                                         error = ENODEV;
160                                         break;
161                               }
162                     }
163                     error = (sysmon_opvec_table[minor(dev)]->so_open)(dev, flag,
164                         mode, l);
165                     if (error == 0)
166                               sysmon_refcnt[minor(dev)]++;
167                     break;
168           default:
169                     error = ENODEV;
170           }
171 
172           mutex_exit(&sysmon_minor_mtx);
173           return error;
174 }
175 
176 /*
177  * sysmonclose:
178  *
179  *        Close the system monitor device.
180  */
181 int
sysmonclose(dev_t dev,int flag,int mode,struct lwp * l)182 sysmonclose(dev_t dev, int flag, int mode, struct lwp *l)
183 {
184           int error;
185 
186           switch (minor(dev)) {
187           case SYSMON_MINOR_ENVSYS:
188           case SYSMON_MINOR_WDOG:
189           case SYSMON_MINOR_POWER:
190                     if (sysmon_opvec_table[minor(dev)] == NULL)
191                               error = ENODEV;
192                     else {
193                               error = (sysmon_opvec_table[minor(dev)]->so_close)(dev,
194                                   flag, mode, l);
195                               if (error == 0) {
196                                         sysmon_refcnt[minor(dev)]--;
197                                         KASSERT(sysmon_refcnt[minor(dev)] >= 0);
198                               }
199                     }
200                     break;
201           default:
202                     error = ENODEV;
203           }
204 
205           return (error);
206 }
207 
208 /*
209  * sysmonioctl:
210  *
211  *        Perform a control request.
212  */
213 int
sysmonioctl(dev_t dev,u_long cmd,void * data,int flag,struct lwp * l)214 sysmonioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
215 {
216           int error;
217 
218           switch (minor(dev)) {
219           case SYSMON_MINOR_ENVSYS:
220           case SYSMON_MINOR_WDOG:
221           case SYSMON_MINOR_POWER:
222                     if (sysmon_opvec_table[minor(dev)] == NULL)
223                               error = ENODEV;
224                     else
225                               error = (sysmon_opvec_table[minor(dev)]->so_ioctl)(dev,
226                                   cmd, data, flag, l);
227                     break;
228           default:
229                     error = ENODEV;
230           }
231 
232           return (error);
233 }
234 
235 /*
236  * sysmonread:
237  *
238  *        Perform a read request.
239  */
240 int
sysmonread(dev_t dev,struct uio * uio,int flags)241 sysmonread(dev_t dev, struct uio *uio, int flags)
242 {
243           int error;
244 
245           switch (minor(dev)) {
246           case SYSMON_MINOR_POWER:
247                     if (sysmon_opvec_table[minor(dev)] == NULL)
248                               error = ENODEV;
249                     else
250                               error = (sysmon_opvec_table[minor(dev)]->so_read)(dev,
251                                   uio, flags);
252                     break;
253           default:
254                     error = ENODEV;
255           }
256 
257           return (error);
258 }
259 
260 /*
261  * sysmonpoll:
262  *
263  *        Poll the system monitor device.
264  */
265 int
sysmonpoll(dev_t dev,int events,struct lwp * l)266 sysmonpoll(dev_t dev, int events, struct lwp *l)
267 {
268           int rv;
269 
270           switch (minor(dev)) {
271           case SYSMON_MINOR_POWER:
272                     if (sysmon_opvec_table[minor(dev)] == NULL)
273                               rv = events;
274                     else
275                               rv = (sysmon_opvec_table[minor(dev)]->so_poll)(dev,
276                                   events, l);
277                     break;
278           default:
279                     rv = events;
280           }
281 
282           return (rv);
283 }
284 
285 /*
286  * sysmonkqfilter:
287  *
288  *        Kqueue filter for the system monitor device.
289  */
290 int
sysmonkqfilter(dev_t dev,struct knote * kn)291 sysmonkqfilter(dev_t dev, struct knote *kn)
292 {
293           int error;
294 
295           switch (minor(dev)) {
296           case SYSMON_MINOR_POWER:
297                     if (sysmon_opvec_table[minor(dev)] == NULL)
298                               error = ENODEV;
299                     else
300                               error = (sysmon_opvec_table[minor(dev)]->so_filter)(dev,
301                                   kn);
302                     break;
303           default:
304                     error = 1;
305           }
306 
307           return (error);
308 }
309 
310 MODULE(MODULE_CLASS_DRIVER, sysmon, NULL);
311 
312 static int
sm_init_once(void)313 sm_init_once(void)
314 {
315 
316           mutex_init(&sysmon_minor_mtx, MUTEX_DEFAULT, IPL_NONE);
317 
318           return 0;
319 }
320 
321 int
sysmon_init(void)322 sysmon_init(void)
323 {
324           int error;
325 #ifdef _MODULE
326           devmajor_t bmajor, cmajor;
327 #endif
328 
329           error = RUN_ONCE(&once_sm, sm_init_once);
330 
331 #ifdef _MODULE
332           mutex_enter(&sysmon_minor_mtx);
333           if (!sm_is_attached) {
334                     bmajor = cmajor = -1;
335                     error = devsw_attach("sysmon", NULL, &bmajor,
336                                         &sysmon_cdevsw, &cmajor);
337                     sm_is_attached = (error != 0);
338           }
339           mutex_exit(&sysmon_minor_mtx);
340 #endif
341 
342           return error;
343 }
344 
345 int
sysmon_fini(void)346 sysmon_fini(void)
347 {
348           int error = 0;
349 
350           if ((sysmon_opvec_table[SYSMON_MINOR_ENVSYS] != NULL) ||
351               (sysmon_opvec_table[SYSMON_MINOR_WDOG] != NULL) ||
352               (sysmon_opvec_table[SYSMON_MINOR_POWER] != NULL))
353                     error = EBUSY;
354 
355 #ifdef _MODULE
356           if (error == 0) {
357                     mutex_enter(&sysmon_minor_mtx);
358                     sm_is_attached = false;
359                     devsw_detach(NULL, &sysmon_cdevsw);
360                     mutex_exit(&sysmon_minor_mtx);
361           }
362 #endif
363 
364           return error;
365 }
366 
367 static int
sysmon_modcmd(modcmd_t cmd,void * arg)368 sysmon_modcmd(modcmd_t cmd, void *arg)
369 {
370           int ret;
371 
372           switch (cmd) {
373           case MODULE_CMD_INIT:
374                     ret = sysmon_init();
375                     break;
376           case MODULE_CMD_FINI:
377                     ret = sysmon_fini();
378                     break;
379           case MODULE_CMD_STAT:
380           default:
381                     ret = ENOTTY;
382           }
383 
384           return ret;
385 }
386