xref: /linux/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c (revision 746680ec6696585e30db3e18c93a63df9cbec39c)
1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright © 2023-2024 Intel Corporation
4  */
5 
6 #include <linux/debugfs.h>
7 
8 #include <drm/drm_print.h>
9 #include <drm/drm_debugfs.h>
10 
11 #include "xe_bo.h"
12 #include "xe_debugfs.h"
13 #include "xe_device.h"
14 #include "xe_gt.h"
15 #include "xe_gt_debugfs.h"
16 #include "xe_gt_sriov_pf_config.h"
17 #include "xe_gt_sriov_pf_control.h"
18 #include "xe_gt_sriov_pf_debugfs.h"
19 #include "xe_gt_sriov_pf_helpers.h"
20 #include "xe_gt_sriov_pf_migration.h"
21 #include "xe_gt_sriov_pf_monitor.h"
22 #include "xe_gt_sriov_pf_policy.h"
23 #include "xe_gt_sriov_pf_service.h"
24 #include "xe_pm.h"
25 #include "xe_sriov_pf.h"
26 
27 /*
28  *      /sys/kernel/debug/dri/0/
29  *      ├── gt0		# d_inode->i_private = gt
30  *      │   ├── pf	# d_inode->i_private = gt
31  *      │   ├── vf1	# d_inode->i_private = VFID(1)
32  *      :   :
33  *      │   ├── vfN	# d_inode->i_private = VFID(N)
34  */
35 
36 static void *extract_priv(struct dentry *d)
37 {
38 	return d->d_inode->i_private;
39 }
40 
41 static struct xe_gt *extract_gt(struct dentry *d)
42 {
43 	return extract_priv(d->d_parent);
44 }
45 
46 static unsigned int extract_vfid(struct dentry *d)
47 {
48 	return extract_priv(d) == extract_gt(d) ? PFID : (uintptr_t)extract_priv(d);
49 }
50 
51 /*
52  *      /sys/kernel/debug/dri/0/
53  *      ├── gt0
54  *      │   ├── pf
55  *      │   │   ├── contexts_provisioned
56  *      │   │   ├── doorbells_provisioned
57  *      │   │   ├── runtime_registers
58  *      │   │   ├── negotiated_versions
59  *      │   │   ├── adverse_events
60  *      ├── gt1
61  *      │   ├── pf
62  *      │   │   ├── ...
63  */
64 
65 static const struct drm_info_list pf_info[] = {
66 	{
67 		"contexts_provisioned",
68 		.show = xe_gt_debugfs_simple_show,
69 		.data = xe_gt_sriov_pf_config_print_ctxs,
70 	},
71 	{
72 		"doorbells_provisioned",
73 		.show = xe_gt_debugfs_simple_show,
74 		.data = xe_gt_sriov_pf_config_print_dbs,
75 	},
76 	{
77 		"runtime_registers",
78 		.show = xe_gt_debugfs_simple_show,
79 		.data = xe_gt_sriov_pf_service_print_runtime,
80 	},
81 	{
82 		"adverse_events",
83 		.show = xe_gt_debugfs_simple_show,
84 		.data = xe_gt_sriov_pf_monitor_print_events,
85 	},
86 };
87 
88 /*
89  *      /sys/kernel/debug/dri/0/
90  *      ├── gt0
91  *      │   ├── pf
92  *      │   │   ├── ggtt_available
93  *      │   │   ├── ggtt_provisioned
94  */
95 
96 static const struct drm_info_list pf_ggtt_info[] = {
97 	{
98 		"ggtt_available",
99 		.show = xe_gt_debugfs_simple_show,
100 		.data = xe_gt_sriov_pf_config_print_available_ggtt,
101 	},
102 	{
103 		"ggtt_provisioned",
104 		.show = xe_gt_debugfs_simple_show,
105 		.data = xe_gt_sriov_pf_config_print_ggtt,
106 	},
107 };
108 
109 /*
110  *      /sys/kernel/debug/dri/0/
111  *      ├── gt0
112  *      │   ├── pf
113  *      │   │   ├── lmem_provisioned
114  */
115 
116 static const struct drm_info_list pf_lmem_info[] = {
117 	{
118 		"lmem_provisioned",
119 		.show = xe_gt_debugfs_simple_show,
120 		.data = xe_gt_sriov_pf_config_print_lmem,
121 	},
122 };
123 
124 /*
125  *      /sys/kernel/debug/dri/0/
126  *      ├── gt0
127  *      │   ├── pf
128  *      │   │   ├── reset_engine
129  *      │   │   ├── sample_period
130  *      │   │   ├── sched_if_idle
131  */
132 
133 #define DEFINE_SRIOV_GT_POLICY_DEBUGFS_ATTRIBUTE(POLICY, TYPE, FORMAT)		\
134 										\
135 static int POLICY##_set(void *data, u64 val)					\
136 {										\
137 	struct xe_gt *gt = extract_gt(data);					\
138 	struct xe_device *xe = gt_to_xe(gt);					\
139 	int err;								\
140 										\
141 	if (val > (TYPE)~0ull)							\
142 		return -EOVERFLOW;						\
143 										\
144 	xe_pm_runtime_get(xe);							\
145 	err = xe_gt_sriov_pf_policy_set_##POLICY(gt, val);			\
146 	xe_pm_runtime_put(xe);							\
147 										\
148 	return err;								\
149 }										\
150 										\
151 static int POLICY##_get(void *data, u64 *val)					\
152 {										\
153 	struct xe_gt *gt = extract_gt(data);					\
154 										\
155 	*val = xe_gt_sriov_pf_policy_get_##POLICY(gt);				\
156 	return 0;								\
157 }										\
158 										\
159 DEFINE_DEBUGFS_ATTRIBUTE(POLICY##_fops, POLICY##_get, POLICY##_set, FORMAT)
160 
161 DEFINE_SRIOV_GT_POLICY_DEBUGFS_ATTRIBUTE(reset_engine, bool, "%llu\n");
162 DEFINE_SRIOV_GT_POLICY_DEBUGFS_ATTRIBUTE(sched_if_idle, bool, "%llu\n");
163 DEFINE_SRIOV_GT_POLICY_DEBUGFS_ATTRIBUTE(sample_period, u32, "%llu\n");
164 
165 static void pf_add_policy_attrs(struct xe_gt *gt, struct dentry *parent)
166 {
167 	xe_gt_assert(gt, gt == extract_gt(parent));
168 	xe_gt_assert(gt, PFID == extract_vfid(parent));
169 
170 	debugfs_create_file_unsafe("reset_engine", 0644, parent, parent, &reset_engine_fops);
171 	debugfs_create_file_unsafe("sched_if_idle", 0644, parent, parent, &sched_if_idle_fops);
172 	debugfs_create_file_unsafe("sample_period_ms", 0644, parent, parent, &sample_period_fops);
173 }
174 
175 /*
176  *      /sys/kernel/debug/dri/0/
177  *      ├── gt0
178  *      │   ├── pf
179  *      │   │   ├── ggtt_spare
180  *      │   │   ├── lmem_spare
181  *      │   │   ├── doorbells_spare
182  *      │   │   ├── contexts_spare
183  *      │   │   ├── exec_quantum_ms
184  *      │   │   ├── preempt_timeout_us
185  *      │   │   ├── sched_priority
186  *      │   ├── vf1
187  *      │   │   ├── ggtt_quota
188  *      │   │   ├── lmem_quota
189  *      │   │   ├── doorbells_quota
190  *      │   │   ├── contexts_quota
191  *      │   │   ├── exec_quantum_ms
192  *      │   │   ├── preempt_timeout_us
193  *      │   │   ├── sched_priority
194  */
195 
196 #define DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(CONFIG, TYPE, FORMAT)		\
197 										\
198 static int CONFIG##_set(void *data, u64 val)					\
199 {										\
200 	struct xe_gt *gt = extract_gt(data);					\
201 	unsigned int vfid = extract_vfid(data);					\
202 	struct xe_device *xe = gt_to_xe(gt);					\
203 	int err;								\
204 										\
205 	if (val > (TYPE)~0ull)							\
206 		return -EOVERFLOW;						\
207 										\
208 	xe_pm_runtime_get(xe);							\
209 	err = xe_sriov_pf_wait_ready(xe) ?:					\
210 	      xe_gt_sriov_pf_config_set_##CONFIG(gt, vfid, val);		\
211 	xe_pm_runtime_put(xe);							\
212 										\
213 	return err;								\
214 }										\
215 										\
216 static int CONFIG##_get(void *data, u64 *val)					\
217 {										\
218 	struct xe_gt *gt = extract_gt(data);					\
219 	unsigned int vfid = extract_vfid(data);					\
220 										\
221 	*val = xe_gt_sriov_pf_config_get_##CONFIG(gt, vfid);			\
222 	return 0;								\
223 }										\
224 										\
225 DEFINE_DEBUGFS_ATTRIBUTE(CONFIG##_fops, CONFIG##_get, CONFIG##_set, FORMAT)
226 
227 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(ggtt, u64, "%llu\n");
228 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(lmem, u64, "%llu\n");
229 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(ctxs, u32, "%llu\n");
230 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(dbs, u32, "%llu\n");
231 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(exec_quantum, u32, "%llu\n");
232 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(preempt_timeout, u32, "%llu\n");
233 DEFINE_SRIOV_GT_CONFIG_DEBUGFS_ATTRIBUTE(sched_priority, u32, "%llu\n");
234 
235 /*
236  *      /sys/kernel/debug/dri/0/
237  *      ├── gt0
238  *      │   ├── pf
239  *      │   │   ├── threshold_cat_error_count
240  *      │   │   ├── threshold_doorbell_time_us
241  *      │   │   ├── threshold_engine_reset_count
242  *      │   │   ├── threshold_guc_time_us
243  *      │   │   ├── threshold_irq_time_us
244  *      │   │   ├── threshold_page_fault_count
245  *      │   ├── vf1
246  *      │   │   ├── threshold_cat_error_count
247  *      │   │   ├── threshold_doorbell_time_us
248  *      │   │   ├── threshold_engine_reset_count
249  *      │   │   ├── threshold_guc_time_us
250  *      │   │   ├── threshold_irq_time_us
251  *      │   │   ├── threshold_page_fault_count
252  */
253 
254 static int set_threshold(void *data, u64 val, enum xe_guc_klv_threshold_index index)
255 {
256 	struct xe_gt *gt = extract_gt(data);
257 	unsigned int vfid = extract_vfid(data);
258 	struct xe_device *xe = gt_to_xe(gt);
259 	int err;
260 
261 	if (val > (u32)~0ull)
262 		return -EOVERFLOW;
263 
264 	xe_pm_runtime_get(xe);
265 	err = xe_gt_sriov_pf_config_set_threshold(gt, vfid, index, val);
266 	xe_pm_runtime_put(xe);
267 
268 	return err;
269 }
270 
271 static int get_threshold(void *data, u64 *val, enum xe_guc_klv_threshold_index index)
272 {
273 	struct xe_gt *gt = extract_gt(data);
274 	unsigned int vfid = extract_vfid(data);
275 
276 	*val = xe_gt_sriov_pf_config_get_threshold(gt, vfid, index);
277 	return 0;
278 }
279 
280 #define DEFINE_SRIOV_GT_THRESHOLD_DEBUGFS_ATTRIBUTE(THRESHOLD, INDEX)		\
281 										\
282 static int THRESHOLD##_set(void *data, u64 val)					\
283 {										\
284 	return set_threshold(data, val, INDEX);					\
285 }										\
286 										\
287 static int THRESHOLD##_get(void *data, u64 *val)				\
288 {										\
289 	return get_threshold(data, val, INDEX);					\
290 }										\
291 										\
292 DEFINE_DEBUGFS_ATTRIBUTE(THRESHOLD##_fops, THRESHOLD##_get, THRESHOLD##_set, "%llu\n")
293 
294 /* generate all threshold attributes */
295 #define define_threshold_attribute(TAG, NAME, ...) \
296 	DEFINE_SRIOV_GT_THRESHOLD_DEBUGFS_ATTRIBUTE(NAME, MAKE_XE_GUC_KLV_THRESHOLD_INDEX(TAG));
297 MAKE_XE_GUC_KLV_THRESHOLDS_SET(define_threshold_attribute)
298 #undef define_threshold_attribute
299 
300 static void pf_add_config_attrs(struct xe_gt *gt, struct dentry *parent, unsigned int vfid)
301 {
302 	xe_gt_assert(gt, gt == extract_gt(parent));
303 	xe_gt_assert(gt, vfid == extract_vfid(parent));
304 
305 	if (xe_gt_is_main_type(gt)) {
306 		debugfs_create_file_unsafe(vfid ? "ggtt_quota" : "ggtt_spare",
307 					   0644, parent, parent, &ggtt_fops);
308 		if (xe_device_has_lmtt(gt_to_xe(gt)))
309 			debugfs_create_file_unsafe(vfid ? "lmem_quota" : "lmem_spare",
310 						   0644, parent, parent, &lmem_fops);
311 	}
312 	debugfs_create_file_unsafe(vfid ? "doorbells_quota" : "doorbells_spare",
313 				   0644, parent, parent, &dbs_fops);
314 	debugfs_create_file_unsafe(vfid ? "contexts_quota" : "contexts_spare",
315 				   0644, parent, parent, &ctxs_fops);
316 	debugfs_create_file_unsafe("exec_quantum_ms", 0644, parent, parent,
317 				   &exec_quantum_fops);
318 	debugfs_create_file_unsafe("preempt_timeout_us", 0644, parent, parent,
319 				   &preempt_timeout_fops);
320 	debugfs_create_file_unsafe("sched_priority", 0644, parent, parent,
321 				   &sched_priority_fops);
322 
323 	/* register all threshold attributes */
324 #define register_threshold_attribute(TAG, NAME, ...) \
325 	debugfs_create_file_unsafe("threshold_" #NAME, 0644, parent, parent, \
326 				   &NAME##_fops);
327 	MAKE_XE_GUC_KLV_THRESHOLDS_SET(register_threshold_attribute)
328 #undef register_threshold_attribute
329 }
330 
331 /*
332  *      /sys/kernel/debug/dri/0/
333  *      ├── gt0
334  *      │   ├── vf1
335  *      │   │   ├── control { stop, pause, resume }
336  */
337 
338 static const struct {
339 	const char *cmd;
340 	int (*fn)(struct xe_gt *gt, unsigned int vfid);
341 } control_cmds[] = {
342 	{ "stop", xe_gt_sriov_pf_control_stop_vf },
343 	{ "pause", xe_gt_sriov_pf_control_pause_vf },
344 	{ "resume", xe_gt_sriov_pf_control_resume_vf },
345 #ifdef CONFIG_DRM_XE_DEBUG_SRIOV
346 	{ "restore!", xe_gt_sriov_pf_migration_restore_guc_state },
347 #endif
348 };
349 
350 static ssize_t control_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
351 {
352 	struct dentry *dent = file_dentry(file);
353 	struct dentry *parent = dent->d_parent;
354 	struct xe_gt *gt = extract_gt(parent);
355 	struct xe_device *xe = gt_to_xe(gt);
356 	unsigned int vfid = extract_vfid(parent);
357 	int ret = -EINVAL;
358 	char cmd[32];
359 	size_t n;
360 
361 	xe_gt_assert(gt, vfid);
362 	xe_gt_sriov_pf_assert_vfid(gt, vfid);
363 
364 	if (*pos)
365 		return -ESPIPE;
366 
367 	if (count > sizeof(cmd) - 1)
368 		return -EINVAL;
369 
370 	ret = simple_write_to_buffer(cmd, sizeof(cmd) - 1, pos, buf, count);
371 	if (ret < 0)
372 		return ret;
373 	cmd[ret] = '\0';
374 
375 	for (n = 0; n < ARRAY_SIZE(control_cmds); n++) {
376 		xe_gt_assert(gt, sizeof(cmd) > strlen(control_cmds[n].cmd));
377 
378 		if (sysfs_streq(cmd, control_cmds[n].cmd)) {
379 			xe_pm_runtime_get(xe);
380 			ret = control_cmds[n].fn ? (*control_cmds[n].fn)(gt, vfid) : 0;
381 			xe_pm_runtime_put(xe);
382 			break;
383 		}
384 	}
385 
386 	return (ret < 0) ? ret : count;
387 }
388 
389 static ssize_t control_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
390 {
391 	char help[128];
392 	size_t n;
393 
394 	help[0] = '\0';
395 	for (n = 0; n < ARRAY_SIZE(control_cmds); n++) {
396 		strlcat(help, control_cmds[n].cmd, sizeof(help));
397 		strlcat(help, "\n", sizeof(help));
398 	}
399 
400 	return simple_read_from_buffer(buf, count, ppos, help, strlen(help));
401 }
402 
403 static const struct file_operations control_ops = {
404 	.owner		= THIS_MODULE,
405 	.open		= simple_open,
406 	.write		= control_write,
407 	.read		= control_read,
408 	.llseek		= default_llseek,
409 };
410 
411 /*
412  *      /sys/kernel/debug/dri/0/
413  *      ├── gt0
414  *      │   ├── vf1
415  *      │   │   ├── guc_state
416  */
417 static ssize_t guc_state_read(struct file *file, char __user *buf,
418 			      size_t count, loff_t *pos)
419 {
420 	struct dentry *dent = file_dentry(file);
421 	struct dentry *parent = dent->d_parent;
422 	struct xe_gt *gt = extract_gt(parent);
423 	unsigned int vfid = extract_vfid(parent);
424 
425 	return xe_gt_sriov_pf_migration_read_guc_state(gt, vfid, buf, count, pos);
426 }
427 
428 static ssize_t guc_state_write(struct file *file, const char __user *buf,
429 			       size_t count, loff_t *pos)
430 {
431 	struct dentry *dent = file_dentry(file);
432 	struct dentry *parent = dent->d_parent;
433 	struct xe_gt *gt = extract_gt(parent);
434 	unsigned int vfid = extract_vfid(parent);
435 
436 	if (*pos)
437 		return -EINVAL;
438 
439 	return xe_gt_sriov_pf_migration_write_guc_state(gt, vfid, buf, count);
440 }
441 
442 static const struct file_operations guc_state_ops = {
443 	.owner		= THIS_MODULE,
444 	.read		= guc_state_read,
445 	.write		= guc_state_write,
446 	.llseek		= default_llseek,
447 };
448 
449 /*
450  *      /sys/kernel/debug/dri/0/
451  *      ├── gt0
452  *      │   ├── vf1
453  *      │   │   ├── config_blob
454  */
455 static ssize_t config_blob_read(struct file *file, char __user *buf,
456 				size_t count, loff_t *pos)
457 {
458 	struct dentry *dent = file_dentry(file);
459 	struct dentry *parent = dent->d_parent;
460 	struct xe_gt *gt = extract_gt(parent);
461 	unsigned int vfid = extract_vfid(parent);
462 	ssize_t ret;
463 	void *tmp;
464 
465 	ret = xe_gt_sriov_pf_config_save(gt, vfid, NULL, 0);
466 	if (!ret)
467 		return -ENODATA;
468 	if (ret < 0)
469 		return ret;
470 
471 	tmp = kzalloc(ret, GFP_KERNEL);
472 	if (!tmp)
473 		return -ENOMEM;
474 
475 	ret = xe_gt_sriov_pf_config_save(gt, vfid, tmp, ret);
476 	if (ret > 0)
477 		ret = simple_read_from_buffer(buf, count, pos, tmp, ret);
478 
479 	kfree(tmp);
480 	return ret;
481 }
482 
483 static ssize_t config_blob_write(struct file *file, const char __user *buf,
484 				 size_t count, loff_t *pos)
485 {
486 	struct dentry *dent = file_dentry(file);
487 	struct dentry *parent = dent->d_parent;
488 	struct xe_gt *gt = extract_gt(parent);
489 	unsigned int vfid = extract_vfid(parent);
490 	ssize_t ret;
491 	void *tmp;
492 
493 	if (*pos)
494 		return -EINVAL;
495 
496 	if (!count)
497 		return -ENODATA;
498 
499 	if (count > SZ_4K)
500 		return -EINVAL;
501 
502 	tmp = kzalloc(count, GFP_KERNEL);
503 	if (!tmp)
504 		return -ENOMEM;
505 
506 	if (copy_from_user(tmp, buf, count)) {
507 		ret = -EFAULT;
508 	} else {
509 		ret = xe_gt_sriov_pf_config_restore(gt, vfid, tmp, count);
510 		if (!ret)
511 			ret = count;
512 	}
513 	kfree(tmp);
514 	return ret;
515 }
516 
517 static const struct file_operations config_blob_ops = {
518 	.owner		= THIS_MODULE,
519 	.read		= config_blob_read,
520 	.write		= config_blob_write,
521 	.llseek		= default_llseek,
522 };
523 
524 /**
525  * xe_gt_sriov_pf_debugfs_register - Register SR-IOV PF specific entries in GT debugfs.
526  * @gt: the &xe_gt to register
527  * @root: the &dentry that represents the GT directory
528  *
529  * Register SR-IOV PF entries that are GT related and must be shown under GT debugfs.
530  */
531 void xe_gt_sriov_pf_debugfs_register(struct xe_gt *gt, struct dentry *root)
532 {
533 	struct xe_device *xe = gt_to_xe(gt);
534 	struct drm_minor *minor = xe->drm.primary;
535 	int n, totalvfs = xe_sriov_pf_get_totalvfs(xe);
536 	struct dentry *pfdentry;
537 	struct dentry *vfdentry;
538 	char buf[14]; /* should be enough up to "vf%u\0" for 2^32 - 1 */
539 
540 	xe_gt_assert(gt, IS_SRIOV_PF(xe));
541 	xe_gt_assert(gt, root->d_inode->i_private == gt);
542 
543 	/*
544 	 *      /sys/kernel/debug/dri/0/
545 	 *      ├── gt0
546 	 *      │   ├── pf
547 	 */
548 	pfdentry = debugfs_create_dir("pf", root);
549 	if (IS_ERR(pfdentry))
550 		return;
551 	pfdentry->d_inode->i_private = gt;
552 
553 	drm_debugfs_create_files(pf_info, ARRAY_SIZE(pf_info), pfdentry, minor);
554 	if (xe_gt_is_main_type(gt)) {
555 		drm_debugfs_create_files(pf_ggtt_info,
556 					 ARRAY_SIZE(pf_ggtt_info),
557 					 pfdentry, minor);
558 		if (xe_device_has_lmtt(gt_to_xe(gt)))
559 			drm_debugfs_create_files(pf_lmem_info,
560 						 ARRAY_SIZE(pf_lmem_info),
561 						 pfdentry, minor);
562 	}
563 
564 	pf_add_policy_attrs(gt, pfdentry);
565 	pf_add_config_attrs(gt, pfdentry, PFID);
566 
567 	for (n = 1; n <= totalvfs; n++) {
568 		/*
569 		 *      /sys/kernel/debug/dri/0/
570 		 *      ├── gt0
571 		 *      │   ├── vf1
572 		 *      │   ├── vf2
573 		 */
574 		snprintf(buf, sizeof(buf), "vf%u", n);
575 		vfdentry = debugfs_create_dir(buf, root);
576 		if (IS_ERR(vfdentry))
577 			break;
578 		vfdentry->d_inode->i_private = (void *)(uintptr_t)n;
579 
580 		pf_add_config_attrs(gt, vfdentry, VFID(n));
581 		debugfs_create_file("control", 0600, vfdentry, NULL, &control_ops);
582 
583 		/* for testing/debugging purposes only! */
584 		if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
585 			debugfs_create_file("guc_state",
586 					    IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV) ? 0600 : 0400,
587 					    vfdentry, NULL, &guc_state_ops);
588 			debugfs_create_file("config_blob",
589 					    IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV) ? 0600 : 0400,
590 					    vfdentry, NULL, &config_blob_ops);
591 		}
592 	}
593 }
594