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__FBSDID("$FreeBSD$");
26
27/** @file drm_sysctl.c
28 * Implementation of various sysctls for controlling DRM behavior and reporting
29 * debug information.
30 */
31
32#include <dev/drm2/drmP.h>
33#include <dev/drm2/drm.h>
34
35#include <sys/sysctl.h>
36
37static int	   drm_name_info DRM_SYSCTL_HANDLER_ARGS;
38static int	   drm_vm_info DRM_SYSCTL_HANDLER_ARGS;
39static int	   drm_clients_info DRM_SYSCTL_HANDLER_ARGS;
40static int	   drm_bufs_info DRM_SYSCTL_HANDLER_ARGS;
41static int	   drm_vblank_info DRM_SYSCTL_HANDLER_ARGS;
42
43struct drm_sysctl_list {
44	const char *name;
45	int	   (*f) DRM_SYSCTL_HANDLER_ARGS;
46} drm_sysctl_list[] = {
47	{"name",    drm_name_info},
48	{"vm",	    drm_vm_info},
49	{"clients", drm_clients_info},
50	{"bufs",    drm_bufs_info},
51	{"vblank",    drm_vblank_info},
52};
53#define DRM_SYSCTL_ENTRIES (sizeof(drm_sysctl_list)/sizeof(drm_sysctl_list[0]))
54
55struct drm_sysctl_info {
56	struct sysctl_ctx_list ctx;
57	char		       name[2];
58};
59
60int drm_sysctl_init(struct drm_device *dev)
61{
62	struct drm_sysctl_info *info;
63	struct sysctl_oid *oid;
64	struct sysctl_oid *top, *drioid;
65	int		  i;
66
67	info = malloc(sizeof *info, DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
68	dev->sysctl = info;
69
70	/* Add the sysctl node for DRI if it doesn't already exist */
71	drioid = SYSCTL_ADD_NODE(&info->ctx, &sysctl__hw_children, OID_AUTO,
72	    "dri", CTLFLAG_RW, NULL, "DRI Graphics");
73	if (!drioid)
74		return 1;
75
76	/* Find the next free slot under hw.dri */
77	i = 0;
78	SLIST_FOREACH(oid, SYSCTL_CHILDREN(drioid), oid_link) {
79		if (i <= oid->oid_arg2)
80			i = oid->oid_arg2 + 1;
81	}
82	if (i > 9)
83		return (1);
84
85	dev->sysctl_node_idx = i;
86	/* Add the hw.dri.x for our device */
87	info->name[0] = '0' + i;
88	info->name[1] = 0;
89	top = SYSCTL_ADD_NODE(&info->ctx, SYSCTL_CHILDREN(drioid),
90	    OID_AUTO, info->name, CTLFLAG_RW, NULL, NULL);
91	if (!top)
92		return 1;
93
94	for (i = 0; i < DRM_SYSCTL_ENTRIES; i++) {
95		oid = SYSCTL_ADD_OID(&info->ctx,
96			SYSCTL_CHILDREN(top),
97			OID_AUTO,
98			drm_sysctl_list[i].name,
99			CTLTYPE_STRING | CTLFLAG_RD,
100			dev,
101			0,
102			drm_sysctl_list[i].f,
103			"A",
104			NULL);
105		if (!oid)
106			return 1;
107	}
108	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO, "debug",
109	    CTLFLAG_RW, &drm_debug_flag, sizeof(drm_debug_flag),
110	    "Enable debugging output");
111	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO, "notyet",
112	    CTLFLAG_RW, &drm_notyet_flag, sizeof(drm_debug_flag),
113	    "Enable notyet reminders");
114
115	if (dev->driver->sysctl_init != NULL)
116		dev->driver->sysctl_init(dev, &info->ctx, top);
117
118	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO,
119	    "vblank_offdelay", CTLFLAG_RW, &drm_vblank_offdelay,
120	    sizeof(drm_vblank_offdelay),
121	    "");
122	SYSCTL_ADD_INT(&info->ctx, SYSCTL_CHILDREN(drioid), OID_AUTO,
123	    "timestamp_precision", CTLFLAG_RW, &drm_timestamp_precision,
124	    sizeof(drm_timestamp_precision),
125	    "");
126
127	return (0);
128}
129
130int drm_sysctl_cleanup(struct drm_device *dev)
131{
132	int error;
133
134	error = sysctl_ctx_free(&dev->sysctl->ctx);
135	free(dev->sysctl, DRM_MEM_DRIVER);
136	dev->sysctl = NULL;
137	if (dev->driver->sysctl_cleanup != NULL)
138		dev->driver->sysctl_cleanup(dev);
139
140	return (error);
141}
142
143#define DRM_SYSCTL_PRINT(fmt, arg...)				\
144do {								\
145	snprintf(buf, sizeof(buf), fmt, ##arg);			\
146	retcode = SYSCTL_OUT(req, buf, strlen(buf));		\
147	if (retcode)						\
148		goto done;					\
149} while (0)
150
151static int drm_name_info DRM_SYSCTL_HANDLER_ARGS
152{
153	struct drm_device *dev = arg1;
154	char buf[128];
155	int retcode;
156	int hasunique = 0;
157
158	DRM_SYSCTL_PRINT("%s 0x%x", dev->driver->name, dev2udev(dev->devnode));
159
160	DRM_LOCK(dev);
161	if (dev->unique) {
162		snprintf(buf, sizeof(buf), " %s", dev->unique);
163		hasunique = 1;
164	}
165	DRM_UNLOCK(dev);
166
167	if (hasunique)
168		SYSCTL_OUT(req, buf, strlen(buf));
169
170	SYSCTL_OUT(req, "", 1);
171
172done:
173	return retcode;
174}
175
176static int drm_vm_info DRM_SYSCTL_HANDLER_ARGS
177{
178	struct drm_device *dev = arg1;
179	drm_local_map_t *map, *tempmaps;
180	const char *types[] = {
181		[_DRM_FRAME_BUFFER] = "FB",
182		[_DRM_REGISTERS] = "REG",
183		[_DRM_SHM] = "SHM",
184		[_DRM_AGP] = "AGP",
185		[_DRM_SCATTER_GATHER] = "SG",
186		[_DRM_CONSISTENT] = "CONS",
187		[_DRM_GEM] = "GEM"
188	};
189	const char *type, *yesno;
190	int i, mapcount;
191	char buf[128];
192	int retcode;
193
194	/* We can't hold the lock while doing SYSCTL_OUTs, so allocate a
195	 * temporary copy of all the map entries and then SYSCTL_OUT that.
196	 */
197	DRM_LOCK(dev);
198
199	mapcount = 0;
200	TAILQ_FOREACH(map, &dev->maplist, link)
201		mapcount++;
202
203	tempmaps = malloc(sizeof(drm_local_map_t) * mapcount, DRM_MEM_DRIVER,
204	    M_NOWAIT);
205	if (tempmaps == NULL) {
206		DRM_UNLOCK(dev);
207		return ENOMEM;
208	}
209
210	i = 0;
211	TAILQ_FOREACH(map, &dev->maplist, link)
212		tempmaps[i++] = *map;
213
214	DRM_UNLOCK(dev);
215
216	DRM_SYSCTL_PRINT("\nslot offset	        size       "
217	    "type flags address            handle mtrr\n");
218
219	for (i = 0; i < mapcount; i++) {
220		map = &tempmaps[i];
221
222		switch(map->type) {
223		default:
224			type = "??";
225			break;
226		case _DRM_FRAME_BUFFER:
227		case _DRM_REGISTERS:
228		case _DRM_SHM:
229		case _DRM_AGP:
230		case _DRM_SCATTER_GATHER:
231		case _DRM_CONSISTENT:
232		case _DRM_GEM:
233			type = types[map->type];
234			break;
235		}
236
237		if (!map->mtrr)
238			yesno = "no";
239		else
240			yesno = "yes";
241
242		DRM_SYSCTL_PRINT(
243		    "%4d 0x%016lx 0x%08lx %4.4s  0x%02x 0x%016lx %6d %s\n",
244		    i, map->offset, map->size, type, map->flags,
245		    (unsigned long)map->virtual,
246		    (unsigned int)((unsigned long)map->handle >>
247		    DRM_MAP_HANDLE_SHIFT), yesno);
248	}
249	SYSCTL_OUT(req, "", 1);
250
251done:
252	free(tempmaps, DRM_MEM_DRIVER);
253	return retcode;
254}
255
256static int drm_bufs_info DRM_SYSCTL_HANDLER_ARGS
257{
258	struct drm_device	 *dev = arg1;
259	drm_device_dma_t *dma = dev->dma;
260	drm_device_dma_t tempdma;
261	int *templists;
262	int i;
263	char buf[128];
264	int retcode;
265
266	/* We can't hold the locks around DRM_SYSCTL_PRINT, so make a temporary
267	 * copy of the whole structure and the relevant data from buflist.
268	 */
269	DRM_LOCK(dev);
270	if (dma == NULL) {
271		DRM_UNLOCK(dev);
272		return 0;
273	}
274	DRM_SPINLOCK(&dev->dma_lock);
275	tempdma = *dma;
276	templists = malloc(sizeof(int) * dma->buf_count, DRM_MEM_DRIVER,
277	    M_NOWAIT);
278	for (i = 0; i < dma->buf_count; i++)
279		templists[i] = dma->buflist[i]->list;
280	dma = &tempdma;
281	DRM_SPINUNLOCK(&dev->dma_lock);
282	DRM_UNLOCK(dev);
283
284	DRM_SYSCTL_PRINT("\n o     size count  free	 segs pages    kB\n");
285	for (i = 0; i <= DRM_MAX_ORDER; i++) {
286		if (dma->bufs[i].buf_count)
287			DRM_SYSCTL_PRINT("%2d %8d %5d %5d %5d %5d %5d\n",
288				       i,
289				       dma->bufs[i].buf_size,
290				       dma->bufs[i].buf_count,
291				       atomic_read(&dma->bufs[i]
292						   .freelist.count),
293				       dma->bufs[i].seg_count,
294				       dma->bufs[i].seg_count
295				       *(1 << dma->bufs[i].page_order),
296				       (dma->bufs[i].seg_count
297					* (1 << dma->bufs[i].page_order))
298				       * (int)PAGE_SIZE / 1024);
299	}
300	DRM_SYSCTL_PRINT("\n");
301	for (i = 0; i < dma->buf_count; i++) {
302		if (i && !(i%32)) DRM_SYSCTL_PRINT("\n");
303		DRM_SYSCTL_PRINT(" %d", templists[i]);
304	}
305	DRM_SYSCTL_PRINT("\n");
306
307	SYSCTL_OUT(req, "", 1);
308done:
309	free(templists, DRM_MEM_DRIVER);
310	return retcode;
311}
312
313static int drm_clients_info DRM_SYSCTL_HANDLER_ARGS
314{
315	struct drm_device *dev = arg1;
316	struct drm_file *priv, *tempprivs;
317	char buf[128];
318	int retcode;
319	int privcount, i;
320
321	DRM_LOCK(dev);
322
323	privcount = 0;
324	TAILQ_FOREACH(priv, &dev->files, link)
325		privcount++;
326
327	tempprivs = malloc(sizeof(struct drm_file) * privcount, DRM_MEM_DRIVER,
328	    M_NOWAIT);
329	if (tempprivs == NULL) {
330		DRM_UNLOCK(dev);
331		return ENOMEM;
332	}
333	i = 0;
334	TAILQ_FOREACH(priv, &dev->files, link)
335		tempprivs[i++] = *priv;
336
337	DRM_UNLOCK(dev);
338
339	DRM_SYSCTL_PRINT(
340	    "\na dev            pid   uid      magic     ioctls\n");
341	for (i = 0; i < privcount; i++) {
342		priv = &tempprivs[i];
343		DRM_SYSCTL_PRINT("%c %-12s %5d %5d %10u %10lu\n",
344			       priv->authenticated ? 'y' : 'n',
345			       devtoname(priv->dev->devnode),
346			       priv->pid,
347			       priv->uid,
348			       priv->magic,
349			       priv->ioctl_count);
350	}
351
352	SYSCTL_OUT(req, "", 1);
353done:
354	free(tempprivs, DRM_MEM_DRIVER);
355	return retcode;
356}
357
358static int drm_vblank_info DRM_SYSCTL_HANDLER_ARGS
359{
360	struct drm_device *dev = arg1;
361	char buf[128];
362	int retcode;
363	int i;
364
365	DRM_SYSCTL_PRINT("\ncrtc ref count    last     enabled inmodeset\n");
366	DRM_LOCK(dev);
367	if (dev->_vblank_count == NULL)
368		goto done;
369	for (i = 0 ; i < dev->num_crtcs ; i++) {
370		DRM_SYSCTL_PRINT("  %02d  %02d %08d %08d %02d      %02d\n",
371		    i, dev->vblank_refcount[i],
372		    dev->_vblank_count[i],
373		    dev->last_vblank[i],
374		    dev->vblank_enabled[i],
375		    dev->vblank_inmodeset[i]);
376	}
377done:
378	DRM_UNLOCK(dev);
379
380	SYSCTL_OUT(req, "", -1);
381	return retcode;
382}
383