1/* $NetBSD: sysmon.c,v 1.28 2015/05/05 09:22:33 pgoyette 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.28 2015/05/05 09:22:33 pgoyette 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
59dev_type_open(sysmonopen);
60dev_type_close(sysmonclose);
61dev_type_ioctl(sysmonioctl);
62dev_type_read(sysmonread);
63dev_type_poll(sysmonpoll);
64dev_type_kqfilter(sysmonkqfilter);
65
66const 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
81static int sysmon_modcmd(modcmd_t, void *);
82static int sm_init_once(void);
83
84/*
85 * Info about our minor "devices"
86 */
87static struct sysmon_opvec *sysmon_opvec_table[] = { NULL, NULL, NULL };
88static int sysmon_refcnt[] = { 0, 0, 0 };
89static const char *sysmon_mod[] = { "sysmon_envsys",
90 "sysmon_wdog",
91 "sysmon_power" };
92static kmutex_t sysmon_minor_mtx;
93
94#ifdef _MODULE
95static bool sm_is_attached;
96#endif
97
98ONCE_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 */
110int
111sysmon_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 */
140int
141sysmonopen(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_MISC);
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 */
181int
182sysmonclose(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 */
213int
214sysmonioctl(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 */
240int
241sysmonread(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 */
265int
266sysmonpoll(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 */
290int
291sysmonkqfilter(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
310MODULE(MODULE_CLASS_DRIVER, sysmon, "");
311
312static int
313sm_init_once(void)
314{
315
316 mutex_init(&sysmon_minor_mtx, MUTEX_DEFAULT, IPL_NONE);
317
318 return 0;
319}
320
321int
322sysmon_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
345int
346sysmon_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 error = devsw_detach(NULL, &sysmon_cdevsw);
360 mutex_exit(&sysmon_minor_mtx);
361 }
362#endif
363
364 return error;
365}
366
367static
368int
369sysmon_modcmd(modcmd_t cmd, void *arg)
370{
371 int ret;
372
373 switch (cmd) {
374 case MODULE_CMD_INIT:
375 ret = sysmon_init();
376 break;
377
378 case MODULE_CMD_FINI:
379 ret = sysmon_fini();
380 break;
381
382 case MODULE_CMD_STAT:
383 default:
384 ret = ENOTTY;
385 }
386
387 return ret;
388}
389