1/*-
2 * Copyright 2003 Eric Anholt
3 * All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * ERIC ANHOLT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24#include <sys/cdefs.h>
25/** @file drm_sysctl.c
26 * Implementation of various sysctls for controlling DRM behavior and reporting
27 * debug information.
28 */
29
30#include <dev/drm2/drmP.h>
31#include <dev/drm2/drm.h>
32
33#include <sys/sysctl.h>
34
35static int	   drm_name_info DRM_SYSCTL_HANDLER_ARGS;
36static int	   drm_vm_info DRM_SYSCTL_HANDLER_ARGS;
37static int	   drm_clients_info DRM_SYSCTL_HANDLER_ARGS;
38static int	   drm_bufs_info DRM_SYSCTL_HANDLER_ARGS;
39static int	   drm_vblank_info DRM_SYSCTL_HANDLER_ARGS;
40
41struct drm_sysctl_list {
42	const char *name;
43	int	   (*f) DRM_SYSCTL_HANDLER_ARGS;
44} drm_sysctl_list[] = {
45	{"name",    drm_name_info},
46	{"vm",	    drm_vm_info},
47	{"clients", drm_clients_info},
48	{"bufs",    drm_bufs_info},
49	{"vblank",    drm_vblank_info},
50};
51#define DRM_SYSCTL_ENTRIES (sizeof(drm_sysctl_list)/sizeof(drm_sysctl_list[0]))
52
53struct drm_sysctl_info {
54	struct sysctl_ctx_list ctx;
55	char		       name[2];
56};
57
58int drm_sysctl_init(struct drm_device *dev)
59{
60	struct drm_sysctl_info *info;
61	struct sysctl_oid *oid;
62	struct sysctl_oid *top, *drioid;
63	int		  i;
64
65	info = malloc(sizeof *info, DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
66	dev->sysctl = info;
67
68	/* Add the sysctl node for DRI if it doesn't already exist */
69	drioid = SYSCTL_ADD_NODE(&info->ctx, SYSCTL_CHILDREN(&sysctl___hw), OID_AUTO,
70	    "dri", CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, "DRI Graphics");
71	if (!drioid) {
72		free(dev->sysctl, DRM_MEM_DRIVER);
73		dev->sysctl = NULL;
74		return (-ENOMEM);
75	}
76
77	/* Find the next free slot under hw.dri */
78	i = 0;
79	SYSCTL_FOREACH(oid, SYSCTL_CHILDREN(drioid)) {
80		if (i <= oid->oid_arg2)
81			i = oid->oid_arg2 + 1;
82	}
83	if (i > 9) {
84		drm_sysctl_cleanup(dev);
85		return (-ENOSPC);
86	}
87
88	dev->sysctl_node_idx = i;
89	/* Add the hw.dri.x for our device */
90	info->name[0] = '0' + i;
91	info->name[1] = 0;
92	top = SYSCTL_ADD_NODE(&info->ctx, SYSCTL_CHILDREN(drioid),
93	    OID_AUTO, info->name, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, NULL);
94	if (!top) {
95		drm_sysctl_cleanup(dev);
96		return (-ENOMEM);
97	}
98
99	for (i = 0; i < DRM_SYSCTL_ENTRIES; i++) {
100		oid = SYSCTL_ADD_OID(&info->ctx, SYSCTL_CHILDREN(top),
101			OID_AUTO, drm_sysctl_list[i].name,
102			CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
103			dev, 0, drm_sysctl_list[i].f, "A", NULL);
104		if (!oid) {
105			drm_sysctl_cleanup(dev);
106			return (-ENOMEM);
107		}
108	}
109	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO, "debug",
110	    CTLFLAG_RW, &drm_debug, sizeof(drm_debug),
111	    "Enable debugging output");
112	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO, "notyet",
113	    CTLFLAG_RW, &drm_notyet, sizeof(drm_debug),
114	    "Enable notyet reminders");
115
116	if (dev->driver->sysctl_init != NULL)
117		dev->driver->sysctl_init(dev, &info->ctx, top);
118
119	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO,
120	    "vblank_offdelay", CTLFLAG_RW, &drm_vblank_offdelay,
121	    sizeof(drm_vblank_offdelay),
122	    "");
123	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO,
124	    "timestamp_precision", CTLFLAG_RW, &drm_timestamp_precision,
125	    sizeof(drm_timestamp_precision),
126	    "");
127
128	return (0);
129}
130
131int drm_sysctl_cleanup(struct drm_device *dev)
132{
133	int error;
134
135	if (dev->sysctl == NULL)
136		return (0);
137
138	error = sysctl_ctx_free(&dev->sysctl->ctx);
139	free(dev->sysctl, DRM_MEM_DRIVER);
140	dev->sysctl = NULL;
141	if (dev->driver->sysctl_cleanup != NULL)
142		dev->driver->sysctl_cleanup(dev);
143
144	return (-error);
145}
146
147#define DRM_SYSCTL_PRINT(fmt, arg...)				\
148do {								\
149	snprintf(buf, sizeof(buf), fmt, ##arg);			\
150	retcode = SYSCTL_OUT(req, buf, strlen(buf));		\
151	if (retcode)						\
152		goto done;					\
153} while (0)
154
155static int drm_name_info DRM_SYSCTL_HANDLER_ARGS
156{
157	struct drm_device *dev = arg1;
158	struct drm_minor *minor;
159	struct drm_master *master;
160	char buf[128];
161	int retcode;
162	int hasunique = 0;
163
164	/* FIXME: This still uses primary minor. */
165	minor = dev->primary;
166	DRM_SYSCTL_PRINT("%s 0x%jx", dev->driver->name,
167	    (uintmax_t)dev2udev(minor->device));
168
169	DRM_LOCK(dev);
170	master = minor->master;
171	if (master != NULL && master->unique) {
172		snprintf(buf, sizeof(buf), " %s", master->unique);
173		hasunique = 1;
174	}
175	DRM_UNLOCK(dev);
176
177	if (hasunique)
178		SYSCTL_OUT(req, buf, strlen(buf));
179
180	SYSCTL_OUT(req, "", 1);
181
182done:
183	return retcode;
184}
185
186static int drm_vm_info DRM_SYSCTL_HANDLER_ARGS
187{
188	struct drm_device *dev = arg1;
189	struct drm_map_list *entry;
190	struct drm_local_map *map, *tempmaps;
191	const char *types[] = {
192		[_DRM_FRAME_BUFFER] = "FB",
193		[_DRM_REGISTERS] = "REG",
194		[_DRM_SHM] = "SHM",
195		[_DRM_AGP] = "AGP",
196		[_DRM_SCATTER_GATHER] = "SG",
197		[_DRM_CONSISTENT] = "CONS",
198		[_DRM_GEM] = "GEM"
199	};
200	const char *type, *yesno;
201	int i, mapcount;
202	char buf[128];
203	int retcode;
204
205	/* We can't hold the lock while doing SYSCTL_OUTs, so allocate a
206	 * temporary copy of all the map entries and then SYSCTL_OUT that.
207	 */
208	DRM_LOCK(dev);
209
210	mapcount = 0;
211	list_for_each_entry(entry, &dev->maplist, head) {
212		if (entry->map != NULL)
213			mapcount++;
214	}
215
216	tempmaps = malloc(sizeof(*tempmaps) * mapcount, DRM_MEM_DRIVER,
217	    M_NOWAIT);
218	if (tempmaps == NULL) {
219		DRM_UNLOCK(dev);
220		return ENOMEM;
221	}
222
223	i = 0;
224	list_for_each_entry(entry, &dev->maplist, head) {
225		if (entry->map != NULL)
226			tempmaps[i++] = *entry->map;
227	}
228
229	DRM_UNLOCK(dev);
230
231	DRM_SYSCTL_PRINT("\nslot offset	        size       "
232	    "type flags address            mtrr\n");
233
234	for (i = 0; i < mapcount; i++) {
235		map = &tempmaps[i];
236
237		switch(map->type) {
238		default:
239			type = "??";
240			break;
241		case _DRM_FRAME_BUFFER:
242		case _DRM_REGISTERS:
243		case _DRM_SHM:
244		case _DRM_AGP:
245		case _DRM_SCATTER_GATHER:
246		case _DRM_CONSISTENT:
247		case _DRM_GEM:
248			type = types[map->type];
249			break;
250		}
251
252		if (map->mtrr < 0)
253			yesno = "no";
254		else
255			yesno = "yes";
256
257		DRM_SYSCTL_PRINT(
258		    "%4d 0x%016llx 0x%08lx %4.4s  0x%02x 0x%016lx %s\n",
259		    i, (unsigned long long)map->offset, map->size, type,
260		    map->flags, (unsigned long)map->handle, yesno);
261	}
262	SYSCTL_OUT(req, "", 1);
263
264done:
265	free(tempmaps, DRM_MEM_DRIVER);
266	return retcode;
267}
268
269static int drm_bufs_info DRM_SYSCTL_HANDLER_ARGS
270{
271	struct drm_device	 *dev = arg1;
272	struct drm_device_dma *dma = dev->dma;
273	struct drm_device_dma tempdma;
274	int *templists;
275	int i;
276	char buf[128];
277	int retcode;
278
279	/* We can't hold the locks around DRM_SYSCTL_PRINT, so make a temporary
280	 * copy of the whole structure and the relevant data from buflist.
281	 */
282	DRM_LOCK(dev);
283	if (dma == NULL) {
284		DRM_UNLOCK(dev);
285		return 0;
286	}
287	DRM_SPINLOCK(&dev->dma_lock);
288	tempdma = *dma;
289	templists = malloc(sizeof(int) * dma->buf_count, DRM_MEM_DRIVER,
290	    M_NOWAIT);
291	for (i = 0; i < dma->buf_count; i++)
292		templists[i] = dma->buflist[i]->list;
293	dma = &tempdma;
294	DRM_SPINUNLOCK(&dev->dma_lock);
295	DRM_UNLOCK(dev);
296
297	DRM_SYSCTL_PRINT("\n o     size count  free	 segs pages    kB\n");
298	for (i = 0; i <= DRM_MAX_ORDER; i++) {
299		if (dma->bufs[i].buf_count)
300			DRM_SYSCTL_PRINT("%2d %8d %5d %5d %5d %5d %5d\n",
301				       i,
302				       dma->bufs[i].buf_size,
303				       dma->bufs[i].buf_count,
304				       atomic_read(&dma->bufs[i]
305						   .freelist.count),
306				       dma->bufs[i].seg_count,
307				       dma->bufs[i].seg_count
308				       *(1 << dma->bufs[i].page_order),
309				       (dma->bufs[i].seg_count
310					* (1 << dma->bufs[i].page_order))
311				       * (int)PAGE_SIZE / 1024);
312	}
313	DRM_SYSCTL_PRINT("\n");
314	for (i = 0; i < dma->buf_count; i++) {
315		if (i && !(i%32)) DRM_SYSCTL_PRINT("\n");
316		DRM_SYSCTL_PRINT(" %d", templists[i]);
317	}
318	DRM_SYSCTL_PRINT("\n");
319
320	SYSCTL_OUT(req, "", 1);
321done:
322	free(templists, DRM_MEM_DRIVER);
323	return retcode;
324}
325
326static int drm_clients_info DRM_SYSCTL_HANDLER_ARGS
327{
328	struct drm_device *dev = arg1;
329	struct drm_file *priv, *tempprivs;
330	char buf[128];
331	int retcode;
332	int privcount, i;
333
334	DRM_LOCK(dev);
335
336	privcount = 0;
337	list_for_each_entry(priv, &dev->filelist, lhead)
338		privcount++;
339
340	tempprivs = malloc(sizeof(struct drm_file) * privcount, DRM_MEM_DRIVER,
341	    M_NOWAIT);
342	if (tempprivs == NULL) {
343		DRM_UNLOCK(dev);
344		return ENOMEM;
345	}
346	i = 0;
347	list_for_each_entry(priv, &dev->filelist, lhead)
348		tempprivs[i++] = *priv;
349
350	DRM_UNLOCK(dev);
351
352	DRM_SYSCTL_PRINT(
353	    "\na dev            pid   uid      magic     ioctls\n");
354	for (i = 0; i < privcount; i++) {
355		priv = &tempprivs[i];
356		DRM_SYSCTL_PRINT("%c %-12s %5d %5d %10u %10lu\n",
357			       priv->authenticated ? 'y' : 'n',
358			       devtoname(priv->minor->device),
359			       priv->pid,
360			       priv->uid,
361			       priv->magic,
362			       priv->ioctl_count);
363	}
364
365	SYSCTL_OUT(req, "", 1);
366done:
367	free(tempprivs, DRM_MEM_DRIVER);
368	return retcode;
369}
370
371static int drm_vblank_info DRM_SYSCTL_HANDLER_ARGS
372{
373	struct drm_device *dev = arg1;
374	char buf[128];
375	int retcode;
376	int i;
377
378	DRM_SYSCTL_PRINT("\ncrtc ref count    last     enabled inmodeset\n");
379	DRM_LOCK(dev);
380	if (dev->_vblank_count == NULL)
381		goto done;
382	for (i = 0 ; i < dev->num_crtcs ; i++) {
383		DRM_SYSCTL_PRINT("  %02d  %02d %08d %08d %02d      %02d\n",
384		    i, dev->vblank_refcount[i],
385		    dev->_vblank_count[i],
386		    dev->last_vblank[i],
387		    dev->vblank_enabled[i],
388		    dev->vblank_inmodeset[i]);
389	}
390done:
391	DRM_UNLOCK(dev);
392
393	SYSCTL_OUT(req, "", -1);
394	return retcode;
395}
396