1/* 2 * IEEE 1394 for Linux 3 * 4 * Copyright (C) 1999 Andreas E. Bombe 5 * 6 * This code is licensed under the GPL. See the file COPYING in the root 7 * directory of the kernel sources for details. 8 * 9 * 10 * Contributions: 11 * 12 * Christian Toegel <christian.toegel@gmx.at> 13 * unregister address space 14 * 15 * Manfred Weihs <weihs@ict.tuwien.ac.at> 16 * unregister address space 17 * 18 */ 19 20#include <linux/slab.h> 21#include <linux/list.h> 22#include <linux/bitops.h> 23 24#include "ieee1394.h" 25#include "ieee1394_types.h" 26#include "hosts.h" 27#include "ieee1394_core.h" 28#include "highlevel.h" 29#include "nodemgr.h" 30 31 32struct hl_host_info { 33 struct list_head list; 34 struct hpsb_host *host; 35 size_t size; 36 unsigned long key; 37 void *data; 38}; 39 40 41static LIST_HEAD(hl_drivers); 42static DECLARE_RWSEM(hl_drivers_sem); 43 44static LIST_HEAD(hl_irqs); 45static DEFINE_RWLOCK(hl_irqs_lock); 46 47static DEFINE_RWLOCK(addr_space_lock); 48 49/* addr_space list will have zero and max already included as bounds */ 50static struct hpsb_address_ops dummy_ops = { NULL, NULL, NULL, NULL }; 51static struct hpsb_address_serve dummy_zero_addr, dummy_max_addr; 52 53 54static struct hl_host_info *hl_get_hostinfo(struct hpsb_highlevel *hl, 55 struct hpsb_host *host) 56{ 57 struct hl_host_info *hi = NULL; 58 59 if (!hl || !host) 60 return NULL; 61 62 read_lock(&hl->host_info_lock); 63 list_for_each_entry(hi, &hl->host_info_list, list) { 64 if (hi->host == host) { 65 read_unlock(&hl->host_info_lock); 66 return hi; 67 } 68 } 69 read_unlock(&hl->host_info_lock); 70 return NULL; 71} 72 73/** 74 * hpsb_get_hostinfo - retrieve a hostinfo pointer bound to this driver/host 75 * 76 * Returns a per @host and @hl driver data structure that was previously stored 77 * by hpsb_create_hostinfo. 78 */ 79void *hpsb_get_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host) 80{ 81 struct hl_host_info *hi = hl_get_hostinfo(hl, host); 82 83 return hi ? hi->data : NULL; 84} 85 86/** 87 * hpsb_create_hostinfo - allocate a hostinfo pointer bound to this driver/host 88 * 89 * Allocate a hostinfo pointer backed by memory with @data_size and bind it to 90 * to this @hl driver and @host. If @data_size is zero, then the return here is 91 * only valid for error checking. 92 */ 93void *hpsb_create_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, 94 size_t data_size) 95{ 96 struct hl_host_info *hi; 97 void *data; 98 unsigned long flags; 99 100 hi = hl_get_hostinfo(hl, host); 101 if (hi) { 102 HPSB_ERR("%s called hpsb_create_hostinfo when hostinfo already" 103 " exists", hl->name); 104 return NULL; 105 } 106 107 hi = kzalloc(sizeof(*hi) + data_size, GFP_ATOMIC); 108 if (!hi) 109 return NULL; 110 111 if (data_size) { 112 data = hi->data = hi + 1; 113 hi->size = data_size; 114 } else 115 data = hi; 116 117 hi->host = host; 118 119 write_lock_irqsave(&hl->host_info_lock, flags); 120 list_add_tail(&hi->list, &hl->host_info_list); 121 write_unlock_irqrestore(&hl->host_info_lock, flags); 122 123 return data; 124} 125 126/** 127 * hpsb_set_hostinfo - set the hostinfo pointer to something useful 128 * 129 * Usually follows a call to hpsb_create_hostinfo, where the size is 0. 130 */ 131int hpsb_set_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, 132 void *data) 133{ 134 struct hl_host_info *hi; 135 136 hi = hl_get_hostinfo(hl, host); 137 if (hi) { 138 if (!hi->size && !hi->data) { 139 hi->data = data; 140 return 0; 141 } else 142 HPSB_ERR("%s called hpsb_set_hostinfo when hostinfo " 143 "already has data", hl->name); 144 } else 145 HPSB_ERR("%s called hpsb_set_hostinfo when no hostinfo exists", 146 hl->name); 147 return -EINVAL; 148} 149 150/** 151 * hpsb_destroy_hostinfo - free and remove a hostinfo pointer 152 * 153 * Free and remove the hostinfo pointer bound to this @hl driver and @host. 154 */ 155void hpsb_destroy_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host) 156{ 157 struct hl_host_info *hi; 158 159 hi = hl_get_hostinfo(hl, host); 160 if (hi) { 161 unsigned long flags; 162 write_lock_irqsave(&hl->host_info_lock, flags); 163 list_del(&hi->list); 164 write_unlock_irqrestore(&hl->host_info_lock, flags); 165 kfree(hi); 166 } 167 return; 168} 169 170/** 171 * hpsb_set_hostinfo_key - set an alternate lookup key for an hostinfo 172 * 173 * Sets an alternate lookup key for the hostinfo bound to this @hl driver and 174 * @host. 175 */ 176void hpsb_set_hostinfo_key(struct hpsb_highlevel *hl, struct hpsb_host *host, 177 unsigned long key) 178{ 179 struct hl_host_info *hi; 180 181 hi = hl_get_hostinfo(hl, host); 182 if (hi) 183 hi->key = key; 184 return; 185} 186 187/** 188 * hpsb_get_hostinfo_bykey - retrieve a hostinfo pointer by its alternate key 189 */ 190void *hpsb_get_hostinfo_bykey(struct hpsb_highlevel *hl, unsigned long key) 191{ 192 struct hl_host_info *hi; 193 void *data = NULL; 194 195 if (!hl) 196 return NULL; 197 198 read_lock(&hl->host_info_lock); 199 list_for_each_entry(hi, &hl->host_info_list, list) { 200 if (hi->key == key) { 201 data = hi->data; 202 break; 203 } 204 } 205 read_unlock(&hl->host_info_lock); 206 return data; 207} 208 209static int highlevel_for_each_host_reg(struct hpsb_host *host, void *__data) 210{ 211 struct hpsb_highlevel *hl = __data; 212 213 hl->add_host(host); 214 215 if (host->update_config_rom && hpsb_update_config_rom_image(host) < 0) 216 HPSB_ERR("Failed to generate Configuration ROM image for host " 217 "%s-%d", hl->name, host->id); 218 return 0; 219} 220 221/** 222 * hpsb_register_highlevel - register highlevel driver 223 * 224 * The name pointer in @hl has to stay valid at all times because the string is 225 * not copied. 226 */ 227void hpsb_register_highlevel(struct hpsb_highlevel *hl) 228{ 229 unsigned long flags; 230 231 INIT_LIST_HEAD(&hl->addr_list); 232 INIT_LIST_HEAD(&hl->host_info_list); 233 234 rwlock_init(&hl->host_info_lock); 235 236 down_write(&hl_drivers_sem); 237 list_add_tail(&hl->hl_list, &hl_drivers); 238 up_write(&hl_drivers_sem); 239 240 write_lock_irqsave(&hl_irqs_lock, flags); 241 list_add_tail(&hl->irq_list, &hl_irqs); 242 write_unlock_irqrestore(&hl_irqs_lock, flags); 243 244 if (hl->add_host) 245 nodemgr_for_each_host(hl, highlevel_for_each_host_reg); 246 return; 247} 248 249static void __delete_addr(struct hpsb_address_serve *as) 250{ 251 list_del(&as->host_list); 252 list_del(&as->hl_list); 253 kfree(as); 254} 255 256static void __unregister_host(struct hpsb_highlevel *hl, struct hpsb_host *host, 257 int update_cr) 258{ 259 unsigned long flags; 260 struct list_head *lh, *next; 261 struct hpsb_address_serve *as; 262 263 /* First, let the highlevel driver unreg */ 264 if (hl->remove_host) 265 hl->remove_host(host); 266 267 /* Remove any addresses that are matched for this highlevel driver 268 * and this particular host. */ 269 write_lock_irqsave(&addr_space_lock, flags); 270 list_for_each_safe (lh, next, &hl->addr_list) { 271 as = list_entry(lh, struct hpsb_address_serve, hl_list); 272 if (as->host == host) 273 __delete_addr(as); 274 } 275 write_unlock_irqrestore(&addr_space_lock, flags); 276 277 /* Now update the config-rom to reflect anything removed by the 278 * highlevel driver. */ 279 if (update_cr && host->update_config_rom && 280 hpsb_update_config_rom_image(host) < 0) 281 HPSB_ERR("Failed to generate Configuration ROM image for host " 282 "%s-%d", hl->name, host->id); 283 284 /* Finally remove all the host info associated between these two. */ 285 hpsb_destroy_hostinfo(hl, host); 286} 287 288static int highlevel_for_each_host_unreg(struct hpsb_host *host, void *__data) 289{ 290 struct hpsb_highlevel *hl = __data; 291 292 __unregister_host(hl, host, 1); 293 return 0; 294} 295 296/** 297 * hpsb_unregister_highlevel - unregister highlevel driver 298 */ 299void hpsb_unregister_highlevel(struct hpsb_highlevel *hl) 300{ 301 unsigned long flags; 302 303 write_lock_irqsave(&hl_irqs_lock, flags); 304 list_del(&hl->irq_list); 305 write_unlock_irqrestore(&hl_irqs_lock, flags); 306 307 down_write(&hl_drivers_sem); 308 list_del(&hl->hl_list); 309 up_write(&hl_drivers_sem); 310 311 nodemgr_for_each_host(hl, highlevel_for_each_host_unreg); 312} 313 314/** 315 * hpsb_allocate_and_register_addrspace - alloc' and reg' a host address space 316 * 317 * @start and @end are 48 bit pointers and have to be quadlet aligned. 318 * @end points to the first address behind the handled addresses. This 319 * function can be called multiple times for a single hpsb_highlevel @hl to 320 * implement sparse register sets. The requested region must not overlap any 321 * previously allocated region, otherwise registering will fail. 322 * 323 * It returns true for successful allocation. Address spaces can be 324 * unregistered with hpsb_unregister_addrspace. All remaining address spaces 325 * are automatically deallocated together with the hpsb_highlevel @hl. 326 */ 327u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, 328 struct hpsb_host *host, 329 struct hpsb_address_ops *ops, 330 u64 size, u64 alignment, 331 u64 start, u64 end) 332{ 333 struct hpsb_address_serve *as, *a1, *a2; 334 struct list_head *entry; 335 u64 retval = CSR1212_INVALID_ADDR_SPACE; 336 unsigned long flags; 337 u64 align_mask = ~(alignment - 1); 338 339 if ((alignment & 3) || (alignment > 0x800000000000ULL) || 340 (hweight64(alignment) != 1)) { 341 HPSB_ERR("%s called with invalid alignment: 0x%048llx", 342 __FUNCTION__, (unsigned long long)alignment); 343 return retval; 344 } 345 346 /* default range, 347 * avoids controller's posted write area (see OHCI 1.1 clause 1.5) */ 348 if (start == CSR1212_INVALID_ADDR_SPACE && 349 end == CSR1212_INVALID_ADDR_SPACE) { 350 start = host->middle_addr_space; 351 end = CSR1212_ALL_SPACE_END; 352 } 353 354 if (((start|end) & ~align_mask) || (start >= end) || 355 (end > CSR1212_ALL_SPACE_END)) { 356 HPSB_ERR("%s called with invalid addresses " 357 "(start = %012Lx end = %012Lx)", __FUNCTION__, 358 (unsigned long long)start,(unsigned long long)end); 359 return retval; 360 } 361 362 as = kmalloc(sizeof(*as), GFP_KERNEL); 363 if (!as) 364 return retval; 365 366 INIT_LIST_HEAD(&as->host_list); 367 INIT_LIST_HEAD(&as->hl_list); 368 as->op = ops; 369 as->host = host; 370 371 write_lock_irqsave(&addr_space_lock, flags); 372 list_for_each(entry, &host->addr_space) { 373 u64 a1sa, a1ea; 374 u64 a2sa, a2ea; 375 376 a1 = list_entry(entry, struct hpsb_address_serve, host_list); 377 a2 = list_entry(entry->next, struct hpsb_address_serve, 378 host_list); 379 380 a1sa = a1->start & align_mask; 381 a1ea = (a1->end + alignment -1) & align_mask; 382 a2sa = a2->start & align_mask; 383 a2ea = (a2->end + alignment -1) & align_mask; 384 385 if ((a2sa - a1ea >= size) && (a2sa - start >= size) && 386 (a2sa > start)) { 387 as->start = max(start, a1ea); 388 as->end = as->start + size; 389 list_add(&as->host_list, entry); 390 list_add_tail(&as->hl_list, &hl->addr_list); 391 retval = as->start; 392 break; 393 } 394 } 395 write_unlock_irqrestore(&addr_space_lock, flags); 396 397 if (retval == CSR1212_INVALID_ADDR_SPACE) 398 kfree(as); 399 return retval; 400} 401 402/** 403 * hpsb_register_addrspace - register a host address space 404 * 405 * @start and @end are 48 bit pointers and have to be quadlet aligned. 406 * @end points to the first address behind the handled addresses. This 407 * function can be called multiple times for a single hpsb_highlevel @hl to 408 * implement sparse register sets. The requested region must not overlap any 409 * previously allocated region, otherwise registering will fail. 410 * 411 * It returns true for successful allocation. Address spaces can be 412 * unregistered with hpsb_unregister_addrspace. All remaining address spaces 413 * are automatically deallocated together with the hpsb_highlevel @hl. 414 */ 415int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, 416 struct hpsb_address_ops *ops, u64 start, u64 end) 417{ 418 struct hpsb_address_serve *as; 419 struct list_head *lh; 420 int retval = 0; 421 unsigned long flags; 422 423 if (((start|end) & 3) || (start >= end) || 424 (end > CSR1212_ALL_SPACE_END)) { 425 HPSB_ERR("%s called with invalid addresses", __FUNCTION__); 426 return 0; 427 } 428 429 as = kmalloc(sizeof(*as), GFP_ATOMIC); 430 if (!as) 431 return 0; 432 433 INIT_LIST_HEAD(&as->host_list); 434 INIT_LIST_HEAD(&as->hl_list); 435 as->op = ops; 436 as->start = start; 437 as->end = end; 438 as->host = host; 439 440 write_lock_irqsave(&addr_space_lock, flags); 441 list_for_each(lh, &host->addr_space) { 442 struct hpsb_address_serve *as_this = 443 list_entry(lh, struct hpsb_address_serve, host_list); 444 struct hpsb_address_serve *as_next = 445 list_entry(lh->next, struct hpsb_address_serve, 446 host_list); 447 448 if (as_this->end > as->start) 449 break; 450 451 if (as_next->start >= as->end) { 452 list_add(&as->host_list, lh); 453 list_add_tail(&as->hl_list, &hl->addr_list); 454 retval = 1; 455 break; 456 } 457 } 458 write_unlock_irqrestore(&addr_space_lock, flags); 459 460 if (retval == 0) 461 kfree(as); 462 return retval; 463} 464 465int hpsb_unregister_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, 466 u64 start) 467{ 468 int retval = 0; 469 struct hpsb_address_serve *as; 470 struct list_head *lh, *next; 471 unsigned long flags; 472 473 write_lock_irqsave(&addr_space_lock, flags); 474 list_for_each_safe (lh, next, &hl->addr_list) { 475 as = list_entry(lh, struct hpsb_address_serve, hl_list); 476 if (as->start == start && as->host == host) { 477 __delete_addr(as); 478 retval = 1; 479 break; 480 } 481 } 482 write_unlock_irqrestore(&addr_space_lock, flags); 483 return retval; 484} 485 486/** 487 * hpsb_listen_channel - enable receving a certain isochronous channel 488 * 489 * Reception is handled through the @hl's iso_receive op. 490 */ 491int hpsb_listen_channel(struct hpsb_highlevel *hl, struct hpsb_host *host, 492 unsigned int channel) 493{ 494 if (channel > 63) { 495 HPSB_ERR("%s called with invalid channel", __FUNCTION__); 496 return -EINVAL; 497 } 498 if (host->iso_listen_count[channel]++ == 0) 499 return host->driver->devctl(host, ISO_LISTEN_CHANNEL, channel); 500 return 0; 501} 502 503/** 504 * hpsb_unlisten_channel - disable receving a certain isochronous channel 505 */ 506void hpsb_unlisten_channel(struct hpsb_highlevel *hl, struct hpsb_host *host, 507 unsigned int channel) 508{ 509 if (channel > 63) { 510 HPSB_ERR("%s called with invalid channel", __FUNCTION__); 511 return; 512 } 513 if (--host->iso_listen_count[channel] == 0) 514 host->driver->devctl(host, ISO_UNLISTEN_CHANNEL, channel); 515} 516 517static void init_hpsb_highlevel(struct hpsb_host *host) 518{ 519 INIT_LIST_HEAD(&dummy_zero_addr.host_list); 520 INIT_LIST_HEAD(&dummy_zero_addr.hl_list); 521 INIT_LIST_HEAD(&dummy_max_addr.host_list); 522 INIT_LIST_HEAD(&dummy_max_addr.hl_list); 523 524 dummy_zero_addr.op = dummy_max_addr.op = &dummy_ops; 525 526 dummy_zero_addr.start = dummy_zero_addr.end = 0; 527 dummy_max_addr.start = dummy_max_addr.end = ((u64) 1) << 48; 528 529 list_add_tail(&dummy_zero_addr.host_list, &host->addr_space); 530 list_add_tail(&dummy_max_addr.host_list, &host->addr_space); 531} 532 533void highlevel_add_host(struct hpsb_host *host) 534{ 535 struct hpsb_highlevel *hl; 536 537 init_hpsb_highlevel(host); 538 539 down_read(&hl_drivers_sem); 540 list_for_each_entry(hl, &hl_drivers, hl_list) { 541 if (hl->add_host) 542 hl->add_host(host); 543 } 544 up_read(&hl_drivers_sem); 545 if (host->update_config_rom && hpsb_update_config_rom_image(host) < 0) 546 HPSB_ERR("Failed to generate Configuration ROM image for host " 547 "%s-%d", hl->name, host->id); 548} 549 550void highlevel_remove_host(struct hpsb_host *host) 551{ 552 struct hpsb_highlevel *hl; 553 554 down_read(&hl_drivers_sem); 555 list_for_each_entry(hl, &hl_drivers, hl_list) 556 __unregister_host(hl, host, 0); 557 up_read(&hl_drivers_sem); 558} 559 560void highlevel_host_reset(struct hpsb_host *host) 561{ 562 unsigned long flags; 563 struct hpsb_highlevel *hl; 564 565 read_lock_irqsave(&hl_irqs_lock, flags); 566 list_for_each_entry(hl, &hl_irqs, irq_list) { 567 if (hl->host_reset) 568 hl->host_reset(host); 569 } 570 read_unlock_irqrestore(&hl_irqs_lock, flags); 571} 572 573void highlevel_iso_receive(struct hpsb_host *host, void *data, size_t length) 574{ 575 unsigned long flags; 576 struct hpsb_highlevel *hl; 577 int channel = (((quadlet_t *)data)[0] >> 8) & 0x3f; 578 579 read_lock_irqsave(&hl_irqs_lock, flags); 580 list_for_each_entry(hl, &hl_irqs, irq_list) { 581 if (hl->iso_receive) 582 hl->iso_receive(host, channel, data, length); 583 } 584 read_unlock_irqrestore(&hl_irqs_lock, flags); 585} 586 587void highlevel_fcp_request(struct hpsb_host *host, int nodeid, int direction, 588 void *data, size_t length) 589{ 590 unsigned long flags; 591 struct hpsb_highlevel *hl; 592 int cts = ((quadlet_t *)data)[0] >> 4; 593 594 read_lock_irqsave(&hl_irqs_lock, flags); 595 list_for_each_entry(hl, &hl_irqs, irq_list) { 596 if (hl->fcp_request) 597 hl->fcp_request(host, nodeid, direction, cts, data, 598 length); 599 } 600 read_unlock_irqrestore(&hl_irqs_lock, flags); 601} 602 603/* 604 * highlevel_read, highlevel_write, highlevel_lock, highlevel_lock64: 605 * 606 * These functions are called to handle transactions. They are called when a 607 * packet arrives. The flags argument contains the second word of the first 608 * header quadlet of the incoming packet (containing transaction label, retry 609 * code, transaction code and priority). These functions either return a 610 * response code or a negative number. In the first case a response will be 611 * generated. In the latter case, no response will be sent and the driver which 612 * handled the request will send the response itself. 613 */ 614int highlevel_read(struct hpsb_host *host, int nodeid, void *data, u64 addr, 615 unsigned int length, u16 flags) 616{ 617 struct hpsb_address_serve *as; 618 unsigned int partlength; 619 int rcode = RCODE_ADDRESS_ERROR; 620 621 read_lock(&addr_space_lock); 622 list_for_each_entry(as, &host->addr_space, host_list) { 623 if (as->start > addr) 624 break; 625 626 if (as->end > addr) { 627 partlength = min(as->end - addr, (u64) length); 628 629 if (as->op->read) 630 rcode = as->op->read(host, nodeid, data, 631 addr, partlength, flags); 632 else 633 rcode = RCODE_TYPE_ERROR; 634 635 data += partlength; 636 length -= partlength; 637 addr += partlength; 638 639 if ((rcode != RCODE_COMPLETE) || !length) 640 break; 641 } 642 } 643 read_unlock(&addr_space_lock); 644 645 if (length && (rcode == RCODE_COMPLETE)) 646 rcode = RCODE_ADDRESS_ERROR; 647 return rcode; 648} 649 650int highlevel_write(struct hpsb_host *host, int nodeid, int destid, void *data, 651 u64 addr, unsigned int length, u16 flags) 652{ 653 struct hpsb_address_serve *as; 654 unsigned int partlength; 655 int rcode = RCODE_ADDRESS_ERROR; 656 657 read_lock(&addr_space_lock); 658 list_for_each_entry(as, &host->addr_space, host_list) { 659 if (as->start > addr) 660 break; 661 662 if (as->end > addr) { 663 partlength = min(as->end - addr, (u64) length); 664 665 if (as->op->write) 666 rcode = as->op->write(host, nodeid, destid, 667 data, addr, partlength, 668 flags); 669 else 670 rcode = RCODE_TYPE_ERROR; 671 672 data += partlength; 673 length -= partlength; 674 addr += partlength; 675 676 if ((rcode != RCODE_COMPLETE) || !length) 677 break; 678 } 679 } 680 read_unlock(&addr_space_lock); 681 682 if (length && (rcode == RCODE_COMPLETE)) 683 rcode = RCODE_ADDRESS_ERROR; 684 return rcode; 685} 686 687int highlevel_lock(struct hpsb_host *host, int nodeid, quadlet_t *store, 688 u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, 689 u16 flags) 690{ 691 struct hpsb_address_serve *as; 692 int rcode = RCODE_ADDRESS_ERROR; 693 694 read_lock(&addr_space_lock); 695 list_for_each_entry(as, &host->addr_space, host_list) { 696 if (as->start > addr) 697 break; 698 699 if (as->end > addr) { 700 if (as->op->lock) 701 rcode = as->op->lock(host, nodeid, store, addr, 702 data, arg, ext_tcode, 703 flags); 704 else 705 rcode = RCODE_TYPE_ERROR; 706 break; 707 } 708 } 709 read_unlock(&addr_space_lock); 710 return rcode; 711} 712 713int highlevel_lock64(struct hpsb_host *host, int nodeid, octlet_t *store, 714 u64 addr, octlet_t data, octlet_t arg, int ext_tcode, 715 u16 flags) 716{ 717 struct hpsb_address_serve *as; 718 int rcode = RCODE_ADDRESS_ERROR; 719 720 read_lock(&addr_space_lock); 721 722 list_for_each_entry(as, &host->addr_space, host_list) { 723 if (as->start > addr) 724 break; 725 726 if (as->end > addr) { 727 if (as->op->lock64) 728 rcode = as->op->lock64(host, nodeid, store, 729 addr, data, arg, 730 ext_tcode, flags); 731 else 732 rcode = RCODE_TYPE_ERROR; 733 break; 734 } 735 } 736 read_unlock(&addr_space_lock); 737 return rcode; 738} 739