xref: /dflybsd-src/sys/dev/misc/dimm/dimm.c (revision 5943f66ca1b24657281aeb1be2396c522bee7e07)
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 
60 	struct ksensordev	dimm_sensdev;
61 	uint32_t		dimm_sens_taskflags;	/* DIMM_SENS_TF_ */
62 
63 	struct sysctl_ctx_list	dimm_sysctl_ctx;
64 	struct sysctl_oid	*dimm_sysctl_tree;
65 };
66 TAILQ_HEAD(dimm_softc_list, dimm_softc);
67 
68 #define DIMM_SENS_TF_TEMP_CRIT		0x1
69 
70 static void	dimm_mod_unload(void);
71 
72 /* In the ascending order of dimm_softc.dimm_id */
73 static struct dimm_softc_list	dimm_softc_list;
74 
75 static SYSCTL_NODE(_hw, OID_AUTO, dimminfo, CTLFLAG_RD, NULL,
76     "DIMM information");
77 
78 struct dimm_softc *
79 dimm_create(int node, int chan, int slot)
80 {
81 	struct dimm_softc *sc, *after = NULL;
82 	int dimm_id = 0;
83 
84 	SYSCTL_XLOCK();
85 
86 	TAILQ_FOREACH(sc, &dimm_softc_list, dimm_link) {
87 		/*
88 		 * Already exists; done.
89 		 */
90 		if (sc->dimm_node == node && sc->dimm_chan == chan &&
91 		    sc->dimm_slot == slot) {
92 			KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d",
93 			    sc->dimm_ref));
94 			sc->dimm_ref++;
95 			SYSCTL_XUNLOCK();
96 			return sc;
97 		}
98 
99 		/*
100 		 * Find the lowest usable id.
101 		 */
102 		if (sc->dimm_id == dimm_id) {
103 			++dimm_id;
104 			after = sc;
105 		}
106 	}
107 
108 	sc = kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
109 	sc->dimm_node = node;
110 	sc->dimm_chan = chan;
111 	sc->dimm_slot = slot;
112 	sc->dimm_id = dimm_id;
113 	sc->dimm_ref = 1;
114 	sc->dimm_temp_hiwat = DIMM_TEMP_HIWAT_DEFAULT;
115 	sc->dimm_temp_lowat = DIMM_TEMP_LOWAT_DEFAULT;
116 
117 	ksnprintf(sc->dimm_sensdev.xname, sizeof(sc->dimm_sensdev.xname),
118 	    "dimm%d", sc->dimm_id);
119 
120 	/*
121 	 * Create sysctl tree for the location information.  Use
122 	 * same name as the sensor device.
123 	 */
124 	sysctl_ctx_init(&sc->dimm_sysctl_ctx);
125 	sc->dimm_sysctl_tree = SYSCTL_ADD_NODE(&sc->dimm_sysctl_ctx,
126 	    SYSCTL_STATIC_CHILDREN(_hw_dimminfo), OID_AUTO,
127 	    sc->dimm_sensdev.xname, CTLFLAG_RD, 0, "");
128 	if (sc->dimm_sysctl_tree != NULL) {
129 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
130 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
131 		    "node", CTLFLAG_RD, &sc->dimm_node, 0,
132 		    "CPU node of this DIMM");
133 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
134 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
135 		    "chan", CTLFLAG_RD, &sc->dimm_chan, 0,
136 		    "channel of this DIMM");
137 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
138 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
139 		    "slot", CTLFLAG_RD, &sc->dimm_slot, 0,
140 		    "slot of this DIMM");
141 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
142 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
143 		    "temp_hiwat", CTLFLAG_RW, &sc->dimm_temp_hiwat, 0,
144 		    "Raise alarm once DIMM temperature is above this value "
145 		    "(unit: C)");
146 		SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
147 		    SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
148 		    "temp_lowat", CTLFLAG_RW, &sc->dimm_temp_lowat, 0,
149 		    "Cancel alarm once DIMM temperature is below this value "
150 		    "(unit: C)");
151 	}
152 
153 	if (after == NULL) {
154 		KKASSERT(sc->dimm_id == 0);
155 		TAILQ_INSERT_HEAD(&dimm_softc_list, sc, dimm_link);
156 	} else {
157 		TAILQ_INSERT_AFTER(&dimm_softc_list, after, sc, dimm_link);
158 	}
159 
160 	sensordev_install(&sc->dimm_sensdev);
161 
162 	SYSCTL_XUNLOCK();
163 	return sc;
164 }
165 
166 int
167 dimm_destroy(struct dimm_softc *sc)
168 {
169 	SYSCTL_XLOCK();
170 
171 	KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d", sc->dimm_ref));
172 	sc->dimm_ref--;
173 	if (sc->dimm_ref > 0) {
174 		SYSCTL_XUNLOCK();
175 		return EAGAIN;
176 	}
177 
178 	sensordev_deinstall(&sc->dimm_sensdev);
179 
180 	TAILQ_REMOVE(&dimm_softc_list, sc, dimm_link);
181 	if (sc->dimm_sysctl_tree != NULL)
182 		sysctl_ctx_free(&sc->dimm_sysctl_ctx);
183 	kfree(sc, M_DEVBUF);
184 
185 	SYSCTL_XUNLOCK();
186 	return 0;
187 }
188 
189 void
190 dimm_sensor_attach(struct dimm_softc *sc, struct ksensor *sens)
191 {
192 	sensor_attach(&sc->dimm_sensdev, sens);
193 }
194 
195 void
196 dimm_sensor_detach(struct dimm_softc *sc, struct ksensor *sens)
197 {
198 	sensor_detach(&sc->dimm_sensdev, sens);
199 }
200 
201 void
202 dimm_set_temp_thresh(struct dimm_softc *sc, int hiwat, int lowat)
203 {
204 	sc->dimm_temp_hiwat = hiwat;
205 	sc->dimm_temp_lowat = lowat;
206 }
207 
208 void
209 dimm_sensor_temp(struct dimm_softc *sc, struct ksensor *sens, int temp)
210 {
211 	if (temp >= sc->dimm_temp_hiwat &&
212 	    (sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) == 0) {
213 		char temp_str[16], data[64];
214 
215 		ksnprintf(temp_str, sizeof(temp_str), "%d", temp);
216 		ksnprintf(data, sizeof(data), "node=%d channel=%d dimm=%d",
217 		    sc->dimm_node, sc->dimm_chan, sc->dimm_slot);
218 		devctl_notify("memtemp", "Thermal", temp_str, data);
219 
220 		kprintf("dimm%d: node%d channel%d DIMM%d "
221 		    "temperature (%dC) is too high (>= %dC)\n",
222 		    sc->dimm_id, sc->dimm_node, sc->dimm_chan, sc->dimm_slot,
223 		    temp, sc->dimm_temp_hiwat);
224 
225 		sc->dimm_sens_taskflags |= DIMM_SENS_TF_TEMP_CRIT;
226 	} else if ((sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) &&
227 	     temp < sc->dimm_temp_lowat) {
228 		sc->dimm_sens_taskflags &= ~DIMM_SENS_TF_TEMP_CRIT;
229 	}
230 
231 	if (sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT)
232 		sens->status = SENSOR_S_CRIT;
233 	else
234 		sens->status = SENSOR_S_OK;
235 	sens->flags &= ~SENSOR_FINVALID;
236 	sens->value = (temp * 1000000) + 273150000;
237 }
238 
239 static void
240 dimm_mod_unload(void)
241 {
242 	struct dimm_softc *sc;
243 
244 	SYSCTL_XLOCK();
245 
246 	while ((sc = TAILQ_FIRST(&dimm_softc_list)) != NULL) {
247 		int error;
248 
249 		error = dimm_destroy(sc);
250 		KASSERT(!error, ("dimm%d is still referenced, ref %d",
251 		    sc->dimm_id, sc->dimm_ref));
252 	}
253 
254 	SYSCTL_XUNLOCK();
255 }
256 
257 static int
258 dimm_mod_event(module_t mod, int type, void *unused)
259 {
260 	switch (type) {
261 	case MOD_LOAD:
262 		TAILQ_INIT(&dimm_softc_list);
263 		return 0;
264 
265 	case MOD_UNLOAD:
266 		dimm_mod_unload();
267 		return 0;
268 
269 	default:
270 		return 0;
271 	}
272 }
273 
274 static moduledata_t dimm_mod = {
275 	"dimm",
276 	dimm_mod_event,
277 	0
278 };
279 DECLARE_MODULE(dimm, dimm_mod, SI_SUB_PRE_DRIVERS, SI_ORDER_ANY);
280 MODULE_VERSION(dimm, 1);
281