1/*-
2 * Copyright (c) 2014 Luiz Otavio O Souza <loos@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/param.h>
28#include <sys/queue.h>
29#include <sys/sysctl.h>
30
31#include <bsnmp/snmpmod.h>
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <syslog.h>
37
38#include "lm75_oid.h"
39#include "lm75_tree.h"
40
41#ifndef	LM75BUF
42#define	LM75BUF		64
43#endif
44#define	TZ_ZEROC	2732
45#define	UPDATE_INTERVAL	500	/* update interval in ticks */
46
47static struct lmodule *module;
48
49static const struct asn_oid oid_lm75 = OIDX_begemotLm75;
50
51/* the Object Resource registration index */
52static u_int lm75_index = 0;
53
54/* Number of available sensors in the system. */
55static int lm75_sensors;
56
57/*
58 * Structure that describes single sensor.
59 */
60struct lm75_snmp_sensor {
61	TAILQ_ENTRY(lm75_snmp_sensor) link;
62	int32_t		index;
63	int32_t		sysctlidx;
64	int32_t		temp;
65	char		desc[LM75BUF];
66	char		location[LM75BUF];
67	char		parent[LM75BUF];
68	char		pnpinfo[LM75BUF];
69};
70
71static TAILQ_HEAD(, lm75_snmp_sensor) sensors =
72    TAILQ_HEAD_INITIALIZER(sensors);
73
74/* Ticks of the last sensors reading. */
75static uint64_t last_sensors_update;
76
77static void free_sensors(void);
78static int lm75_fini(void);
79static int lm75_init(struct lmodule *mod, int argc, char *argv[]);
80static void lm75_start(void);
81static int update_sensors(void);
82
83const struct snmp_module config = {
84    .comment   =
85	"This module implements the BEGEMOT MIB for reading LM75 sensors data.",
86    .init      = lm75_init,
87    .start     = lm75_start,
88    .fini      = lm75_fini,
89    .tree      = lm75_ctree,
90    .tree_size = lm75_CTREE_SIZE,
91};
92
93static int
94lm75_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
95{
96
97	module = mod;
98
99	lm75_sensors = 0;
100
101	return(0);
102}
103
104static void
105lm75_start(void)
106{
107
108	lm75_index = or_register(&oid_lm75,
109	    "The MIB module for reading lm75 sensors data.", module);
110}
111
112static int
113lm75_fini(void)
114{
115
116	or_unregister(lm75_index);
117	free_sensors();
118	closelog();
119
120	return (0);
121}
122
123static void
124free_sensors(void)
125{
126	struct lm75_snmp_sensor *sensor;
127
128	while ((sensor = TAILQ_FIRST(&sensors)) != NULL) {
129		TAILQ_REMOVE(&sensors, sensor, link);
130		free(sensor);
131	}
132}
133
134static int
135sysctlname(int *oid, int nlen, char *name, size_t len)
136{
137	int mib[12];
138
139	if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
140		return (-1);
141
142	mib[0] = 0;
143	mib[1] = 1;
144	memcpy(mib + 2, oid, nlen * sizeof(int));
145
146	if (sysctl(mib, nlen + 2, name, &len, 0, 0) == -1)
147		return (-1);
148
149	return (0);
150}
151
152static int
153sysctlgetnext(int *oid, int nlen, int *next, size_t *nextlen)
154{
155	int mib[12];
156
157	if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
158		return (-1);
159
160	mib[0] = 0;
161	mib[1] = 2;
162	memcpy(mib + 2, oid, nlen * sizeof(int));
163
164	if (sysctl(mib, nlen + 2, next, nextlen, 0, 0) == -1)
165		return (-1);
166
167	return (0);
168}
169
170static int
171update_sensor_sysctl(void *obuf, size_t *obuflen, int idx, const char *name)
172{
173	char buf[LM75BUF];
174	int mib[5];
175	size_t len;
176
177	/* Fill out the mib information. */
178	snprintf(buf, sizeof(buf) - 1, "dev.lm75.%d.%s", idx, name);
179	len = sizeof(mib) / sizeof(int);
180	if (sysctlnametomib(buf, mib, &len) == -1)
181		return (-1);
182
183	if (len != 4)
184		return (-1);
185
186	/* Read the sysctl data. */
187	if (sysctl(mib, len, obuf, obuflen, NULL, 0) == -1)
188		return (-1);
189
190	return (0);
191}
192
193static void
194update_sensor(struct lm75_snmp_sensor *sensor, int idx)
195{
196	size_t len;
197
198	len = sizeof(sensor->desc);
199	update_sensor_sysctl(sensor->desc, &len, idx, "%desc");
200
201	len = sizeof(sensor->location);
202	update_sensor_sysctl(sensor->location, &len, idx, "%location");
203
204	len = sizeof(sensor->pnpinfo);
205	update_sensor_sysctl(sensor->pnpinfo, &len, idx, "%pnpinfo");
206
207	len = sizeof(sensor->parent);
208	update_sensor_sysctl(sensor->parent, &len, idx, "%parent");
209}
210
211static int
212add_sensor(char *buf)
213{
214	int idx, temp;
215	size_t len;
216	struct lm75_snmp_sensor *sensor;
217
218	if (sscanf(buf, "dev.lm75.%d.temperature", &idx) != 1)
219		return (-1);
220
221	/* Read the sensor temperature. */
222	len = sizeof(temp);
223	if (update_sensor_sysctl(&temp, &len, idx, "temperature") != 0)
224		return (-1);
225
226	/* Add the sensor data to the table. */
227	sensor = calloc(1, sizeof(*sensor));
228	if (sensor == NULL) {
229		syslog(LOG_ERR, "Unable to allocate %zu bytes for resource",
230		    sizeof(*sensor));
231		return (-1);
232	}
233	sensor->index = ++lm75_sensors;
234	sensor->sysctlidx = idx;
235	sensor->temp = (temp - TZ_ZEROC) / 10;
236	TAILQ_INSERT_TAIL(&sensors, sensor, link);
237
238	update_sensor(sensor, idx);
239
240	return (0);
241}
242
243static int
244update_sensors(void)
245{
246	char buf[LM75BUF];
247	int i, root[5], *next, *oid;
248	size_t len, nextlen, rootlen;
249	static uint64_t now;
250
251	now = get_ticks();
252	if (now - last_sensors_update < UPDATE_INTERVAL)
253		return (0);
254
255	last_sensors_update = now;
256
257	/* Reset the sensor data. */
258	free_sensors();
259	lm75_sensors = 0;
260
261	/* Start from the lm75 default root node. */
262	rootlen = 2;
263	if (sysctlnametomib("dev.lm75", root, &rootlen) == -1)
264		return (0);
265
266	oid = (int *)malloc(sizeof(int) * rootlen);
267	if (oid == NULL) {
268		perror("malloc");
269		return (-1);
270	}
271	memcpy(oid, root, rootlen * sizeof(int));
272	len = rootlen;
273
274	/* Traverse the sysctl(3) interface and find the active sensors. */
275	for (;;) {
276
277		/* Find the size of the next mib. */
278		nextlen = 0;
279		if (sysctlgetnext(oid, len, NULL, &nextlen) == -1) {
280			free(oid);
281			return (0);
282		}
283		/* Allocate and read the next mib. */
284		next = (int *)malloc(nextlen);
285		if (next == NULL) {
286			syslog(LOG_ERR,
287			    "Unable to allocate %zu bytes for resource",
288			    nextlen);
289			free(oid);
290			return (-1);
291		}
292		if (sysctlgetnext(oid, len, next, &nextlen) == -1) {
293			free(oid);
294			free(next);
295			return (0);
296		}
297		free(oid);
298		/* Check if we care about the next mib. */
299		for (i = 0; i < (int)rootlen; i++)
300			if (next[i] != root[i]) {
301				free(next);
302				return (0);
303			}
304		oid = (int *)malloc(nextlen);
305		if (oid == NULL) {
306			syslog(LOG_ERR,
307			    "Unable to allocate %zu bytes for resource",
308			    nextlen);
309			free(next);
310			return (-1);
311		}
312		memcpy(oid, next, nextlen);
313		free(next);
314		len = nextlen / sizeof(int);
315
316		/* Find the mib name. */
317		if (sysctlname(oid, len, buf, sizeof(buf)) != 0)
318			continue;
319
320		if (strstr(buf, "temperature"))
321			if (add_sensor(buf) != 0) {
322				free(oid);
323				return (-1);
324			}
325	}
326
327	return (0);
328}
329
330int
331op_lm75Sensors(struct snmp_context *context __unused, struct snmp_value *value,
332    u_int sub, u_int iidx __unused, enum snmp_op op)
333{
334	asn_subid_t which;
335
336	if (update_sensors() == -1)
337		return (SNMP_ERR_RES_UNAVAIL);
338
339	which = value->var.subs[sub - 1];
340
341	switch (op) {
342	case SNMP_OP_GET:
343		switch (which) {
344		case LEAF_lm75Sensors:
345			value->v.integer = lm75_sensors;
346			break;
347		default:
348			return (SNMP_ERR_RES_UNAVAIL);
349		}
350		break;
351	case SNMP_OP_SET:
352		return (SNMP_ERR_NOT_WRITEABLE);
353	case SNMP_OP_GETNEXT:
354	case SNMP_OP_ROLLBACK:
355	case SNMP_OP_COMMIT:
356		return (SNMP_ERR_NOERROR);
357	default:
358		return (SNMP_ERR_RES_UNAVAIL);
359	}
360
361	return (SNMP_ERR_NOERROR);
362}
363
364int
365op_lm75SensorTable(struct snmp_context *context __unused,
366    struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
367{
368	struct lm75_snmp_sensor *sensor;
369	asn_subid_t which;
370	int ret;
371
372	if (update_sensors() == -1)
373		return (SNMP_ERR_RES_UNAVAIL);
374
375	which = value->var.subs[sub - 1];
376
377	switch (op) {
378	case SNMP_OP_GETNEXT:
379		sensor = NEXT_OBJECT_INT(&sensors, &value->var, sub);
380		if (sensor == NULL)
381			return (SNMP_ERR_NOSUCHNAME);
382		value->var.len = sub + 1;
383		value->var.subs[sub] = sensor->index;
384		break;
385	case SNMP_OP_GET:
386		if (value->var.len - sub != 1)
387			return (SNMP_ERR_NOSUCHNAME);
388		sensor = FIND_OBJECT_INT(&sensors, &value->var, sub);
389		if (sensor == NULL)
390			return (SNMP_ERR_NOSUCHNAME);
391		break;
392	case SNMP_OP_SET:
393		return (SNMP_ERR_NOT_WRITEABLE);
394	case SNMP_OP_ROLLBACK:
395	case SNMP_OP_COMMIT:
396		return (SNMP_ERR_NOERROR);
397	default:
398		return (SNMP_ERR_RES_UNAVAIL);
399	}
400
401	ret = SNMP_ERR_NOERROR;
402
403	switch (which) {
404	case LEAF_lm75SensorIndex:
405		value->v.integer = sensor->index;
406		break;
407	case LEAF_lm75SensorSysctlIndex:
408		value->v.integer = sensor->sysctlidx;
409		break;
410	case LEAF_lm75SensorDesc:
411		ret = string_get(value, sensor->desc, -1);
412		break;
413	case LEAF_lm75SensorLocation:
414		ret = string_get(value, sensor->location, -1);
415		break;
416	case LEAF_lm75SensorPnpInfo:
417		ret = string_get(value, sensor->pnpinfo, -1);
418		break;
419	case LEAF_lm75SensorParent:
420		ret = string_get(value, sensor->parent, -1);
421		break;
422	case LEAF_lm75SensorTemperature:
423		value->v.integer = sensor->temp;
424		break;
425	default:
426		ret = SNMP_ERR_RES_UNAVAIL;
427		break;
428	}
429
430	return (ret);
431}
432