1 /*
2  * PCI HotPlug Controller Core
3  *
4  * Copyright (C) 2001-2002 Greg Kroah-Hartman (greg@kroah.com)
5  * Copyright (C) 2001-2002 IBM Corp.
6  *
7  * All rights reserved.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or (at
12  * your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
17  * NON INFRINGEMENT.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  * Send feedback to <kristen.c.accardi@intel.com>
25  *
26  */
27 
28 #include <linux/module.h>
29 #include <linux/moduleparam.h>
30 #include <linux/kernel.h>
31 #include <linux/types.h>
32 #include <linux/list.h>
33 #include <linux/kobject.h>
34 #include <linux/sysfs.h>
35 #include <linux/pagemap.h>
36 #include <linux/slab.h>
37 #include <linux/init.h>
38 #include <linux/mount.h>
39 #include <linux/namei.h>
40 #include <linux/mutex.h>
41 #include <linux/pci.h>
42 #include <linux/pci_hotplug.h>
43 #include <asm/uaccess.h>
44 #include "../pci.h"
45 
46 #define MY_NAME	"pci_hotplug"
47 
48 #define dbg(fmt, arg...) do { if (debug) printk(KERN_DEBUG "%s: %s: " fmt , MY_NAME , __func__ , ## arg); } while (0)
49 #define err(format, arg...) printk(KERN_ERR "%s: " format , MY_NAME , ## arg)
50 #define info(format, arg...) printk(KERN_INFO "%s: " format , MY_NAME , ## arg)
51 #define warn(format, arg...) printk(KERN_WARNING "%s: " format , MY_NAME , ## arg)
52 
53 
54 /* local variables */
55 static int debug;
56 
57 #define DRIVER_VERSION	"0.5"
58 #define DRIVER_AUTHOR	"Greg Kroah-Hartman <greg@kroah.com>, Scott Murray <scottm@somanetworks.com>"
59 #define DRIVER_DESC	"PCI Hot Plug PCI Core"
60 
61 
62 //////////////////////////////////////////////////////////////////
63 
64 static LIST_HEAD(pci_hotplug_slot_list);
65 static DEFINE_MUTEX(pci_hp_mutex);
66 
67 /* these strings match up with the values in pci_bus_speed */
68 static char *pci_bus_speed_strings[] = {
69 	"33 MHz PCI",		/* 0x00 */
70 	"66 MHz PCI",		/* 0x01 */
71 	"66 MHz PCIX", 		/* 0x02 */
72 	"100 MHz PCIX",		/* 0x03 */
73 	"133 MHz PCIX",		/* 0x04 */
74 	NULL,			/* 0x05 */
75 	NULL,			/* 0x06 */
76 	NULL,			/* 0x07 */
77 	NULL,			/* 0x08 */
78 	"66 MHz PCIX 266",	/* 0x09 */
79 	"100 MHz PCIX 266",	/* 0x0a */
80 	"133 MHz PCIX 266",	/* 0x0b */
81 	NULL,			/* 0x0c */
82 	NULL,			/* 0x0d */
83 	NULL,			/* 0x0e */
84 	NULL,			/* 0x0f */
85 	NULL,			/* 0x10 */
86 	"66 MHz PCIX 533",	/* 0x11 */
87 	"100 MHz PCIX 533",	/* 0x12 */
88 	"133 MHz PCIX 533",	/* 0x13 */
89 	"2.5 GT/s PCI-E",	/* 0x14 */
90 	"5.0 GT/s PCI-E",	/* 0x15 */
91 };
92 
93 #ifdef CONFIG_HOTPLUG_PCI_CPCI
94 extern int cpci_hotplug_init(int debug);
95 extern void cpci_hotplug_exit(void);
96 #else
97 static inline int cpci_hotplug_init(int debug) { return 0; }
98 static inline void cpci_hotplug_exit(void) { }
99 #endif
100 
101 /* Weee, fun with macros... */
102 #define GET_STATUS(name,type)	\
103 static int get_##name (struct hotplug_slot *slot, type *value)		\
104 {									\
105 	struct hotplug_slot_ops *ops = slot->ops;			\
106 	int retval = 0;							\
107 	if (!try_module_get(ops->owner))				\
108 		return -ENODEV;						\
109 	if (ops->get_##name)						\
110 		retval = ops->get_##name(slot, value);			\
111 	else								\
112 		*value = slot->info->name;				\
113 	module_put(ops->owner);						\
114 	return retval;							\
115 }
116 
117 GET_STATUS(power_status, u8)
118 GET_STATUS(attention_status, u8)
119 GET_STATUS(latch_status, u8)
120 GET_STATUS(adapter_status, u8)
121 GET_STATUS(max_bus_speed, enum pci_bus_speed)
122 GET_STATUS(cur_bus_speed, enum pci_bus_speed)
123 
124 static ssize_t power_read_file(struct pci_slot *slot, char *buf)
125 {
126 	int retval;
127 	u8 value;
128 
129 	retval = get_power_status(slot->hotplug, &value);
130 	if (retval)
131 		goto exit;
132 	retval = sprintf (buf, "%d\n", value);
133 exit:
134 	return retval;
135 }
136 
137 static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf,
138 		size_t count)
139 {
140 	struct hotplug_slot *slot = pci_slot->hotplug;
141 	unsigned long lpower;
142 	u8 power;
143 	int retval = 0;
144 
145 	lpower = simple_strtoul (buf, NULL, 10);
146 	power = (u8)(lpower & 0xff);
147 	dbg ("power = %d\n", power);
148 
149 	if (!try_module_get(slot->ops->owner)) {
150 		retval = -ENODEV;
151 		goto exit;
152 	}
153 	switch (power) {
154 		case 0:
155 			if (slot->ops->disable_slot)
156 				retval = slot->ops->disable_slot(slot);
157 			break;
158 
159 		case 1:
160 			if (slot->ops->enable_slot)
161 				retval = slot->ops->enable_slot(slot);
162 			break;
163 
164 		default:
165 			err ("Illegal value specified for power\n");
166 			retval = -EINVAL;
167 	}
168 	module_put(slot->ops->owner);
169 
170 exit:
171 	if (retval)
172 		return retval;
173 	return count;
174 }
175 
176 static struct pci_slot_attribute hotplug_slot_attr_power = {
177 	.attr = {.name = "power", .mode = S_IFREG | S_IRUGO | S_IWUSR},
178 	.show = power_read_file,
179 	.store = power_write_file
180 };
181 
182 static ssize_t attention_read_file(struct pci_slot *slot, char *buf)
183 {
184 	int retval;
185 	u8 value;
186 
187 	retval = get_attention_status(slot->hotplug, &value);
188 	if (retval)
189 		goto exit;
190 	retval = sprintf(buf, "%d\n", value);
191 
192 exit:
193 	return retval;
194 }
195 
196 static ssize_t attention_write_file(struct pci_slot *slot, const char *buf,
197 		size_t count)
198 {
199 	struct hotplug_slot_ops *ops = slot->hotplug->ops;
200 	unsigned long lattention;
201 	u8 attention;
202 	int retval = 0;
203 
204 	lattention = simple_strtoul (buf, NULL, 10);
205 	attention = (u8)(lattention & 0xff);
206 	dbg (" - attention = %d\n", attention);
207 
208 	if (!try_module_get(ops->owner)) {
209 		retval = -ENODEV;
210 		goto exit;
211 	}
212 	if (ops->set_attention_status)
213 		retval = ops->set_attention_status(slot->hotplug, attention);
214 	module_put(ops->owner);
215 
216 exit:
217 	if (retval)
218 		return retval;
219 	return count;
220 }
221 
222 static struct pci_slot_attribute hotplug_slot_attr_attention = {
223 	.attr = {.name = "attention", .mode = S_IFREG | S_IRUGO | S_IWUSR},
224 	.show = attention_read_file,
225 	.store = attention_write_file
226 };
227 
228 static ssize_t latch_read_file(struct pci_slot *slot, char *buf)
229 {
230 	int retval;
231 	u8 value;
232 
233 	retval = get_latch_status(slot->hotplug, &value);
234 	if (retval)
235 		goto exit;
236 	retval = sprintf (buf, "%d\n", value);
237 
238 exit:
239 	return retval;
240 }
241 
242 static struct pci_slot_attribute hotplug_slot_attr_latch = {
243 	.attr = {.name = "latch", .mode = S_IFREG | S_IRUGO},
244 	.show = latch_read_file,
245 };
246 
247 static ssize_t presence_read_file(struct pci_slot *slot, char *buf)
248 {
249 	int retval;
250 	u8 value;
251 
252 	retval = get_adapter_status(slot->hotplug, &value);
253 	if (retval)
254 		goto exit;
255 	retval = sprintf (buf, "%d\n", value);
256 
257 exit:
258 	return retval;
259 }
260 
261 static struct pci_slot_attribute hotplug_slot_attr_presence = {
262 	.attr = {.name = "adapter", .mode = S_IFREG | S_IRUGO},
263 	.show = presence_read_file,
264 };
265 
266 static char *unknown_speed = "Unknown bus speed";
267 
268 static ssize_t max_bus_speed_read_file(struct pci_slot *slot, char *buf)
269 {
270 	char *speed_string;
271 	int retval;
272 	enum pci_bus_speed value;
273 
274 	retval = get_max_bus_speed(slot->hotplug, &value);
275 	if (retval)
276 		goto exit;
277 
278 	if (value == PCI_SPEED_UNKNOWN)
279 		speed_string = unknown_speed;
280 	else
281 		speed_string = pci_bus_speed_strings[value];
282 
283 	retval = sprintf (buf, "%s\n", speed_string);
284 
285 exit:
286 	return retval;
287 }
288 
289 static struct pci_slot_attribute hotplug_slot_attr_max_bus_speed = {
290 	.attr = {.name = "max_bus_speed", .mode = S_IFREG | S_IRUGO},
291 	.show = max_bus_speed_read_file,
292 };
293 
294 static ssize_t cur_bus_speed_read_file(struct pci_slot *slot, char *buf)
295 {
296 	char *speed_string;
297 	int retval;
298 	enum pci_bus_speed value;
299 
300 	retval = get_cur_bus_speed(slot->hotplug, &value);
301 	if (retval)
302 		goto exit;
303 
304 	if (value == PCI_SPEED_UNKNOWN)
305 		speed_string = unknown_speed;
306 	else
307 		speed_string = pci_bus_speed_strings[value];
308 
309 	retval = sprintf (buf, "%s\n", speed_string);
310 
311 exit:
312 	return retval;
313 }
314 
315 static struct pci_slot_attribute hotplug_slot_attr_cur_bus_speed = {
316 	.attr = {.name = "cur_bus_speed", .mode = S_IFREG | S_IRUGO},
317 	.show = cur_bus_speed_read_file,
318 };
319 
320 static ssize_t test_write_file(struct pci_slot *pci_slot, const char *buf,
321 		size_t count)
322 {
323 	struct hotplug_slot *slot = pci_slot->hotplug;
324 	unsigned long ltest;
325 	u32 test;
326 	int retval = 0;
327 
328 	ltest = simple_strtoul (buf, NULL, 10);
329 	test = (u32)(ltest & 0xffffffff);
330 	dbg ("test = %d\n", test);
331 
332 	if (!try_module_get(slot->ops->owner)) {
333 		retval = -ENODEV;
334 		goto exit;
335 	}
336 	if (slot->ops->hardware_test)
337 		retval = slot->ops->hardware_test(slot, test);
338 	module_put(slot->ops->owner);
339 
340 exit:
341 	if (retval)
342 		return retval;
343 	return count;
344 }
345 
346 static struct pci_slot_attribute hotplug_slot_attr_test = {
347 	.attr = {.name = "test", .mode = S_IFREG | S_IRUGO | S_IWUSR},
348 	.store = test_write_file
349 };
350 
351 static bool has_power_file(struct pci_slot *pci_slot)
352 {
353 	struct hotplug_slot *slot = pci_slot->hotplug;
354 	if ((!slot) || (!slot->ops))
355 		return false;
356 	if ((slot->ops->enable_slot) ||
357 	    (slot->ops->disable_slot) ||
358 	    (slot->ops->get_power_status))
359 		return true;
360 	return false;
361 }
362 
363 static bool has_attention_file(struct pci_slot *pci_slot)
364 {
365 	struct hotplug_slot *slot = pci_slot->hotplug;
366 	if ((!slot) || (!slot->ops))
367 		return false;
368 	if ((slot->ops->set_attention_status) ||
369 	    (slot->ops->get_attention_status))
370 		return true;
371 	return false;
372 }
373 
374 static bool has_latch_file(struct pci_slot *pci_slot)
375 {
376 	struct hotplug_slot *slot = pci_slot->hotplug;
377 	if ((!slot) || (!slot->ops))
378 		return false;
379 	if (slot->ops->get_latch_status)
380 		return true;
381 	return false;
382 }
383 
384 static bool has_adapter_file(struct pci_slot *pci_slot)
385 {
386 	struct hotplug_slot *slot = pci_slot->hotplug;
387 	if ((!slot) || (!slot->ops))
388 		return false;
389 	if (slot->ops->get_adapter_status)
390 		return true;
391 	return false;
392 }
393 
394 static bool has_max_bus_speed_file(struct pci_slot *pci_slot)
395 {
396 	struct hotplug_slot *slot = pci_slot->hotplug;
397 	if ((!slot) || (!slot->ops))
398 		return false;
399 	if (slot->ops->get_max_bus_speed)
400 		return true;
401 	return false;
402 }
403 
404 static bool has_cur_bus_speed_file(struct pci_slot *pci_slot)
405 {
406 	struct hotplug_slot *slot = pci_slot->hotplug;
407 	if ((!slot) || (!slot->ops))
408 		return false;
409 	if (slot->ops->get_cur_bus_speed)
410 		return true;
411 	return false;
412 }
413 
414 static bool has_test_file(struct pci_slot *pci_slot)
415 {
416 	struct hotplug_slot *slot = pci_slot->hotplug;
417 	if ((!slot) || (!slot->ops))
418 		return false;
419 	if (slot->ops->hardware_test)
420 		return true;
421 	return false;
422 }
423 
424 static int fs_add_slot(struct pci_slot *slot)
425 {
426 	int retval = 0;
427 
428 	/* Create symbolic link to the hotplug driver module */
429 	pci_hp_create_module_link(slot);
430 
431 	if (has_power_file(slot)) {
432 		retval = sysfs_create_file(&slot->kobj,
433 					   &hotplug_slot_attr_power.attr);
434 		if (retval)
435 			goto exit_power;
436 	}
437 
438 	if (has_attention_file(slot)) {
439 		retval = sysfs_create_file(&slot->kobj,
440 					   &hotplug_slot_attr_attention.attr);
441 		if (retval)
442 			goto exit_attention;
443 	}
444 
445 	if (has_latch_file(slot)) {
446 		retval = sysfs_create_file(&slot->kobj,
447 					   &hotplug_slot_attr_latch.attr);
448 		if (retval)
449 			goto exit_latch;
450 	}
451 
452 	if (has_adapter_file(slot)) {
453 		retval = sysfs_create_file(&slot->kobj,
454 					   &hotplug_slot_attr_presence.attr);
455 		if (retval)
456 			goto exit_adapter;
457 	}
458 
459 	if (has_max_bus_speed_file(slot)) {
460 		retval = sysfs_create_file(&slot->kobj,
461 					&hotplug_slot_attr_max_bus_speed.attr);
462 		if (retval)
463 			goto exit_max_speed;
464 	}
465 
466 	if (has_cur_bus_speed_file(slot)) {
467 		retval = sysfs_create_file(&slot->kobj,
468 					&hotplug_slot_attr_cur_bus_speed.attr);
469 		if (retval)
470 			goto exit_cur_speed;
471 	}
472 
473 	if (has_test_file(slot)) {
474 		retval = sysfs_create_file(&slot->kobj,
475 					   &hotplug_slot_attr_test.attr);
476 		if (retval)
477 			goto exit_test;
478 	}
479 
480 	goto exit;
481 
482 exit_test:
483 	if (has_cur_bus_speed_file(slot))
484 		sysfs_remove_file(&slot->kobj,
485 				  &hotplug_slot_attr_cur_bus_speed.attr);
486 exit_cur_speed:
487 	if (has_max_bus_speed_file(slot))
488 		sysfs_remove_file(&slot->kobj,
489 				  &hotplug_slot_attr_max_bus_speed.attr);
490 exit_max_speed:
491 	if (has_adapter_file(slot))
492 		sysfs_remove_file(&slot->kobj,
493 				  &hotplug_slot_attr_presence.attr);
494 exit_adapter:
495 	if (has_latch_file(slot))
496 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
497 exit_latch:
498 	if (has_attention_file(slot))
499 		sysfs_remove_file(&slot->kobj,
500 				  &hotplug_slot_attr_attention.attr);
501 exit_attention:
502 	if (has_power_file(slot))
503 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr);
504 exit_power:
505 	pci_hp_remove_module_link(slot);
506 exit:
507 	return retval;
508 }
509 
510 static void fs_remove_slot(struct pci_slot *slot)
511 {
512 	if (has_power_file(slot))
513 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_power.attr);
514 
515 	if (has_attention_file(slot))
516 		sysfs_remove_file(&slot->kobj,
517 				  &hotplug_slot_attr_attention.attr);
518 
519 	if (has_latch_file(slot))
520 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_latch.attr);
521 
522 	if (has_adapter_file(slot))
523 		sysfs_remove_file(&slot->kobj,
524 				  &hotplug_slot_attr_presence.attr);
525 
526 	if (has_max_bus_speed_file(slot))
527 		sysfs_remove_file(&slot->kobj,
528 				  &hotplug_slot_attr_max_bus_speed.attr);
529 
530 	if (has_cur_bus_speed_file(slot))
531 		sysfs_remove_file(&slot->kobj,
532 				  &hotplug_slot_attr_cur_bus_speed.attr);
533 
534 	if (has_test_file(slot))
535 		sysfs_remove_file(&slot->kobj, &hotplug_slot_attr_test.attr);
536 
537 	pci_hp_remove_module_link(slot);
538 }
539 
540 static struct hotplug_slot *get_slot_from_name (const char *name)
541 {
542 	struct hotplug_slot *slot;
543 	struct list_head *tmp;
544 
545 	list_for_each (tmp, &pci_hotplug_slot_list) {
546 		slot = list_entry (tmp, struct hotplug_slot, slot_list);
547 		if (strcmp(hotplug_slot_name(slot), name) == 0)
548 			return slot;
549 	}
550 	return NULL;
551 }
552 
553 /**
554  * __pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem
555  * @bus: bus this slot is on
556  * @slot: pointer to the &struct hotplug_slot to register
557  * @devnr: device number
558  * @name: name registered with kobject core
559  * @owner: caller module owner
560  * @mod_name: caller module name
561  *
562  * Registers a hotplug slot with the pci hotplug subsystem, which will allow
563  * userspace interaction to the slot.
564  *
565  * Returns 0 if successful, anything else for an error.
566  */
567 int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus,
568 		      int devnr, const char *name,
569 		      struct module *owner, const char *mod_name)
570 {
571 	int result;
572 	struct pci_slot *pci_slot;
573 
574 	if (slot == NULL)
575 		return -ENODEV;
576 	if ((slot->info == NULL) || (slot->ops == NULL))
577 		return -EINVAL;
578 	if (slot->release == NULL) {
579 		dbg("Why are you trying to register a hotplug slot "
580 		    "without a proper release function?\n");
581 		return -EINVAL;
582 	}
583 
584 	slot->ops->owner = owner;
585 	slot->ops->mod_name = mod_name;
586 
587 	mutex_lock(&pci_hp_mutex);
588 	/*
589 	 * No problems if we call this interface from both ACPI_PCI_SLOT
590 	 * driver and call it here again. If we've already created the
591 	 * pci_slot, the interface will simply bump the refcount.
592 	 */
593 	pci_slot = pci_create_slot(bus, devnr, name, slot);
594 	if (IS_ERR(pci_slot)) {
595 		result = PTR_ERR(pci_slot);
596 		goto out;
597 	}
598 
599 	slot->pci_slot = pci_slot;
600 	pci_slot->hotplug = slot;
601 
602 	list_add(&slot->slot_list, &pci_hotplug_slot_list);
603 
604 	result = fs_add_slot(pci_slot);
605 	kobject_uevent(&pci_slot->kobj, KOBJ_ADD);
606 	dbg("Added slot %s to the list\n", name);
607 out:
608 	mutex_unlock(&pci_hp_mutex);
609 	return result;
610 }
611 
612 /**
613  * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem
614  * @hotplug: pointer to the &struct hotplug_slot to deregister
615  *
616  * The @slot must have been registered with the pci hotplug subsystem
617  * previously with a call to pci_hp_register().
618  *
619  * Returns 0 if successful, anything else for an error.
620  */
621 int pci_hp_deregister(struct hotplug_slot *hotplug)
622 {
623 	struct hotplug_slot *temp;
624 	struct pci_slot *slot;
625 
626 	if (!hotplug)
627 		return -ENODEV;
628 
629 	mutex_lock(&pci_hp_mutex);
630 	temp = get_slot_from_name(hotplug_slot_name(hotplug));
631 	if (temp != hotplug) {
632 		mutex_unlock(&pci_hp_mutex);
633 		return -ENODEV;
634 	}
635 
636 	list_del(&hotplug->slot_list);
637 
638 	slot = hotplug->pci_slot;
639 	fs_remove_slot(slot);
640 	dbg("Removed slot %s from the list\n", hotplug_slot_name(hotplug));
641 
642 	hotplug->release(hotplug);
643 	slot->hotplug = NULL;
644 	pci_destroy_slot(slot);
645 	mutex_unlock(&pci_hp_mutex);
646 
647 	return 0;
648 }
649 
650 /**
651  * pci_hp_change_slot_info - changes the slot's information structure in the core
652  * @hotplug: pointer to the slot whose info has changed
653  * @info: pointer to the info copy into the slot's info structure
654  *
655  * @slot must have been registered with the pci
656  * hotplug subsystem previously with a call to pci_hp_register().
657  *
658  * Returns 0 if successful, anything else for an error.
659  */
660 int __must_check pci_hp_change_slot_info(struct hotplug_slot *hotplug,
661 					 struct hotplug_slot_info *info)
662 {
663 	struct pci_slot *slot;
664 	if (!hotplug || !info)
665 		return -ENODEV;
666 	slot = hotplug->pci_slot;
667 
668 	memcpy(hotplug->info, info, sizeof(struct hotplug_slot_info));
669 
670 	return 0;
671 }
672 
673 static int __init pci_hotplug_init (void)
674 {
675 	int result;
676 
677 	result = cpci_hotplug_init(debug);
678 	if (result) {
679 		err ("cpci_hotplug_init with error %d\n", result);
680 		goto err_cpci;
681 	}
682 
683 	info (DRIVER_DESC " version: " DRIVER_VERSION "\n");
684 
685 err_cpci:
686 	return result;
687 }
688 
689 static void __exit pci_hotplug_exit (void)
690 {
691 	cpci_hotplug_exit();
692 }
693 
694 module_init(pci_hotplug_init);
695 module_exit(pci_hotplug_exit);
696 
697 MODULE_AUTHOR(DRIVER_AUTHOR);
698 MODULE_DESCRIPTION(DRIVER_DESC);
699 MODULE_LICENSE("GPL");
700 module_param(debug, bool, 0644);
701 MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
702 
703 EXPORT_SYMBOL_GPL(__pci_hp_register);
704 EXPORT_SYMBOL_GPL(pci_hp_deregister);
705 EXPORT_SYMBOL_GPL(pci_hp_change_slot_info);
706