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
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_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 */
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, NULL);
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		devsw_detach(NULL, &sysmon_cdevsw);
360		mutex_exit(&sysmon_minor_mtx);
361	}
362#endif
363
364	return error;
365}
366
367static int
368sysmon_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