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