xref: /dflybsd-src/sys/dev/misc/dimm/dimm.c (revision 23832f75edc9855492226d612851679a00de2f9c)
1 /*
2  * Copyright (c) 2015 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Sepherosa Ziehau <sepherosa@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/bus.h>
37 #include <sys/kernel.h>
38 #include <sys/lock.h>
39 #include <sys/malloc.h>
40 #include <sys/module.h>
41 #include <sys/sensors.h>
42 #include <sys/sysctl.h>
43 #include <sys/systm.h>
44 
45 #include <dev/misc/dimm/dimm.h>
46 
47 #define DIMM_TEMP_HIWAT_DEFAULT	85
48 #define DIMM_TEMP_LOWAT_DEFAULT	75
49 
50 struct dimm_softc {
51 	TAILQ_ENTRY(dimm_softc) dimm_link;
52 	int			dimm_node;
53 	int			dimm_chan;
54 	int			dimm_slot;
55 	int			dimm_temp_hiwat;
56 	int			dimm_temp_lowat;
57 	int			dimm_id;
58 	int			dimm_ref;
59 	int			dimm_ecc_cnt;
60 
61 	struct ksensordev	dimm_sensdev;
62 	uint32_t		dimm_sens_taskflags;	/* DIMM_SENS_TF_ */
63 
64 	struct sysctl_ctx_list	dimm_sysctl_ctx;
65 	struct sysctl_oid	*dimm_sysctl_tree;
66 };
67 TAILQ_HEAD(dimm_softc_list, dimm_softc);
68 
69 #define DIMM_SENS_TF_TEMP_CRIT		0x1
70 #define DIMM_SENS_TF_ECC_CRIT		0x2
71 
72 static void	dimm_mod_unload(void);
73 
74 /* In the ascending order of dimm_softc.dimm_id */
75 static struct dimm_softc_list	dimm_softc_list;
76 
77 static SYSCTL_NODE(_hw, OID_AUTO, dimminfo, CTLFLAG_RD, NULL,
78     "DIMM information");
79 
80 struct dimm_softc *
81 dimm_create(int node, int chan, int slot)
82 {
83 	struct dimm_softc *sc, *after = NULL;
84 	int dimm_id = 0;
85 
86 	SYSCTL_XLOCK();
87 
88 	TAILQ_FOREACH(sc, &dimm_softc_list, dimm_link) {
89 		/*
90 		 * Already exists; done.
91 		 */
92 		if (sc->dimm_node == node && sc->dimm_chan == chan &&
93 		    sc->dimm_slot == slot) {
94 			KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d",
95 			    sc->dimm_ref));
96 			sc->dimm_ref++;
97 			SYSCTL_XUNLOCK();
98 			return sc;
99 		}
100 
101 		/*
102 		 * Find the lowest usable id.
103 		 */
104 		if (sc->dimm_id == dimm_id) {
105 			++dimm_id;
106 			after = sc;
107 		}
108 	}
109 
110 	sc = kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
111 	sc->dimm_node = node;
112 	sc->dimm_chan = chan;
113 	sc->dimm_slot = slot;
114 	sc->dimm_id = dimm_id;
115 	sc->dimm_ref = 1;
116 	sc->dimm_temp_hiwat = DIMM_TEMP_HIWAT_DEFAULT;
117 	sc->dimm_temp_lowat = DIMM_TEMP_LOWAT_DEFAULT;
118 
119 	ksnprintf(sc->dimm_sensdev.xname, sizeof(sc->dimm_sensdev.xname),
120 	    "dimm%d", sc->dimm_id);
121 
122 	/*
123 	 * Create sysctl tree for the location information.  Use
124 	 * same name as the sensor device.
125 	 */
126 	sysctl_ctx_init(&sc->dimm_sysctl_ctx);
127 	sc->dimm_sysctl_tree = SYSCTL_ADD_NODE(&sc->dimm_sysctl_ctx,
128 	    SYSCTL_STATIC_CHILDREN(_hw_dimminfo), OID_AUTO,
129 	    sc->dimm_sensdev.xname, CTLFLAG_RD, 0, "");
130 	if (sc->dimm_sysctl_tree != NULL) {
131 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
132 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
133 		    "node", CTLFLAG_RD, &sc->dimm_node, 0,
134 		    "CPU node of this DIMM");
135 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
136 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
137 		    "chan", CTLFLAG_RD, &sc->dimm_chan, 0,
138 		    "channel of this DIMM");
139 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
140 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
141 		    "slot", CTLFLAG_RD, &sc->dimm_slot, 0,
142 		    "slot of this DIMM");
143 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
144 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
145 		    "temp_hiwat", CTLFLAG_RW, &sc->dimm_temp_hiwat, 0,
146 		    "Raise alarm once DIMM temperature is above this value "
147 		    "(unit: C)");
148 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
149 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
150 		    "temp_lowat", CTLFLAG_RW, &sc->dimm_temp_lowat, 0,
151 		    "Cancel alarm once DIMM temperature is below this value "
152 		    "(unit: C)");
153 	}
154 
155 	if (after == NULL) {
156 		KKASSERT(sc->dimm_id == 0);
157 		TAILQ_INSERT_HEAD(&dimm_softc_list, sc, dimm_link);
158 	} else {
159 		TAILQ_INSERT_AFTER(&dimm_softc_list, after, sc, dimm_link);
160 	}
161 
162 	sensordev_install(&sc->dimm_sensdev);
163 
164 	SYSCTL_XUNLOCK();
165 	return sc;
166 }
167 
168 int
169 dimm_destroy(struct dimm_softc *sc)
170 {
171 	SYSCTL_XLOCK();
172 
173 	KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d", sc->dimm_ref));
174 	sc->dimm_ref--;
175 	if (sc->dimm_ref > 0) {
176 		SYSCTL_XUNLOCK();
177 		return EAGAIN;
178 	}
179 
180 	sensordev_deinstall(&sc->dimm_sensdev);
181 
182 	TAILQ_REMOVE(&dimm_softc_list, sc, dimm_link);
183 	if (sc->dimm_sysctl_tree != NULL)
184 		sysctl_ctx_free(&sc->dimm_sysctl_ctx);
185 	kfree(sc, M_DEVBUF);
186 
187 	SYSCTL_XUNLOCK();
188 	return 0;
189 }
190 
191 void
192 dimm_sensor_attach(struct dimm_softc *sc, struct ksensor *sens)
193 {
194 	sensor_attach(&sc->dimm_sensdev, sens);
195 }
196 
197 void
198 dimm_sensor_detach(struct dimm_softc *sc, struct ksensor *sens)
199 {
200 	sensor_detach(&sc->dimm_sensdev, sens);
201 }
202 
203 void
204 dimm_set_temp_thresh(struct dimm_softc *sc, int hiwat, int lowat)
205 {
206 	sc->dimm_temp_hiwat = hiwat;
207 	sc->dimm_temp_lowat = lowat;
208 }
209 
210 void
211 dimm_sensor_temp(struct dimm_softc *sc, struct ksensor *sens, int temp)
212 {
213 	if (temp >= sc->dimm_temp_hiwat &&
214 	    (sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) == 0) {
215 		char temp_str[16], data[64];
216 
217 		ksnprintf(temp_str, sizeof(temp_str), "%d", temp);
218 		ksnprintf(data, sizeof(data), "node=%d channel=%d dimm=%d",
219 		    sc->dimm_node, sc->dimm_chan, sc->dimm_slot);
220 		devctl_notify("memtemp", "Thermal", temp_str, data);
221 
222 		kprintf("dimm%d: node%d channel%d DIMM%d "
223 		    "temperature (%dC) is too high (>= %dC)\n",
224 		    sc->dimm_id, sc->dimm_node, sc->dimm_chan, sc->dimm_slot,
225 		    temp, sc->dimm_temp_hiwat);
226 
227 		sc->dimm_sens_taskflags |= DIMM_SENS_TF_TEMP_CRIT;
228 	} else if ((sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) &&
229 	     temp < sc->dimm_temp_lowat) {
230 		sc->dimm_sens_taskflags &= ~DIMM_SENS_TF_TEMP_CRIT;
231 	}
232 
233 	if (sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT)
234 		sens->status = SENSOR_S_CRIT;
235 	else
236 		sens->status = SENSOR_S_OK;
237 	sens->flags &= ~SENSOR_FINVALID;
238 	sens->value = (temp * 1000000) + 273150000;
239 }
240 
241 void
242 dimm_sensor_ecc_set(struct dimm_softc *sc, struct ksensor *sens,
243     int ecc_cnt, boolean_t crit)
244 {
245 	if (crit && (sc->dimm_sens_taskflags & DIMM_SENS_TF_ECC_CRIT) == 0) {
246 		/* TODO devctl(4) */
247 		sc->dimm_sens_taskflags |= DIMM_SENS_TF_ECC_CRIT;
248 	} else if (!crit && (sc->dimm_sens_taskflags & DIMM_SENS_TF_ECC_CRIT)) {
249 		sc->dimm_sens_taskflags &= ~DIMM_SENS_TF_ECC_CRIT;
250 	}
251 	sc->dimm_ecc_cnt = ecc_cnt;
252 
253 	if (sc->dimm_sens_taskflags & DIMM_SENS_TF_ECC_CRIT)
254 		sens->status = SENSOR_S_CRIT;
255 	else
256 		sens->status = SENSOR_S_OK;
257 	sens->flags &= ~SENSOR_FINVALID;
258 	sens->value = ecc_cnt;
259 }
260 
261 static void
262 dimm_mod_unload(void)
263 {
264 	struct dimm_softc *sc;
265 
266 	SYSCTL_XLOCK();
267 
268 	while ((sc = TAILQ_FIRST(&dimm_softc_list)) != NULL) {
269 		int error;
270 
271 		error = dimm_destroy(sc);
272 		KASSERT(!error, ("dimm%d is still referenced, ref %d",
273 		    sc->dimm_id, sc->dimm_ref));
274 	}
275 
276 	SYSCTL_XUNLOCK();
277 }
278 
279 static int
280 dimm_mod_event(module_t mod, int type, void *unused)
281 {
282 	switch (type) {
283 	case MOD_LOAD:
284 		TAILQ_INIT(&dimm_softc_list);
285 		return 0;
286 
287 	case MOD_UNLOAD:
288 		dimm_mod_unload();
289 		return 0;
290 
291 	default:
292 		return 0;
293 	}
294 }
295 
296 static moduledata_t dimm_mod = {
297 	"dimm",
298 	dimm_mod_event,
299 	0
300 };
301 DECLARE_MODULE(dimm, dimm_mod, SI_SUB_PRE_DRIVERS, SI_ORDER_ANY);
302 MODULE_VERSION(dimm, 1);
303