1 /* 2 * Ben NanoNote and Arduino USB Host shield communication. 3 * 4 * Copyright 2013 Paul Boddie 5 * 6 * SPI functions derived from those in lib/atben.c by Werner Almesberger: 7 * 8 * Copyright 2010-2011 Werner Almesberger 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 */ 15 16 #include <ubb/ubb.h> 17 #include <stdio.h> 18 #include <signal.h> 19 #include <stdlib.h> 20 #include <usb.h> 21 #include <unistd.h> 22 23 /* Found in Python's asdl.h. */ 24 25 #ifndef __cplusplus 26 typedef enum {false, true} bool; 27 #endif 28 29 /* Initialisation states. */ 30 31 typedef enum 32 { 33 MAX_DEVSTATE_INIT = 0, 34 MAX_DEVSTATE_CONNECTED = 1, 35 MAX_DEVSTATE_RESET = 2, 36 MAX_DEVSTATE_READY = 3 37 } max_devstate; 38 39 /* Pin assignments: 40 * 41 * Sniffer UBB Shield 42 * ------- ---- ------ 43 * DAT2 DAT2 9 (INT) 44 * CD DAT3 10 (SS) 45 * CMD CMD 7 (RESET) 46 * VCC VDD VIN 47 * CLK CLK 13 (SCLK) 48 * GND GND GND 49 * DAT0 DAT0 11 (MOSI) 50 * DAT1 DAT1 12 (MISO) 51 * 8 (GPX) (not assigned) 52 */ 53 54 #define MAX_RESET UBB_CMD 55 #define MAX_SCLK UBB_CLK 56 #define MAX_MOSI UBB_DAT0 57 #define MAX_MISO UBB_DAT1 58 #define MAX_INT UBB_DAT2 59 #define MAX_SS UBB_DAT3 60 61 /* MAX3421E definitions. */ 62 63 #define MAX_REG_READ 0x00 64 #define MAX_REG_WRITE 0x02 65 66 #define MAX_REG_RCVFIFO 1 67 #define MAX_REG_SNDFIFO 2 68 #define MAX_REG_SUDFIFO 4 69 #define MAX_REG_RCVBC 6 70 #define MAX_REG_SNDBC 7 71 #define MAX_REG_USBIRQ 13 72 #define MAX_REG_USBIEN 14 73 #define MAX_REG_USBCTL 15 74 #define MAX_REG_CPUCTL 16 75 #define MAX_REG_PINCTL 17 76 #define MAX_REG_REVISION 18 77 #define MAX_REG_HIRQ 25 78 #define MAX_REG_HIEN 26 79 #define MAX_REG_MODE 27 80 #define MAX_REG_PERADDR 28 81 #define MAX_REG_HCTL 29 82 #define MAX_REG_HXFR 30 83 #define MAX_REG_HRSL 31 84 85 #define MAX_USBIRQ_OSCOKIRQ 1 86 #define MAX_USBIRQ_NOVBUSIRQ 32 87 #define MAX_USBIRQ_VBUSIRQ 64 88 89 #define MAX_USBCTL_PWRDOWN 16 90 #define MAX_USBCTL_CHIPRES 32 91 92 #define MAX_CPUCTL_IE 1 93 94 #define MAX_PINCTL_POSINT_LOW 0 95 #define MAX_PINCTL_POSINT_HIGH 4 96 #define MAX_PINCTL_INTLEVEL_EDGE 0 97 #define MAX_PINCTL_INTLEVEL_LEVEL 8 98 #define MAX_PINCTL_FDUPSPI_HALF 0 99 #define MAX_PINCTL_FDUPSPI_FULL 16 100 101 #define MAX_HIRQ_BUSEVENTIRQ 1 102 #define MAX_HIRQ_RWUIRQ 2 103 #define MAX_HIRQ_RCVDAVIRQ 4 104 #define MAX_HIRQ_SNDBAVIRQ 8 105 #define MAX_HIRQ_SUSDNIRQ 16 106 #define MAX_HIRQ_CONDETIRQ 32 107 #define MAX_HIRQ_FRAMEIRQ 64 108 #define MAX_HIRQ_HXFRDNIRQ 128 109 110 #define MAX_HIEN_BUSEVENTIE 1 111 #define MAX_HIEN_CONDETIE 32 112 #define MAX_HIEN_FRAMEIE 64 113 114 #define MAX_MODE_PERIPHERAL 0 115 #define MAX_MODE_HOST 1 116 #define MAX_MODE_LOWSPEED 2 117 #define MAX_MODE_SOFKAENAB 8 118 #define MAX_MODE_SEPIRQ_OFF 0 119 #define MAX_MODE_SEPIRQ_ON 16 120 #define MAX_MODE_DMPULLDN 64 121 #define MAX_MODE_DPPULLDN 128 122 123 #define MAX_MODE_HOST_ENABLED MAX_MODE_HOST | MAX_MODE_SEPIRQ_OFF | MAX_MODE_DMPULLDN | MAX_MODE_DPPULLDN 124 #define MAX_MODE_HOST_FULLSPEED MAX_MODE_HOST_ENABLED 125 #define MAX_MODE_HOST_LOWSPEED MAX_MODE_HOST_ENABLED | MAX_MODE_LOWSPEED 126 127 #define MAX_HCTL_BUSRST 1 128 #define MAX_HCTL_SAMPLEBUS 4 129 #define MAX_HCTL_RCVTOG0 16 130 #define MAX_HCTL_RCVTOG1 32 131 #define MAX_HCTL_SNDTOG0 64 132 #define MAX_HCTL_SNDTOG1 128 133 134 #define MAX_HXFR_SETUP 16 135 #define MAX_HXFR_OUTNIN 32 136 #define MAX_HXFR_HS 128 137 138 #define MAX_HRSL_JSTATUS 128 139 #define MAX_HRSL_KSTATUS 64 140 #define MAX_HRSL_SNDTOGRD 32 141 #define MAX_HRSL_RCVTOGRD 16 142 #define MAX_HRSL_HRSLT 15 143 144 #define max_reg(n) ((uint8_t) (n << 3)) 145 #define max_reg_read(n) (max_reg(n) | MAX_REG_READ) 146 #define max_reg_write(n) (max_reg(n) | MAX_REG_WRITE) 147 148 void spi_begin() 149 { 150 CLR(MAX_SS); 151 } 152 153 void spi_end() 154 { 155 SET(MAX_SS); 156 } 157 158 /** 159 * Send the given value via MOSI while receiving a value via MISO. 160 * This requires full-duplex SPI and will produce a status value for the first 161 * value sent (the command). 162 */ 163 uint8_t spi_sendrecv(uint8_t v) 164 { 165 uint8_t result = 0; 166 uint8_t mask; 167 168 for (mask = 0x80; mask; mask >>= 1) 169 { 170 if (v & mask) 171 { 172 #ifdef DEBUG 173 printf("1"); 174 #endif 175 SET(MAX_MOSI); 176 } 177 else 178 { 179 #ifdef DEBUG 180 printf("0"); 181 #endif 182 CLR(MAX_MOSI); 183 } 184 185 /* Wait for stable output signal. */ 186 187 SET(MAX_SCLK); 188 189 if (PIN(MAX_MISO)) 190 result |= mask; 191 192 CLR(MAX_SCLK); 193 } 194 195 #ifdef DEBUG 196 printf("\n"); 197 #endif 198 return result; 199 } 200 201 uint8_t max_read(uint8_t reg, uint8_t *status) 202 { 203 uint8_t result = 0, tmpstatus = 0; 204 205 tmpstatus = 0; 206 207 spi_begin(); 208 tmpstatus = spi_sendrecv(max_reg_read(reg)); 209 result = spi_sendrecv(0); 210 spi_end(); 211 212 if (status != NULL) 213 *status = tmpstatus; 214 215 return result; 216 } 217 218 uint8_t max_write(uint8_t reg, uint8_t value) 219 { 220 uint8_t status = 0; 221 222 spi_begin(); 223 status = spi_sendrecv(max_reg_write(reg)); 224 spi_sendrecv(value); 225 spi_end(); 226 227 return status; 228 } 229 230 /** 231 * Return whether data can be sent. 232 */ 233 bool max_can_send(uint8_t *status) 234 { 235 if (status == NULL) 236 return max_read(MAX_REG_HIRQ, NULL) & MAX_HIRQ_SNDBAVIRQ; 237 else 238 return *status & MAX_HIRQ_SNDBAVIRQ; 239 } 240 241 /** 242 * Set the sending data toggle. 243 */ 244 void max_set_send_toggle(bool toggle) 245 { 246 max_write(MAX_REG_HCTL, toggle ? MAX_HCTL_SNDTOG1 : MAX_HCTL_SNDTOG0); 247 } 248 249 /** 250 * Return the sending data toggle. 251 */ 252 bool max_get_send_toggle() 253 { 254 return (max_read(MAX_REG_HRSL, NULL) & MAX_HRSL_SNDTOGRD) != 0; 255 } 256 257 /** 258 * Set the receiving data toggle. 259 */ 260 void max_set_recv_toggle(bool toggle) 261 { 262 max_write(MAX_REG_HCTL, toggle ? MAX_HCTL_RCVTOG1 : MAX_HCTL_RCVTOG0); 263 } 264 265 /** 266 * Return the receiving data toggle. 267 */ 268 bool max_get_recv_toggle() 269 { 270 return (max_read(MAX_REG_HRSL, NULL) & MAX_HRSL_RCVTOGRD) != 0; 271 } 272 273 /** 274 * Wait for handshake/timeout after a transfer. 275 */ 276 uint8_t max_wait_transfer(uint8_t status) 277 { 278 while (!(status & MAX_HIRQ_HXFRDNIRQ)) 279 { 280 status = max_read(MAX_REG_HIRQ, NULL); 281 } 282 283 return status; 284 } 285 286 /** 287 * Send HS payload for control transfers. 288 */ 289 uint8_t max_send_hs() 290 { 291 uint8_t status = max_write(MAX_REG_HXFR, MAX_HXFR_HS); 292 return max_wait_transfer(status); 293 } 294 295 /** 296 * Write the given data to the FIFO. 297 */ 298 void max_write_fifo(uint8_t endpoint, uint8_t *data, uint8_t len) 299 { 300 uint8_t count; 301 302 for (count = 0; count < len; count++) 303 { 304 max_write(endpoint ? MAX_REG_SNDFIFO : MAX_REG_SUDFIFO, data[count]); 305 } 306 307 if (endpoint) 308 max_write(MAX_REG_SNDBC, len); 309 } 310 311 /** 312 * Read the data from the FIFO. 313 */ 314 void max_read_fifo(uint8_t *data, uint8_t *len, uint8_t *datalimit) 315 { 316 uint8_t count, received = max_read(MAX_REG_RCVBC, NULL); 317 318 *len += received; 319 320 for (count = 0; (count < received) && (data < datalimit); count++) 321 { 322 *data++ = max_read(MAX_REG_RCVFIFO, NULL); 323 } 324 } 325 326 /** 327 * Send a control request to the given address consisting of the given setup 328 * data. 329 */ 330 uint8_t max_control(uint8_t address, uint8_t *setup) 331 { 332 uint8_t status, hrsl = 0; 333 334 printf("Writing FIFO with setup...\n"); 335 max_write_fifo(0, setup, 8); 336 337 /* Set the address. */ 338 339 max_write(MAX_REG_PERADDR, address); 340 341 /* Initiate the transfer. */ 342 343 do 344 { 345 printf("Initiating transfer...\n"); 346 status = max_write(MAX_REG_HXFR, MAX_HXFR_SETUP); 347 status = max_wait_transfer(status); 348 hrsl = max_read(MAX_REG_HRSL, &status); 349 printf("HRSL = %x\n", hrsl); 350 sleep(1); 351 } 352 while (hrsl & MAX_HRSL_HRSLT); 353 354 return status; 355 } 356 357 /** 358 * Send a request to the given address and endpoint, using the supplied data 359 * payload with the given length, indicating the preserved toggle state of the 360 * endpoint (which will be updated). 361 */ 362 uint8_t max_send(uint8_t address, uint8_t endpoint, uint8_t *data, uint8_t len, bool *toggle) 363 { 364 uint8_t status, hrsl = 0; 365 366 max_write_fifo(endpoint, data, len); 367 368 if (endpoint) 369 max_set_send_toggle(*toggle); 370 371 /* Set the address. */ 372 373 max_write(MAX_REG_PERADDR, address); 374 375 /* Initiate the transfer. */ 376 377 do 378 { 379 status = max_write(MAX_REG_HXFR, endpoint | MAX_HXFR_OUTNIN); 380 status = max_wait_transfer(status); 381 382 /* Test for usable data. */ 383 384 if (!(status & MAX_HIRQ_SNDBAVIRQ)) 385 continue; 386 387 hrsl = max_read(MAX_REG_HRSL, &status); 388 } 389 while (hrsl & MAX_HRSL_HRSLT); 390 391 if (endpoint) 392 *toggle = max_get_send_toggle(); 393 394 return status; 395 } 396 397 /** 398 * Make a request for data from the given address and endpoint, collecting it in 399 * the supplied buffer with the given length, indicating the preserved toggle 400 * state of the endpoint (which will be updated) The length will be updated to 401 * indicate the total length of the received data. 402 */ 403 uint8_t max_recv(uint8_t address, uint8_t endpoint, uint8_t *data, uint8_t *len, bool *toggle) 404 { 405 uint8_t *datalimit = data + *len; 406 uint8_t status, hrsl = 0; 407 408 if (endpoint) 409 max_set_send_toggle(*toggle); 410 411 /* Set the address. */ 412 413 max_write(MAX_REG_PERADDR, address); 414 415 /* Initiate the transfer. */ 416 417 do 418 { 419 status = max_write(MAX_REG_HXFR, endpoint); 420 status = max_wait_transfer(status); 421 422 /* Test for usable data. */ 423 424 if (!(status & MAX_HIRQ_RCVDAVIRQ)) 425 continue; 426 427 hrsl = max_read(MAX_REG_HRSL, &status); 428 } 429 while (hrsl & MAX_HRSL_HRSLT); 430 431 do 432 { 433 max_read_fifo(data, len, datalimit); 434 435 /* Indicate that all data has been read. */ 436 437 status = max_write(MAX_REG_HIRQ, MAX_HIRQ_RCVDAVIRQ); 438 } 439 while (status & MAX_HIRQ_RCVDAVIRQ); 440 441 if (endpoint) 442 *toggle = max_get_send_toggle(); 443 444 return status; 445 } 446 447 void chipreset() 448 { 449 printf("Resetting...\n"); 450 max_write(MAX_REG_USBCTL, MAX_USBCTL_CHIPRES); 451 452 printf("Clearing the reset...\n"); 453 max_write(MAX_REG_USBCTL, 0); 454 } 455 456 uint8_t check() 457 { 458 uint8_t oscillator; 459 460 oscillator = max_read(MAX_REG_USBIRQ, NULL); 461 462 return (oscillator & ~(MAX_USBIRQ_NOVBUSIRQ | MAX_USBIRQ_VBUSIRQ)) == MAX_USBIRQ_OSCOKIRQ; 463 } 464 465 uint8_t wait() 466 { 467 uint16_t timeout = 1024; 468 469 /* Wait for the oscillator before performing USB activity. */ 470 471 printf("Waiting...\n"); 472 473 while ((timeout > 0) && (!check())) 474 { 475 timeout--; 476 } 477 478 printf("Iterations remaining: %d\n", timeout); 479 480 return timeout; 481 } 482 483 uint8_t samplebusready() 484 { 485 uint8_t result; 486 487 result = max_read(MAX_REG_HCTL, NULL); 488 489 return !(result & MAX_HCTL_SAMPLEBUS); 490 } 491 492 void samplebus() 493 { 494 max_write(MAX_REG_HCTL, MAX_HCTL_SAMPLEBUS); 495 while (!samplebusready()); 496 } 497 498 /** 499 * Handle the connection or disconnection of a device, returning true if the 500 * device is now connected or false otherwise. 501 */ 502 bool devicechanged() 503 { 504 uint8_t hrsl, mode; 505 506 hrsl = max_read(MAX_REG_HRSL, NULL); 507 mode = max_read(MAX_REG_MODE, NULL); 508 509 if ((hrsl & MAX_HRSL_JSTATUS) && (hrsl & MAX_HRSL_KSTATUS)) 510 { 511 printf("Bad device status.\n"); 512 } 513 else if (!(hrsl & MAX_HRSL_JSTATUS) && !(hrsl & MAX_HRSL_KSTATUS)) 514 { 515 printf("Device disconnected.\n"); 516 } 517 else 518 { 519 printf("Device connected.\n"); 520 521 /* Low speed device when J and lowspeed have the same level. 522 Since J and K should have opposing levels, K can be tested when 523 lowspeed is low. */ 524 525 if (((hrsl & MAX_HRSL_JSTATUS) && (mode & MAX_MODE_LOWSPEED)) || 526 ((hrsl & MAX_HRSL_KSTATUS) && !(mode & MAX_MODE_LOWSPEED))) 527 { 528 printf("Device is low speed.\n"); 529 max_write(MAX_REG_MODE, MAX_MODE_HOST_LOWSPEED); 530 } 531 else 532 { 533 printf("Device is full speed.\n"); 534 max_write(MAX_REG_MODE, MAX_MODE_HOST_FULLSPEED); 535 } 536 537 /* Reset the device. */ 538 539 max_write(MAX_REG_HCTL, MAX_HCTL_BUSRST); 540 return true; 541 } 542 543 return false; 544 } 545 546 void setup_packet(uint8_t *setup, uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, uint16_t length) 547 { 548 setup[0] = request_type; 549 setup[1] = request; 550 setup[2] = value & 0xff; 551 setup[3] = value >> 8; 552 setup[4] = index & 0xff; 553 setup[5] = index >> 8; 554 setup[6] = length & 0xff; 555 setup[7] = length >> 8; 556 } 557 558 bool max_init_device(bool *in_toggle) 559 { 560 uint8_t data[64], len = 64, setup[8]; 561 struct usb_device_descriptor *desc; 562 563 printf("Sending control request to address 0, endpoint 0...\n"); 564 setup_packet(setup, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, USB_DT_DEVICE, 0, USB_DT_DEVICE_SIZE); 565 max_control(0, setup); 566 max_recv(0, 0, data, &len, in_toggle); 567 568 if (len >= sizeof(struct usb_device_descriptor)) 569 { 570 desc = (struct usb_device_descriptor *) data; 571 printf("bLength: %d\n", desc->bLength); 572 printf("bDescriptorType: %d\n", desc->bDescriptorType); 573 printf("bcdUSB: %d\n", desc->bcdUSB); 574 printf("bDeviceClass: %d\n", desc->bDeviceClass); 575 printf("bDeviceSubClass: %d\n", desc->bDeviceSubClass); 576 printf("bDeviceProtocol: %d\n", desc->bDeviceProtocol); 577 printf("bMaxPacketSize0: %d\n", desc->bMaxPacketSize0); 578 printf("idVendor: %x\n", desc->idVendor); 579 printf("idProduct: %x\n", desc->idProduct); 580 printf("bcdDevice: %d\n", desc->bcdDevice); 581 printf("iManufacturer: %d\n", desc->iManufacturer); 582 printf("iProduct: %d\n", desc->iProduct); 583 printf("iSerialNumber: %d\n", desc->iSerialNumber); 584 printf("bNumConfigurations: %d\n", desc->bNumConfigurations); 585 return true; 586 } 587 588 return false; 589 } 590 591 void shutdown(int signum) 592 { 593 printf("Closing...\n"); 594 ubb_close(0); 595 exit(1); 596 } 597 598 int main(int argc, char *argv[]) 599 { 600 uint8_t status = 0, revision = 0; 601 uint16_t count; 602 bool in_toggle = 0, bus_event, data_event, suspended_event, connection_event, frame_event; 603 max_devstate devstate = MAX_DEVSTATE_INIT; 604 605 signal(SIGINT, &shutdown); 606 607 if (ubb_open(0) < 0) { 608 perror("ubb_open"); 609 return 1; 610 } 611 612 ubb_power(1); 613 printf("Power on.\n"); 614 615 OUT(MAX_SS); 616 OUT(MAX_MOSI); 617 OUT(MAX_SCLK); 618 OUT(MAX_RESET); 619 IN(MAX_INT); 620 IN(MAX_MISO); 621 622 /* Initialise SPI. */ 623 /* Set SS# to 1. */ 624 625 SET(MAX_SS); 626 CLR(MAX_MOSI); 627 CLR(MAX_SCLK); 628 SET(MAX_RESET); 629 630 /* Initialise the MAX3421E. */ 631 632 /* Set full-duplex, interrupt signalling. */ 633 634 printf("Setting pin control...\n"); 635 max_write(MAX_REG_PINCTL, MAX_PINCTL_INTLEVEL_LEVEL | MAX_PINCTL_FDUPSPI_FULL); 636 637 chipreset(); 638 printf("Ready? %d\n", wait()); 639 640 /* Check various registers. */ 641 642 printf("Mode: %x\n", max_read(MAX_REG_MODE, &status)); 643 printf("IRQ: %x\n", max_read(MAX_REG_HIRQ, &status)); 644 645 /* Set host mode. */ 646 647 printf("Setting mode...\n"); 648 status = max_write(MAX_REG_MODE, MAX_MODE_HOST_ENABLED); 649 650 printf("Setting INT signalling...\n"); 651 status = max_write(MAX_REG_CPUCTL, MAX_CPUCTL_IE); 652 653 printf("Setting event signalling...\n"); 654 status = max_write(MAX_REG_HIEN, MAX_HIEN_CONDETIE | MAX_HIEN_FRAMEIE | MAX_HIEN_BUSEVENTIE); 655 656 /* Check various registers. */ 657 658 printf("Mode: %x\n", max_read(MAX_REG_MODE, &status)); 659 printf("IRQ: %x\n", max_read(MAX_REG_HIRQ, &status)); 660 printf("IE: %x\n", max_read(MAX_REG_HIEN, &status)); 661 printf("CPU: %x\n", max_read(MAX_REG_CPUCTL, &status)); 662 printf("Pin: %x\n", max_read(MAX_REG_PINCTL, &status)); 663 printf("USBIRQ: %x\n", max_read(MAX_REG_USBIRQ, &status)); 664 printf("USBIE: %x\n", max_read(MAX_REG_USBIEN, &status)); 665 666 /* Read from the REVISION register. */ 667 668 printf("Reading...\n"); 669 revision = max_read(MAX_REG_REVISION, &status); 670 printf("Revision = %x\n", revision); 671 672 for (count = 0; count <= 65535; count++) 673 { 674 if (!PIN(MAX_INT)) 675 { 676 status = max_read(MAX_REG_HIRQ, NULL); 677 678 if ((bus_event = status & MAX_HIRQ_BUSEVENTIRQ)) 679 printf("Bus "); 680 if ((data_event = status & MAX_HIRQ_RCVDAVIRQ)) 681 printf("Data "); 682 if ((suspended_event = status & MAX_HIRQ_SUSDNIRQ)) 683 printf("Suspended "); 684 if ((connection_event = status & MAX_HIRQ_CONDETIRQ)) 685 printf("Connection "); 686 frame_event = status & MAX_HIRQ_FRAMEIRQ; 687 printf("\n"); 688 689 max_write(MAX_REG_HIRQ, status); 690 691 /* Detect device connection/disconnection. */ 692 693 if ((devstate == MAX_DEVSTATE_INIT) && connection_event && devicechanged()) 694 { 695 devstate = MAX_DEVSTATE_CONNECTED; 696 } 697 698 /* Handle device reset initiation. */ 699 700 else if ((devstate == MAX_DEVSTATE_CONNECTED) && bus_event) 701 { 702 max_write(MAX_REG_MODE, max_read(MAX_REG_MODE, NULL) | MAX_MODE_SOFKAENAB); 703 devstate = MAX_DEVSTATE_RESET; 704 } 705 706 /* Handle device reset completion, initiating communications. */ 707 708 else if ((devstate == MAX_DEVSTATE_RESET) && frame_event && max_can_send(&status)) 709 { 710 max_init_device(&in_toggle); 711 devstate = MAX_DEVSTATE_READY; 712 max_write(MAX_REG_MODE, max_read(MAX_REG_MODE, NULL) & ~MAX_MODE_SOFKAENAB); 713 } 714 715 /* Handle device disconnection. */ 716 717 else if ((devstate != MAX_DEVSTATE_INIT) && connection_event) 718 { 719 devstate = MAX_DEVSTATE_INIT; 720 } 721 } 722 } 723 724 printf("Closing...\n"); 725 ubb_close(0); 726 727 return 0; 728 }