Merge branch 'vendor/GCC50'
[dragonfly.git] / sys / dev / misc / dimm / dimm.c
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);