1/* -*- linux-c -*- 2 * 3 * iSeries Virtual I/O Message Path code 4 * 5 * Authors: Dave Boutcher <boutcher@us.ibm.com> 6 * Ryan Arnold <ryanarn@us.ibm.com> 7 * Colin Devilbiss <devilbis@us.ibm.com> 8 * 9 * (C) Copyright 2000-2005 IBM Corporation 10 * 11 * This code is used by the iSeries virtual disk, cd, 12 * tape, and console to communicate with OS/400 in another 13 * partition. 14 * 15 * This program is free software; you can redistribute it and/or 16 * modify it under the terms of the GNU General Public License as 17 * published by the Free Software Foundation; either version 2 of the 18 * License, or (at your option) anyu later version. 19 * 20 * This program is distributed in the hope that it will be useful, but 21 * WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 23 * General Public License for more details. 24 * 25 * You should have received a copy of the GNU General Public License 26 * along with this program; if not, write to the Free Software Foundation, 27 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 28 * 29 */ 30#include <linux/module.h> 31#include <linux/kernel.h> 32#include <linux/errno.h> 33#include <linux/vmalloc.h> 34#include <linux/string.h> 35#include <linux/proc_fs.h> 36#include <linux/dma-mapping.h> 37#include <linux/wait.h> 38#include <linux/seq_file.h> 39#include <linux/interrupt.h> 40#include <linux/completion.h> 41 42#include <asm/system.h> 43#include <asm/uaccess.h> 44#include <asm/prom.h> 45#include <asm/firmware.h> 46#include <asm/iseries/hv_types.h> 47#include <asm/iseries/hv_lp_event.h> 48#include <asm/iseries/hv_lp_config.h> 49#include <asm/iseries/mf.h> 50#include <asm/iseries/vio.h> 51 52/* Status of the path to each other partition in the system. 53 * This is overkill, since we will only ever establish connections 54 * to our hosting partition and the primary partition on the system. 55 * But this allows for other support in the future. 56 */ 57static struct viopathStatus { 58 int isOpen; /* Did we open the path? */ 59 int isActive; /* Do we have a mon msg outstanding */ 60 int users[VIO_MAX_SUBTYPES]; 61 HvLpInstanceId mSourceInst; 62 HvLpInstanceId mTargetInst; 63 int numberAllocated; 64} viopathStatus[HVMAXARCHITECTEDLPS]; 65 66static DEFINE_SPINLOCK(statuslock); 67 68/* 69 * For each kind of event we allocate a buffer that is 70 * guaranteed not to cross a page boundary 71 */ 72static unsigned char event_buffer[VIO_MAX_SUBTYPES * 256] 73 __attribute__((__aligned__(4096))); 74static atomic_t event_buffer_available[VIO_MAX_SUBTYPES]; 75static int event_buffer_initialised; 76 77static void handleMonitorEvent(struct HvLpEvent *event); 78 79/* 80 * We use this structure to handle asynchronous responses. The caller 81 * blocks on the semaphore and the handler posts the semaphore. However, 82 * if system_state is not SYSTEM_RUNNING, then wait_atomic is used ... 83 */ 84struct alloc_parms { 85 struct completion done; 86 int number; 87 atomic_t wait_atomic; 88 int used_wait_atomic; 89}; 90 91/* Put a sequence number in each mon msg. The value is not 92 * important. Start at something other than 0 just for 93 * readability. wrapping this is ok. 94 */ 95static u8 viomonseq = 22; 96 97/* Our hosting logical partition. We get this at startup 98 * time, and different modules access this variable directly. 99 */ 100HvLpIndex viopath_hostLp = HvLpIndexInvalid; 101EXPORT_SYMBOL(viopath_hostLp); 102HvLpIndex viopath_ourLp = HvLpIndexInvalid; 103EXPORT_SYMBOL(viopath_ourLp); 104 105/* For each kind of incoming event we set a pointer to a 106 * routine to call. 107 */ 108static vio_event_handler_t *vio_handler[VIO_MAX_SUBTYPES]; 109 110#define VIOPATH_KERN_WARN KERN_WARNING "viopath: " 111#define VIOPATH_KERN_INFO KERN_INFO "viopath: " 112 113static int proc_viopath_show(struct seq_file *m, void *v) 114{ 115 char *buf; 116 u16 vlanMap; 117 dma_addr_t handle; 118 HvLpEvent_Rc hvrc; 119 DECLARE_COMPLETION(done); 120 struct device_node *node; 121 const char *sysid; 122 123 buf = kzalloc(HW_PAGE_SIZE, GFP_KERNEL); 124 if (!buf) 125 return 0; 126 127 handle = dma_map_single(iSeries_vio_dev, buf, HW_PAGE_SIZE, 128 DMA_FROM_DEVICE); 129 130 hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, 131 HvLpEvent_Type_VirtualIo, 132 viomajorsubtype_config | vioconfigget, 133 HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, 134 viopath_sourceinst(viopath_hostLp), 135 viopath_targetinst(viopath_hostLp), 136 (u64)(unsigned long)&done, VIOVERSION << 16, 137 ((u64)handle) << 32, HW_PAGE_SIZE, 0, 0); 138 139 if (hvrc != HvLpEvent_Rc_Good) 140 printk(VIOPATH_KERN_WARN "hv error on op %d\n", (int)hvrc); 141 142 wait_for_completion(&done); 143 144 vlanMap = HvLpConfig_getVirtualLanIndexMap(); 145 146 buf[HW_PAGE_SIZE-1] = '\0'; 147 seq_printf(m, "%s", buf); 148 149 dma_unmap_single(iSeries_vio_dev, handle, HW_PAGE_SIZE, 150 DMA_FROM_DEVICE); 151 kfree(buf); 152 153 seq_printf(m, "AVAILABLE_VETH=%x\n", vlanMap); 154 155 node = of_find_node_by_path("/"); 156 sysid = NULL; 157 if (node != NULL) 158 sysid = of_get_property(node, "system-id", NULL); 159 160 if (sysid == NULL) 161 seq_printf(m, "SRLNBR=<UNKNOWN>\n"); 162 else 163 /* Skip "IBM," on front of serial number, see dt.c */ 164 seq_printf(m, "SRLNBR=%s\n", sysid + 4); 165 166 of_node_put(node); 167 168 return 0; 169} 170 171static int proc_viopath_open(struct inode *inode, struct file *file) 172{ 173 return single_open(file, proc_viopath_show, NULL); 174} 175 176static const struct file_operations proc_viopath_operations = { 177 .open = proc_viopath_open, 178 .read = seq_read, 179 .llseek = seq_lseek, 180 .release = single_release, 181}; 182 183static int __init vio_proc_init(void) 184{ 185 struct proc_dir_entry *e; 186 187 if (!firmware_has_feature(FW_FEATURE_ISERIES)) 188 return 0; 189 190 e = create_proc_entry("iSeries/config", 0, NULL); 191 if (e) 192 e->proc_fops = &proc_viopath_operations; 193 194 return 0; 195} 196__initcall(vio_proc_init); 197 198/* See if a given LP is active. Allow for invalid lps to be passed in 199 * and just return invalid 200 */ 201int viopath_isactive(HvLpIndex lp) 202{ 203 if (lp == HvLpIndexInvalid) 204 return 0; 205 if (lp < HVMAXARCHITECTEDLPS) 206 return viopathStatus[lp].isActive; 207 else 208 return 0; 209} 210EXPORT_SYMBOL(viopath_isactive); 211 212/* 213 * We cache the source and target instance ids for each 214 * partition. 215 */ 216HvLpInstanceId viopath_sourceinst(HvLpIndex lp) 217{ 218 return viopathStatus[lp].mSourceInst; 219} 220EXPORT_SYMBOL(viopath_sourceinst); 221 222HvLpInstanceId viopath_targetinst(HvLpIndex lp) 223{ 224 return viopathStatus[lp].mTargetInst; 225} 226EXPORT_SYMBOL(viopath_targetinst); 227 228/* 229 * Send a monitor message. This is a message with the acknowledge 230 * bit on that the other side will NOT explicitly acknowledge. When 231 * the other side goes down, the hypervisor will acknowledge any 232 * outstanding messages....so we will know when the other side dies. 233 */ 234static void sendMonMsg(HvLpIndex remoteLp) 235{ 236 HvLpEvent_Rc hvrc; 237 238 viopathStatus[remoteLp].mSourceInst = 239 HvCallEvent_getSourceLpInstanceId(remoteLp, 240 HvLpEvent_Type_VirtualIo); 241 viopathStatus[remoteLp].mTargetInst = 242 HvCallEvent_getTargetLpInstanceId(remoteLp, 243 HvLpEvent_Type_VirtualIo); 244 245 /* 246 * Deliberately ignore the return code here. if we call this 247 * more than once, we don't care. 248 */ 249 vio_setHandler(viomajorsubtype_monitor, handleMonitorEvent); 250 251 hvrc = HvCallEvent_signalLpEventFast(remoteLp, HvLpEvent_Type_VirtualIo, 252 viomajorsubtype_monitor, HvLpEvent_AckInd_DoAck, 253 HvLpEvent_AckType_DeferredAck, 254 viopathStatus[remoteLp].mSourceInst, 255 viopathStatus[remoteLp].mTargetInst, 256 viomonseq++, 0, 0, 0, 0, 0); 257 258 if (hvrc == HvLpEvent_Rc_Good) 259 viopathStatus[remoteLp].isActive = 1; 260 else { 261 printk(VIOPATH_KERN_WARN "could not connect to partition %d\n", 262 remoteLp); 263 viopathStatus[remoteLp].isActive = 0; 264 } 265} 266 267static void handleMonitorEvent(struct HvLpEvent *event) 268{ 269 HvLpIndex remoteLp; 270 int i; 271 272 /* 273 * This handler is _also_ called as part of the loop 274 * at the end of this routine, so it must be able to 275 * ignore NULL events... 276 */ 277 if (!event) 278 return; 279 280 /* 281 * First see if this is just a normal monitor message from the 282 * other partition 283 */ 284 if (hvlpevent_is_int(event)) { 285 remoteLp = event->xSourceLp; 286 if (!viopathStatus[remoteLp].isActive) 287 sendMonMsg(remoteLp); 288 return; 289 } 290 291 /* 292 * This path is for an acknowledgement; the other partition 293 * died 294 */ 295 remoteLp = event->xTargetLp; 296 if ((event->xSourceInstanceId != viopathStatus[remoteLp].mSourceInst) || 297 (event->xTargetInstanceId != viopathStatus[remoteLp].mTargetInst)) { 298 printk(VIOPATH_KERN_WARN "ignoring ack....mismatched instances\n"); 299 return; 300 } 301 302 printk(VIOPATH_KERN_WARN "partition %d ended\n", remoteLp); 303 304 viopathStatus[remoteLp].isActive = 0; 305 306 /* 307 * For each active handler, pass them a NULL 308 * message to indicate that the other partition 309 * died 310 */ 311 for (i = 0; i < VIO_MAX_SUBTYPES; i++) { 312 if (vio_handler[i] != NULL) 313 (*vio_handler[i])(NULL); 314 } 315} 316 317int vio_setHandler(int subtype, vio_event_handler_t *beh) 318{ 319 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 320 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) 321 return -EINVAL; 322 if (vio_handler[subtype] != NULL) 323 return -EBUSY; 324 vio_handler[subtype] = beh; 325 return 0; 326} 327EXPORT_SYMBOL(vio_setHandler); 328 329int vio_clearHandler(int subtype) 330{ 331 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 332 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) 333 return -EINVAL; 334 if (vio_handler[subtype] == NULL) 335 return -EAGAIN; 336 vio_handler[subtype] = NULL; 337 return 0; 338} 339EXPORT_SYMBOL(vio_clearHandler); 340 341static void handleConfig(struct HvLpEvent *event) 342{ 343 if (!event) 344 return; 345 if (hvlpevent_is_int(event)) { 346 printk(VIOPATH_KERN_WARN 347 "unexpected config request from partition %d", 348 event->xSourceLp); 349 350 if (hvlpevent_need_ack(event)) { 351 event->xRc = HvLpEvent_Rc_InvalidSubtype; 352 HvCallEvent_ackLpEvent(event); 353 } 354 return; 355 } 356 357 complete((struct completion *)event->xCorrelationToken); 358} 359 360/* 361 * Initialization of the hosting partition 362 */ 363void vio_set_hostlp(void) 364{ 365 /* 366 * If this has already been set then we DON'T want to either change 367 * it or re-register the proc file system 368 */ 369 if (viopath_hostLp != HvLpIndexInvalid) 370 return; 371 372 /* 373 * Figure out our hosting partition. This isn't allowed to change 374 * while we're active 375 */ 376 viopath_ourLp = HvLpConfig_getLpIndex(); 377 viopath_hostLp = HvLpConfig_getHostingLpIndex(viopath_ourLp); 378 379 if (viopath_hostLp != HvLpIndexInvalid) 380 vio_setHandler(viomajorsubtype_config, handleConfig); 381} 382EXPORT_SYMBOL(vio_set_hostlp); 383 384static void vio_handleEvent(struct HvLpEvent *event) 385{ 386 HvLpIndex remoteLp; 387 int subtype = (event->xSubtype & VIOMAJOR_SUBTYPE_MASK) 388 >> VIOMAJOR_SUBTYPE_SHIFT; 389 390 if (hvlpevent_is_int(event)) { 391 remoteLp = event->xSourceLp; 392 /* 393 * The isActive is checked because if the hosting partition 394 * went down and came back up it would not be active but it 395 * would have different source and target instances, in which 396 * case we'd want to reset them. This case really protects 397 * against an unauthorized active partition sending interrupts 398 * or acks to this linux partition. 399 */ 400 if (viopathStatus[remoteLp].isActive 401 && (event->xSourceInstanceId != 402 viopathStatus[remoteLp].mTargetInst)) { 403 printk(VIOPATH_KERN_WARN 404 "message from invalid partition. " 405 "int msg rcvd, source inst (%d) doesnt match (%d)\n", 406 viopathStatus[remoteLp].mTargetInst, 407 event->xSourceInstanceId); 408 return; 409 } 410 411 if (viopathStatus[remoteLp].isActive 412 && (event->xTargetInstanceId != 413 viopathStatus[remoteLp].mSourceInst)) { 414 printk(VIOPATH_KERN_WARN 415 "message from invalid partition. " 416 "int msg rcvd, target inst (%d) doesnt match (%d)\n", 417 viopathStatus[remoteLp].mSourceInst, 418 event->xTargetInstanceId); 419 return; 420 } 421 } else { 422 remoteLp = event->xTargetLp; 423 if (event->xSourceInstanceId != 424 viopathStatus[remoteLp].mSourceInst) { 425 printk(VIOPATH_KERN_WARN 426 "message from invalid partition. " 427 "ack msg rcvd, source inst (%d) doesnt match (%d)\n", 428 viopathStatus[remoteLp].mSourceInst, 429 event->xSourceInstanceId); 430 return; 431 } 432 433 if (event->xTargetInstanceId != 434 viopathStatus[remoteLp].mTargetInst) { 435 printk(VIOPATH_KERN_WARN 436 "message from invalid partition. " 437 "viopath: ack msg rcvd, target inst (%d) doesnt match (%d)\n", 438 viopathStatus[remoteLp].mTargetInst, 439 event->xTargetInstanceId); 440 return; 441 } 442 } 443 444 if (vio_handler[subtype] == NULL) { 445 printk(VIOPATH_KERN_WARN 446 "unexpected virtual io event subtype %d from partition %d\n", 447 event->xSubtype, remoteLp); 448 /* No handler. Ack if necessary */ 449 if (hvlpevent_is_int(event) && hvlpevent_need_ack(event)) { 450 event->xRc = HvLpEvent_Rc_InvalidSubtype; 451 HvCallEvent_ackLpEvent(event); 452 } 453 return; 454 } 455 456 /* This innocuous little line is where all the real work happens */ 457 (*vio_handler[subtype])(event); 458} 459 460static void viopath_donealloc(void *parm, int number) 461{ 462 struct alloc_parms *parmsp = parm; 463 464 parmsp->number = number; 465 if (parmsp->used_wait_atomic) 466 atomic_set(&parmsp->wait_atomic, 0); 467 else 468 complete(&parmsp->done); 469} 470 471static int allocateEvents(HvLpIndex remoteLp, int numEvents) 472{ 473 struct alloc_parms parms; 474 475 if (system_state != SYSTEM_RUNNING) { 476 parms.used_wait_atomic = 1; 477 atomic_set(&parms.wait_atomic, 1); 478 } else { 479 parms.used_wait_atomic = 0; 480 init_completion(&parms.done); 481 } 482 mf_allocate_lp_events(remoteLp, HvLpEvent_Type_VirtualIo, 250, /* It would be nice to put a real number here! */ 483 numEvents, &viopath_donealloc, &parms); 484 if (system_state != SYSTEM_RUNNING) { 485 while (atomic_read(&parms.wait_atomic)) 486 mb(); 487 } else 488 wait_for_completion(&parms.done); 489 return parms.number; 490} 491 492int viopath_open(HvLpIndex remoteLp, int subtype, int numReq) 493{ 494 int i; 495 unsigned long flags; 496 int tempNumAllocated; 497 498 if ((remoteLp >= HVMAXARCHITECTEDLPS) || (remoteLp == HvLpIndexInvalid)) 499 return -EINVAL; 500 501 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 502 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) 503 return -EINVAL; 504 505 spin_lock_irqsave(&statuslock, flags); 506 507 if (!event_buffer_initialised) { 508 for (i = 0; i < VIO_MAX_SUBTYPES; i++) 509 atomic_set(&event_buffer_available[i], 1); 510 event_buffer_initialised = 1; 511 } 512 513 viopathStatus[remoteLp].users[subtype]++; 514 515 if (!viopathStatus[remoteLp].isOpen) { 516 viopathStatus[remoteLp].isOpen = 1; 517 HvCallEvent_openLpEventPath(remoteLp, HvLpEvent_Type_VirtualIo); 518 519 /* 520 * Don't hold the spinlock during an operation that 521 * can sleep. 522 */ 523 spin_unlock_irqrestore(&statuslock, flags); 524 tempNumAllocated = allocateEvents(remoteLp, 1); 525 spin_lock_irqsave(&statuslock, flags); 526 527 viopathStatus[remoteLp].numberAllocated += tempNumAllocated; 528 529 if (viopathStatus[remoteLp].numberAllocated == 0) { 530 HvCallEvent_closeLpEventPath(remoteLp, 531 HvLpEvent_Type_VirtualIo); 532 533 spin_unlock_irqrestore(&statuslock, flags); 534 return -ENOMEM; 535 } 536 537 viopathStatus[remoteLp].mSourceInst = 538 HvCallEvent_getSourceLpInstanceId(remoteLp, 539 HvLpEvent_Type_VirtualIo); 540 viopathStatus[remoteLp].mTargetInst = 541 HvCallEvent_getTargetLpInstanceId(remoteLp, 542 HvLpEvent_Type_VirtualIo); 543 HvLpEvent_registerHandler(HvLpEvent_Type_VirtualIo, 544 &vio_handleEvent); 545 sendMonMsg(remoteLp); 546 printk(VIOPATH_KERN_INFO "opening connection to partition %d, " 547 "setting sinst %d, tinst %d\n", 548 remoteLp, viopathStatus[remoteLp].mSourceInst, 549 viopathStatus[remoteLp].mTargetInst); 550 } 551 552 spin_unlock_irqrestore(&statuslock, flags); 553 tempNumAllocated = allocateEvents(remoteLp, numReq); 554 spin_lock_irqsave(&statuslock, flags); 555 viopathStatus[remoteLp].numberAllocated += tempNumAllocated; 556 spin_unlock_irqrestore(&statuslock, flags); 557 558 return 0; 559} 560EXPORT_SYMBOL(viopath_open); 561 562int viopath_close(HvLpIndex remoteLp, int subtype, int numReq) 563{ 564 unsigned long flags; 565 int i; 566 int numOpen; 567 struct alloc_parms parms; 568 569 if ((remoteLp >= HVMAXARCHITECTEDLPS) || (remoteLp == HvLpIndexInvalid)) 570 return -EINVAL; 571 572 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 573 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) 574 return -EINVAL; 575 576 spin_lock_irqsave(&statuslock, flags); 577 /* 578 * If the viopath_close somehow gets called before a 579 * viopath_open it could decrement to -1 which is a non 580 * recoverable state so we'll prevent this from 581 * happening. 582 */ 583 if (viopathStatus[remoteLp].users[subtype] > 0) 584 viopathStatus[remoteLp].users[subtype]--; 585 586 spin_unlock_irqrestore(&statuslock, flags); 587 588 parms.used_wait_atomic = 0; 589 init_completion(&parms.done); 590 mf_deallocate_lp_events(remoteLp, HvLpEvent_Type_VirtualIo, 591 numReq, &viopath_donealloc, &parms); 592 wait_for_completion(&parms.done); 593 594 spin_lock_irqsave(&statuslock, flags); 595 for (i = 0, numOpen = 0; i < VIO_MAX_SUBTYPES; i++) 596 numOpen += viopathStatus[remoteLp].users[i]; 597 598 if ((viopathStatus[remoteLp].isOpen) && (numOpen == 0)) { 599 printk(VIOPATH_KERN_INFO "closing connection to partition %d", 600 remoteLp); 601 602 HvCallEvent_closeLpEventPath(remoteLp, 603 HvLpEvent_Type_VirtualIo); 604 viopathStatus[remoteLp].isOpen = 0; 605 viopathStatus[remoteLp].isActive = 0; 606 607 for (i = 0; i < VIO_MAX_SUBTYPES; i++) 608 atomic_set(&event_buffer_available[i], 0); 609 event_buffer_initialised = 0; 610 } 611 spin_unlock_irqrestore(&statuslock, flags); 612 return 0; 613} 614EXPORT_SYMBOL(viopath_close); 615 616void *vio_get_event_buffer(int subtype) 617{ 618 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 619 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) 620 return NULL; 621 622 if (atomic_dec_if_positive(&event_buffer_available[subtype]) == 0) 623 return &event_buffer[subtype * 256]; 624 else 625 return NULL; 626} 627EXPORT_SYMBOL(vio_get_event_buffer); 628 629void vio_free_event_buffer(int subtype, void *buffer) 630{ 631 subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; 632 if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) { 633 printk(VIOPATH_KERN_WARN 634 "unexpected subtype %d freeing event buffer\n", subtype); 635 return; 636 } 637 638 if (atomic_read(&event_buffer_available[subtype]) != 0) { 639 printk(VIOPATH_KERN_WARN 640 "freeing unallocated event buffer, subtype %d\n", 641 subtype); 642 return; 643 } 644 645 if (buffer != &event_buffer[subtype * 256]) { 646 printk(VIOPATH_KERN_WARN 647 "freeing invalid event buffer, subtype %d\n", subtype); 648 } 649 650 atomic_set(&event_buffer_available[subtype], 1); 651} 652EXPORT_SYMBOL(vio_free_event_buffer); 653 654static const struct vio_error_entry vio_no_error = 655 { 0, 0, "Non-VIO Error" }; 656static const struct vio_error_entry vio_unknown_error = 657 { 0, EIO, "Unknown Error" }; 658 659static const struct vio_error_entry vio_default_errors[] = { 660 {0x0001, EIO, "No Connection"}, 661 {0x0002, EIO, "No Receiver"}, 662 {0x0003, EIO, "No Buffer Available"}, 663 {0x0004, EBADRQC, "Invalid Message Type"}, 664 {0x0000, 0, NULL}, 665}; 666 667const struct vio_error_entry *vio_lookup_rc( 668 const struct vio_error_entry *local_table, u16 rc) 669{ 670 const struct vio_error_entry *cur; 671 672 if (!rc) 673 return &vio_no_error; 674 if (local_table) 675 for (cur = local_table; cur->rc; ++cur) 676 if (cur->rc == rc) 677 return cur; 678 for (cur = vio_default_errors; cur->rc; ++cur) 679 if (cur->rc == rc) 680 return cur; 681 return &vio_unknown_error; 682} 683EXPORT_SYMBOL(vio_lookup_rc); 684