1/*-
2 * SPDX-License-Identifier: MIT-CMU
3 *
4 * Mach Operating System
5 * Copyright (c) 1991,1990 Carnegie Mellon University
6 * All Rights Reserved.
7 *
8 * Permission to use, copy, modify and distribute this software and its
9 * documentation is hereby granted, provided that both the copyright
10 * notice and this permission notice appear in all copies of the
11 * software, derivative works or modified versions, and any portions
12 * thereof, and that both notices appear in supporting documentation.
13 *
14 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
15 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
16 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
17 *
18 * Carnegie Mellon requests users of this software to return to
19 *
20 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
21 *  School of Computer Science
22 *  Carnegie Mellon University
23 *  Pittsburgh PA 15213-3890
24 *
25 * any improvements or extensions that they make and grant Carnegie the
26 * rights to redistribute these changes.
27 */
28/*
29 * 	Author: Richard P. Draves, Carnegie Mellon University
30 *	Date:	10/90
31 */
32
33#include <sys/param.h>
34#include <sys/kdb.h>
35#include <sys/kernel.h>
36#include <sys/lock.h>
37#include <sys/proc.h>
38
39#include <vm/vm.h>
40#include <vm/pmap.h>
41#include <vm/vm_map.h>
42
43#include <machine/kdb.h>
44
45#include <ddb/ddb.h>
46#include <ddb/db_watch.h>
47
48/*
49 * Watchpoints.
50 */
51
52static bool		db_watchpoints_inserted = true;
53
54#define	NWATCHPOINTS	100
55static struct db_watchpoint	db_watch_table[NWATCHPOINTS];
56static db_watchpoint_t	db_next_free_watchpoint = &db_watch_table[0];
57static db_watchpoint_t	db_free_watchpoints = 0;
58static db_watchpoint_t	db_watchpoint_list = 0;
59
60static db_watchpoint_t	db_watchpoint_alloc(void);
61static void		db_watchpoint_free(db_watchpoint_t watch);
62static void		db_delete_watchpoint(vm_map_t map, db_addr_t addr);
63#ifdef notused
64static bool		db_find_watchpoint(vm_map_t map, db_addr_t addr,
65					db_regs_t *regs);
66#endif
67static void		db_list_watchpoints(void);
68static void		db_set_watchpoint(vm_map_t map, db_addr_t addr,
69				       vm_size_t size);
70
71static db_watchpoint_t
72db_watchpoint_alloc(void)
73{
74	db_watchpoint_t	watch;
75
76	if ((watch = db_free_watchpoints) != 0) {
77	    db_free_watchpoints = watch->link;
78	    return (watch);
79	}
80	if (db_next_free_watchpoint == &db_watch_table[NWATCHPOINTS]) {
81	    db_printf("All watchpoints used.\n");
82	    return (0);
83	}
84	watch = db_next_free_watchpoint;
85	db_next_free_watchpoint++;
86
87	return (watch);
88}
89
90static void
91db_watchpoint_free(db_watchpoint_t watch)
92{
93	watch->link = db_free_watchpoints;
94	db_free_watchpoints = watch;
95}
96
97static void
98db_set_watchpoint(vm_map_t map, db_addr_t addr, vm_size_t size)
99{
100	db_watchpoint_t	watch;
101
102	if (map == NULL) {
103	    db_printf("No map.\n");
104	    return;
105	}
106
107	/*
108	 *	Should we do anything fancy with overlapping regions?
109	 */
110
111	for (watch = db_watchpoint_list;
112	     watch != 0;
113	     watch = watch->link)
114	    if (db_map_equal(watch->map, map) &&
115		(watch->loaddr == addr) &&
116		(watch->hiaddr == addr+size)) {
117		db_printf("Already set.\n");
118		return;
119	    }
120
121	watch = db_watchpoint_alloc();
122	if (watch == 0) {
123	    db_printf("Too many watchpoints.\n");
124	    return;
125	}
126
127	watch->map = map;
128	watch->loaddr = addr;
129	watch->hiaddr = addr+size;
130
131	watch->link = db_watchpoint_list;
132	db_watchpoint_list = watch;
133
134	db_watchpoints_inserted = false;
135}
136
137static void
138db_delete_watchpoint(vm_map_t map, db_addr_t addr)
139{
140	db_watchpoint_t	watch;
141	db_watchpoint_t	*prev;
142
143	for (prev = &db_watchpoint_list;
144	     (watch = *prev) != 0;
145	     prev = &watch->link)
146	    if (db_map_equal(watch->map, map) &&
147		(watch->loaddr <= addr) &&
148		(addr < watch->hiaddr)) {
149		*prev = watch->link;
150		db_watchpoint_free(watch);
151		return;
152	    }
153
154	db_printf("Not set.\n");
155}
156
157static void
158db_list_watchpoints(void)
159{
160	db_watchpoint_t	watch;
161
162	if (db_watchpoint_list == 0) {
163	    db_printf("No watchpoints set\n");
164	    return;
165	}
166
167#ifdef __LP64__
168	db_printf(" Map                Address          Size\n");
169#else
170	db_printf(" Map        Address  Size\n");
171#endif
172	for (watch = db_watchpoint_list;
173	     watch != 0;
174	     watch = watch->link)
175#ifdef __LP64__
176	    db_printf("%s%16p  %16lx  %lx\n",
177#else
178	    db_printf("%s%8p  %8lx  %lx\n",
179#endif
180		      db_map_current(watch->map) ? "*" : " ",
181		      (void *)watch->map, (long)watch->loaddr,
182		      (long)watch->hiaddr - (long)watch->loaddr);
183}
184
185/* Delete watchpoint */
186/*ARGSUSED*/
187void
188db_deletewatch_cmd(db_expr_t addr, bool have_addr, db_expr_t count,
189   char *modif)
190{
191	db_delete_watchpoint(db_map_addr(addr), addr);
192}
193
194/* Set watchpoint */
195/*ARGSUSED*/
196void
197db_watchpoint_cmd(db_expr_t addr, bool have_addr, db_expr_t count,
198   char *modif)
199{
200	vm_size_t	size;
201	db_expr_t	value;
202
203	if (db_expression(&value))
204	    size = (vm_size_t) value;
205	else
206	    size = 4;
207	db_skip_to_eol();
208
209	db_set_watchpoint(db_map_addr(addr), addr, size);
210}
211
212/*
213 * At least one non-optional show-command must be implemented using
214 * DB_SHOW_COMMAND() so that db_show_cmd_set gets created.  Here is one.
215 */
216DB_SHOW_COMMAND_FLAGS(watches, db_listwatch_cmd, DB_CMD_MEMSAFE)
217{
218	db_list_watchpoints();
219	db_md_list_watchpoints();
220}
221
222void
223db_set_watchpoints(void)
224{
225	db_watchpoint_t	watch;
226
227	if (!db_watchpoints_inserted) {
228	    for (watch = db_watchpoint_list;
229	         watch != 0;
230	         watch = watch->link)
231		pmap_protect(watch->map->pmap,
232			     trunc_page(watch->loaddr),
233			     round_page(watch->hiaddr),
234			     VM_PROT_READ);
235
236	    db_watchpoints_inserted = true;
237	}
238}
239
240void
241db_clear_watchpoints(void)
242{
243	db_watchpoints_inserted = false;
244}
245
246#ifdef notused
247static bool
248db_find_watchpoint(vm_map_t map, db_addr_t addr, db_regs_t regs)
249{
250	db_watchpoint_t watch;
251	db_watchpoint_t found = 0;
252
253	for (watch = db_watchpoint_list;
254	     watch != 0;
255	     watch = watch->link)
256	    if (db_map_equal(watch->map, map)) {
257		if ((watch->loaddr <= addr) &&
258		    (addr < watch->hiaddr))
259		    return (true);
260		else if ((trunc_page(watch->loaddr) <= addr) &&
261			 (addr < round_page(watch->hiaddr)))
262		    found = watch;
263	    }
264
265	/*
266	 *	We didn't hit exactly on a watchpoint, but we are
267	 *	in a protected region.  We want to single-step
268	 *	and then re-protect.
269	 */
270
271	if (found) {
272	    db_watchpoints_inserted = false;
273	    db_single_step(regs);
274	}
275
276	return (false);
277}
278#endif
279
280/* Delete hardware watchpoint */
281void
282db_deletehwatch_cmd(db_expr_t addr, bool have_addr, db_expr_t size,
283   char *modif)
284{
285	int rc;
286
287	if (size < 0)
288		size = 4;
289
290	rc = kdb_cpu_clr_watchpoint((vm_offset_t)addr, (vm_size_t)size);
291	switch (rc) {
292	case ENXIO:
293		/* Not supported, ignored. */
294		break;
295	case EINVAL:
296		db_printf("Invalid watchpoint address or size.\n");
297		break;
298	default:
299		if (rc != 0)
300			db_printf("Hardware watchpoint could not be deleted, "
301			    "status=%d\n", rc);
302		break;
303	}
304}
305
306/* Set hardware watchpoint */
307void
308db_hwatchpoint_cmd(db_expr_t addr, bool have_addr, db_expr_t size,
309   char *modif)
310{
311	int rc;
312
313	if (size < 0)
314		size = 4;
315
316	rc = kdb_cpu_set_watchpoint((vm_offset_t)addr, (vm_size_t)size,
317	    KDB_DBG_ACCESS_W);
318
319	switch (rc) {
320	case EINVAL:
321		db_printf("Invalid watchpoint size or address.\n");
322		break;
323	case EBUSY:
324		db_printf("No hardware watchpoints available.\n");
325		break;
326	case ENXIO:
327		db_printf("Hardware watchpoints are not supported on this platform.\n");
328		break;
329	default:
330		if (rc != 0)
331			db_printf("Could not set hardware watchpoint, "
332			    "status=%d\n", rc);
333	}
334}
335