1 /* 2 Option Card (PCMCIA to) USB to Serial Driver 3 4 Copyright (C) 2005 Matthias Urlichs <smurf@smurf.noris.de> 5 6 This driver is free software; you can redistribute it and/or modify 7 it under the terms of Version 2 of the GNU General Public License as 8 published by the Free Software Foundation. 9 10 Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org> 11 12 History: 13 14 2005-05-19 v0.1 Initial version, based on incomplete docs 15 and analysis of misbehavior with the standard driver 16 2005-05-20 v0.2 Extended the input buffer to avoid losing 17 random 64-byte chunks of data 18 2005-05-21 v0.3 implemented chars_in_buffer() 19 turned on low_latency 20 simplified the code somewhat 21 2005-05-24 v0.4 option_write() sometimes deadlocked under heavy load 22 removed some dead code 23 added sponsor notice 24 coding style clean-up 25 2005-06-20 v0.4.1 add missing braces :-/ 26 killed end-of-line whitespace 27 2005-07-15 v0.4.2 rename WLAN product to FUSION, add FUSION2 28 2005-09-10 v0.4.3 added HUAWEI E600 card and Audiovox AirCard 29 2005-09-20 v0.4.4 increased recv buffer size: the card sometimes 30 wants to send >2000 bytes. 31 2006-04-10 v0.4.2 fixed two array overrun errors :-/ 32 33 Work sponsored by: Sigos GmbH, Germany <info@sigos.de> 34 35 */ 36 37 #define DRIVER_VERSION "v0.4" 38 #define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>" 39 #define DRIVER_DESC "Option Card (PC-Card to) USB to Serial Driver" 40 41 #include <linux/config.h> 42 #include <linux/kernel.h> 43 #include <linux/jiffies.h> 44 #include <linux/errno.h> 45 #include <linux/tty.h> 46 #include <linux/tty_flip.h> 47 #include <linux/module.h> 48 #include <linux/usb.h> 49 #include "usb-serial.h" 50 51 /* Function prototypes */ 52 static int option_open(struct usb_serial_port *port, struct file *filp); 53 static void option_close(struct usb_serial_port *port, struct file *filp); 54 static int option_startup(struct usb_serial *serial); 55 static void option_shutdown(struct usb_serial *serial); 56 static void option_rx_throttle(struct usb_serial_port *port); 57 static void option_rx_unthrottle(struct usb_serial_port *port); 58 static int option_write_room(struct usb_serial_port *port); 59 60 static void option_instat_callback(struct urb *urb, struct pt_regs *regs); 61 62 static int option_write(struct usb_serial_port *port, 63 const unsigned char *buf, int count); 64 65 static int option_chars_in_buffer(struct usb_serial_port *port); 66 static int option_ioctl(struct usb_serial_port *port, struct file *file, 67 unsigned int cmd, unsigned long arg); 68 static void option_set_termios(struct usb_serial_port *port, 69 struct termios *old); 70 static void option_break_ctl(struct usb_serial_port *port, int break_state); 71 static int option_tiocmget(struct usb_serial_port *port, struct file *file); 72 static int option_tiocmset(struct usb_serial_port *port, struct file *file, 73 unsigned int set, unsigned int clear); 74 static int option_send_setup(struct usb_serial_port *port); 75 76 /* Vendor and product IDs */ 77 #define OPTION_VENDOR_ID 0x0AF0 78 #define HUAWEI_VENDOR_ID 0x12D1 79 #define AUDIOVOX_VENDOR_ID 0x0F3D 80 81 #define OPTION_PRODUCT_OLD 0x5000 82 #define OPTION_PRODUCT_FUSION 0x6000 83 #define OPTION_PRODUCT_FUSION2 0x6300 84 #define HUAWEI_PRODUCT_E600 0x1001 85 #define AUDIOVOX_PRODUCT_AIRCARD 0x0112 86 87 static struct usb_device_id option_ids[] = { 88 { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_OLD) }, 89 { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION) }, 90 { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUSION2) }, 91 { USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600) }, 92 { USB_DEVICE(AUDIOVOX_VENDOR_ID, AUDIOVOX_PRODUCT_AIRCARD) }, 93 { } /* Terminating entry */ 94 }; 95 96 MODULE_DEVICE_TABLE(usb, option_ids); 97 98 static struct usb_driver option_driver = { 99 .name = "option", 100 .probe = usb_serial_probe, 101 .disconnect = usb_serial_disconnect, 102 .id_table = option_ids, 103 .no_dynamic_id = 1, 104 }; 105 106 /* The card has three separate interfaces, which the serial driver 107 * recognizes separately, thus num_port=1. 108 */ 109 static struct usb_serial_driver option_3port_device = { 110 .driver = { 111 .owner = THIS_MODULE, 112 .name = "option", 113 }, 114 .description = "Option 3G data card", 115 .id_table = option_ids, 116 .num_interrupt_in = NUM_DONT_CARE, 117 .num_bulk_in = NUM_DONT_CARE, 118 .num_bulk_out = NUM_DONT_CARE, 119 .num_ports = 1, /* 3, but the card reports its ports separately */ 120 .open = option_open, 121 .close = option_close, 122 .write = option_write, 123 .write_room = option_write_room, 124 .chars_in_buffer = option_chars_in_buffer, 125 .throttle = option_rx_throttle, 126 .unthrottle = option_rx_unthrottle, 127 .ioctl = option_ioctl, 128 .set_termios = option_set_termios, 129 .break_ctl = option_break_ctl, 130 .tiocmget = option_tiocmget, 131 .tiocmset = option_tiocmset, 132 .attach = option_startup, 133 .shutdown = option_shutdown, 134 .read_int_callback = option_instat_callback, 135 }; 136 137 #ifdef CONFIG_USB_DEBUG 138 static int debug; 139 #else 140 #define debug 0 141 #endif 142 143 /* per port private data */ 144 145 #define N_IN_URB 4 146 #define N_OUT_URB 1 147 #define IN_BUFLEN 4096 148 #define OUT_BUFLEN 128 149 150 struct option_port_private { 151 /* Input endpoints and buffer for this port */ 152 struct urb *in_urbs[N_IN_URB]; 153 char in_buffer[N_IN_URB][IN_BUFLEN]; 154 /* Output endpoints and buffer for this port */ 155 struct urb *out_urbs[N_OUT_URB]; 156 char out_buffer[N_OUT_URB][OUT_BUFLEN]; 157 158 /* Settings for the port */ 159 int rts_state; /* Handshaking pins (outputs) */ 160 int dtr_state; 161 int cts_state; /* Handshaking pins (inputs) */ 162 int dsr_state; 163 int dcd_state; 164 int ri_state; 165 166 unsigned long tx_start_time[N_OUT_URB]; 167 }; 168 169 /* Functions used by new usb-serial code. */ 170 static int __init option_init(void) 171 { 172 int retval; 173 retval = usb_serial_register(&option_3port_device); 174 if (retval) 175 goto failed_3port_device_register; 176 retval = usb_register(&option_driver); 177 if (retval) 178 goto failed_driver_register; 179 180 info(DRIVER_DESC ": " DRIVER_VERSION); 181 182 return 0; 183 184 failed_driver_register: 185 usb_serial_deregister (&option_3port_device); 186 failed_3port_device_register: 187 return retval; 188 } 189 190 static void __exit option_exit(void) 191 { 192 usb_deregister (&option_driver); 193 usb_serial_deregister (&option_3port_device); 194 } 195 196 module_init(option_init); 197 module_exit(option_exit); 198 199 static void option_rx_throttle(struct usb_serial_port *port) 200 { 201 dbg("%s", __FUNCTION__); 202 } 203 204 static void option_rx_unthrottle(struct usb_serial_port *port) 205 { 206 dbg("%s", __FUNCTION__); 207 } 208 209 static void option_break_ctl(struct usb_serial_port *port, int break_state) 210 { 211 /* Unfortunately, I don't know how to send a break */ 212 dbg("%s", __FUNCTION__); 213 } 214 215 static void option_set_termios(struct usb_serial_port *port, 216 struct termios *old_termios) 217 { 218 dbg("%s", __FUNCTION__); 219 220 option_send_setup(port); 221 } 222 223 static int option_tiocmget(struct usb_serial_port *port, struct file *file) 224 { 225 unsigned int value; 226 struct option_port_private *portdata; 227 228 portdata = usb_get_serial_port_data(port); 229 230 value = ((portdata->rts_state) ? TIOCM_RTS : 0) | 231 ((portdata->dtr_state) ? TIOCM_DTR : 0) | 232 ((portdata->cts_state) ? TIOCM_CTS : 0) | 233 ((portdata->dsr_state) ? TIOCM_DSR : 0) | 234 ((portdata->dcd_state) ? TIOCM_CAR : 0) | 235 ((portdata->ri_state) ? TIOCM_RNG : 0); 236 237 return value; 238 } 239 240 static int option_tiocmset(struct usb_serial_port *port, struct file *file, 241 unsigned int set, unsigned int clear) 242 { 243 struct option_port_private *portdata; 244 245 portdata = usb_get_serial_port_data(port); 246 247 if (set & TIOCM_RTS) 248 portdata->rts_state = 1; 249 if (set & TIOCM_DTR) 250 portdata->dtr_state = 1; 251 252 if (clear & TIOCM_RTS) 253 portdata->rts_state = 0; 254 if (clear & TIOCM_DTR) 255 portdata->dtr_state = 0; 256 return option_send_setup(port); 257 } 258 259 static int option_ioctl(struct usb_serial_port *port, struct file *file, 260 unsigned int cmd, unsigned long arg) 261 { 262 return -ENOIOCTLCMD; 263 } 264 265 /* Write */ 266 static int option_write(struct usb_serial_port *port, 267 const unsigned char *buf, int count) 268 { 269 struct option_port_private *portdata; 270 int i; 271 int left, todo; 272 struct urb *this_urb = NULL; /* spurious */ 273 int err; 274 275 portdata = usb_get_serial_port_data(port); 276 277 dbg("%s: write (%d chars)", __FUNCTION__, count); 278 279 i = 0; 280 left = count; 281 for (i=0; left > 0 && i < N_OUT_URB; i++) { 282 todo = left; 283 if (todo > OUT_BUFLEN) 284 todo = OUT_BUFLEN; 285 286 this_urb = portdata->out_urbs[i]; 287 if (this_urb->status == -EINPROGRESS) { 288 if (time_before(jiffies, 289 portdata->tx_start_time[i] + 10 * HZ)) 290 continue; 291 usb_unlink_urb(this_urb); 292 continue; 293 } 294 if (this_urb->status != 0) 295 dbg("usb_write %p failed (err=%d)", 296 this_urb, this_urb->status); 297 298 dbg("%s: endpoint %d buf %d", __FUNCTION__, 299 usb_pipeendpoint(this_urb->pipe), i); 300 301 /* send the data */ 302 memcpy (this_urb->transfer_buffer, buf, todo); 303 this_urb->transfer_buffer_length = todo; 304 305 this_urb->dev = port->serial->dev; 306 err = usb_submit_urb(this_urb, GFP_ATOMIC); 307 if (err) { 308 dbg("usb_submit_urb %p (write bulk) failed " 309 "(%d, has %d)", this_urb, 310 err, this_urb->status); 311 continue; 312 } 313 portdata->tx_start_time[i] = jiffies; 314 buf += todo; 315 left -= todo; 316 } 317 318 count -= left; 319 dbg("%s: wrote (did %d)", __FUNCTION__, count); 320 return count; 321 } 322 323 static void option_indat_callback(struct urb *urb, struct pt_regs *regs) 324 { 325 int err; 326 int endpoint; 327 struct usb_serial_port *port; 328 struct tty_struct *tty; 329 unsigned char *data = urb->transfer_buffer; 330 331 dbg("%s: %p", __FUNCTION__, urb); 332 333 endpoint = usb_pipeendpoint(urb->pipe); 334 port = (struct usb_serial_port *) urb->context; 335 336 if (urb->status) { 337 dbg("%s: nonzero status: %d on endpoint %02x.", 338 __FUNCTION__, urb->status, endpoint); 339 } else { 340 tty = port->tty; 341 if (urb->actual_length) { 342 tty_buffer_request_room(tty, urb->actual_length); 343 tty_insert_flip_string(tty, data, urb->actual_length); 344 tty_flip_buffer_push(tty); 345 } else { 346 dbg("%s: empty read urb received", __FUNCTION__); 347 } 348 349 /* Resubmit urb so we continue receiving */ 350 if (port->open_count && urb->status != -ESHUTDOWN) { 351 err = usb_submit_urb(urb, GFP_ATOMIC); 352 if (err) 353 printk(KERN_ERR "%s: resubmit read urb failed. " 354 "(%d)", __FUNCTION__, err); 355 } 356 } 357 return; 358 } 359 360 static void option_outdat_callback(struct urb *urb, struct pt_regs *regs) 361 { 362 struct usb_serial_port *port; 363 364 dbg("%s", __FUNCTION__); 365 366 port = (struct usb_serial_port *) urb->context; 367 368 if (port->open_count) 369 schedule_work(&port->work); 370 } 371 372 static void option_instat_callback(struct urb *urb, struct pt_regs *regs) 373 { 374 int err; 375 struct usb_serial_port *port = (struct usb_serial_port *) urb->context; 376 struct option_port_private *portdata = usb_get_serial_port_data(port); 377 struct usb_serial *serial = port->serial; 378 379 dbg("%s", __FUNCTION__); 380 dbg("%s: urb %p port %p has data %p", __FUNCTION__,urb,port,portdata); 381 382 if (urb->status == 0) { 383 struct usb_ctrlrequest *req_pkt = 384 (struct usb_ctrlrequest *)urb->transfer_buffer; 385 386 if (!req_pkt) { 387 dbg("%s: NULL req_pkt\n", __FUNCTION__); 388 return; 389 } 390 if ((req_pkt->bRequestType == 0xA1) && 391 (req_pkt->bRequest == 0x20)) { 392 int old_dcd_state; 393 unsigned char signals = *((unsigned char *) 394 urb->transfer_buffer + 395 sizeof(struct usb_ctrlrequest)); 396 397 dbg("%s: signal x%x", __FUNCTION__, signals); 398 399 old_dcd_state = portdata->dcd_state; 400 portdata->cts_state = 1; 401 portdata->dcd_state = ((signals & 0x01) ? 1 : 0); 402 portdata->dsr_state = ((signals & 0x02) ? 1 : 0); 403 portdata->ri_state = ((signals & 0x08) ? 1 : 0); 404 405 if (port->tty && !C_CLOCAL(port->tty) && 406 old_dcd_state && !portdata->dcd_state) 407 tty_hangup(port->tty); 408 } else { 409 dbg("%s: type %x req %x", __FUNCTION__, 410 req_pkt->bRequestType,req_pkt->bRequest); 411 } 412 } else 413 dbg("%s: error %d", __FUNCTION__, urb->status); 414 415 /* Resubmit urb so we continue receiving IRQ data */ 416 if (urb->status != -ESHUTDOWN) { 417 urb->dev = serial->dev; 418 err = usb_submit_urb(urb, GFP_ATOMIC); 419 if (err) 420 dbg("%s: resubmit intr urb failed. (%d)", 421 __FUNCTION__, err); 422 } 423 } 424 425 static int option_write_room(struct usb_serial_port *port) 426 { 427 struct option_port_private *portdata; 428 int i; 429 int data_len = 0; 430 struct urb *this_urb; 431 432 portdata = usb_get_serial_port_data(port); 433 434 for (i=0; i < N_OUT_URB; i++) { 435 this_urb = portdata->out_urbs[i]; 436 if (this_urb && this_urb->status != -EINPROGRESS) 437 data_len += OUT_BUFLEN; 438 } 439 440 dbg("%s: %d", __FUNCTION__, data_len); 441 return data_len; 442 } 443 444 static int option_chars_in_buffer(struct usb_serial_port *port) 445 { 446 struct option_port_private *portdata; 447 int i; 448 int data_len = 0; 449 struct urb *this_urb; 450 451 portdata = usb_get_serial_port_data(port); 452 453 for (i=0; i < N_OUT_URB; i++) { 454 this_urb = portdata->out_urbs[i]; 455 if (this_urb && this_urb->status == -EINPROGRESS) 456 data_len += this_urb->transfer_buffer_length; 457 } 458 dbg("%s: %d", __FUNCTION__, data_len); 459 return data_len; 460 } 461 462 static int option_open(struct usb_serial_port *port, struct file *filp) 463 { 464 struct option_port_private *portdata; 465 struct usb_serial *serial = port->serial; 466 int i, err; 467 struct urb *urb; 468 469 portdata = usb_get_serial_port_data(port); 470 471 dbg("%s", __FUNCTION__); 472 473 /* Set some sane defaults */ 474 portdata->rts_state = 1; 475 portdata->dtr_state = 1; 476 477 /* Reset low level data toggle and start reading from endpoints */ 478 for (i = 0; i < N_IN_URB; i++) { 479 urb = portdata->in_urbs[i]; 480 if (! urb) 481 continue; 482 if (urb->dev != serial->dev) { 483 dbg("%s: dev %p != %p", __FUNCTION__, 484 urb->dev, serial->dev); 485 continue; 486 } 487 488 /* 489 * make sure endpoint data toggle is synchronized with the 490 * device 491 */ 492 usb_clear_halt(urb->dev, urb->pipe); 493 494 err = usb_submit_urb(urb, GFP_KERNEL); 495 if (err) { 496 dbg("%s: submit urb %d failed (%d) %d", 497 __FUNCTION__, i, err, 498 urb->transfer_buffer_length); 499 } 500 } 501 502 /* Reset low level data toggle on out endpoints */ 503 for (i = 0; i < N_OUT_URB; i++) { 504 urb = portdata->out_urbs[i]; 505 if (! urb) 506 continue; 507 urb->dev = serial->dev; 508 /* usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), 509 usb_pipeout(urb->pipe), 0); */ 510 } 511 512 port->tty->low_latency = 1; 513 514 option_send_setup(port); 515 516 return (0); 517 } 518 519 static inline void stop_urb(struct urb *urb) 520 { 521 if (urb && urb->status == -EINPROGRESS) 522 usb_kill_urb(urb); 523 } 524 525 static void option_close(struct usb_serial_port *port, struct file *filp) 526 { 527 int i; 528 struct usb_serial *serial = port->serial; 529 struct option_port_private *portdata; 530 531 dbg("%s", __FUNCTION__); 532 portdata = usb_get_serial_port_data(port); 533 534 portdata->rts_state = 0; 535 portdata->dtr_state = 0; 536 537 if (serial->dev) { 538 option_send_setup(port); 539 540 /* Stop reading/writing urbs */ 541 for (i = 0; i < N_IN_URB; i++) 542 stop_urb(portdata->in_urbs[i]); 543 for (i = 0; i < N_OUT_URB; i++) 544 stop_urb(portdata->out_urbs[i]); 545 } 546 port->tty = NULL; 547 } 548 549 /* Helper functions used by option_setup_urbs */ 550 static struct urb *option_setup_urb(struct usb_serial *serial, int endpoint, 551 int dir, void *ctx, char *buf, int len, 552 void (*callback)(struct urb *, struct pt_regs *regs)) 553 { 554 struct urb *urb; 555 556 if (endpoint == -1) 557 return NULL; /* endpoint not needed */ 558 559 urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */ 560 if (urb == NULL) { 561 dbg("%s: alloc for endpoint %d failed.", __FUNCTION__, endpoint); 562 return NULL; 563 } 564 565 /* Fill URB using supplied data. */ 566 usb_fill_bulk_urb(urb, serial->dev, 567 usb_sndbulkpipe(serial->dev, endpoint) | dir, 568 buf, len, callback, ctx); 569 570 return urb; 571 } 572 573 /* Setup urbs */ 574 static void option_setup_urbs(struct usb_serial *serial) 575 { 576 int j; 577 struct usb_serial_port *port; 578 struct option_port_private *portdata; 579 580 dbg("%s", __FUNCTION__); 581 582 port = serial->port[0]; 583 portdata = usb_get_serial_port_data(port); 584 585 /* Do indat endpoints first */ 586 for (j = 0; j < N_IN_URB; ++j) { 587 portdata->in_urbs[j] = option_setup_urb (serial, 588 port->bulk_in_endpointAddress, USB_DIR_IN, port, 589 portdata->in_buffer[j], IN_BUFLEN, option_indat_callback); 590 } 591 592 /* outdat endpoints */ 593 for (j = 0; j < N_OUT_URB; ++j) { 594 portdata->out_urbs[j] = option_setup_urb (serial, 595 port->bulk_out_endpointAddress, USB_DIR_OUT, port, 596 portdata->out_buffer[j], OUT_BUFLEN, option_outdat_callback); 597 } 598 } 599 600 static int option_send_setup(struct usb_serial_port *port) 601 { 602 struct usb_serial *serial = port->serial; 603 struct option_port_private *portdata; 604 605 dbg("%s", __FUNCTION__); 606 607 portdata = usb_get_serial_port_data(port); 608 609 if (port->tty) { 610 int val = 0; 611 if (portdata->dtr_state) 612 val |= 0x01; 613 if (portdata->rts_state) 614 val |= 0x02; 615 616 return usb_control_msg(serial->dev, 617 usb_rcvctrlpipe(serial->dev, 0), 618 0x22,0x21,val,0,NULL,0,USB_CTRL_SET_TIMEOUT); 619 } 620 621 return 0; 622 } 623 624 static int option_startup(struct usb_serial *serial) 625 { 626 int i, err; 627 struct usb_serial_port *port; 628 struct option_port_private *portdata; 629 630 dbg("%s", __FUNCTION__); 631 632 /* Now setup per port private data */ 633 for (i = 0; i < serial->num_ports; i++) { 634 port = serial->port[i]; 635 portdata = kzalloc(sizeof(*portdata), GFP_KERNEL); 636 if (!portdata) { 637 dbg("%s: kmalloc for option_port_private (%d) failed!.", 638 __FUNCTION__, i); 639 return (1); 640 } 641 642 usb_set_serial_port_data(port, portdata); 643 644 if (! port->interrupt_in_urb) 645 continue; 646 err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); 647 if (err) 648 dbg("%s: submit irq_in urb failed %d", 649 __FUNCTION__, err); 650 } 651 652 option_setup_urbs(serial); 653 654 return (0); 655 } 656 657 static void option_shutdown(struct usb_serial *serial) 658 { 659 int i, j; 660 struct usb_serial_port *port; 661 struct option_port_private *portdata; 662 663 dbg("%s", __FUNCTION__); 664 665 /* Stop reading/writing urbs */ 666 for (i = 0; i < serial->num_ports; ++i) { 667 port = serial->port[i]; 668 portdata = usb_get_serial_port_data(port); 669 for (j = 0; j < N_IN_URB; j++) 670 stop_urb(portdata->in_urbs[j]); 671 for (j = 0; j < N_OUT_URB; j++) 672 stop_urb(portdata->out_urbs[j]); 673 } 674 675 /* Now free them */ 676 for (i = 0; i < serial->num_ports; ++i) { 677 port = serial->port[i]; 678 portdata = usb_get_serial_port_data(port); 679 680 for (j = 0; j < N_IN_URB; j++) { 681 if (portdata->in_urbs[j]) { 682 usb_free_urb(portdata->in_urbs[j]); 683 portdata->in_urbs[j] = NULL; 684 } 685 } 686 for (j = 0; j < N_OUT_URB; j++) { 687 if (portdata->out_urbs[j]) { 688 usb_free_urb(portdata->out_urbs[j]); 689 portdata->out_urbs[j] = NULL; 690 } 691 } 692 } 693 694 /* Now free per port private data */ 695 for (i = 0; i < serial->num_ports; i++) { 696 port = serial->port[i]; 697 kfree(usb_get_serial_port_data(port)); 698 } 699 } 700 701 MODULE_AUTHOR(DRIVER_AUTHOR); 702 MODULE_DESCRIPTION(DRIVER_DESC); 703 MODULE_VERSION(DRIVER_VERSION); 704 MODULE_LICENSE("GPL"); 705 706 #ifdef CONFIG_USB_DEBUG 707 module_param(debug, bool, S_IRUGO | S_IWUSR); 708 MODULE_PARM_DESC(debug, "Debug messages"); 709 #endif 710 711