Coverage Report

Created: 2017-10-25 09:10

/root/src/xen/xen/drivers/vpci/vpci.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Generic functionality for handling accesses to the PCI configuration space
3
 * from guests.
4
 *
5
 * Copyright (C) 2017 Citrix Systems R&D
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms and conditions of the GNU General Public
9
 * License, version 2, as published by the Free Software Foundation.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public
17
 * License along with this program; If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
#include <xen/sched.h>
21
#include <xen/vpci.h>
22
23
extern vpci_register_init_t *const __start_vpci_array[];
24
extern vpci_register_init_t *const __end_vpci_array[];
25
272
#define NUM_VPCI_INIT (__end_vpci_array - __start_vpci_array)
26
27
/* Internal struct to store the emulated PCI registers. */
28
struct vpci_register {
29
    vpci_read_t *read;
30
    vpci_write_t *write;
31
    unsigned int size;
32
    unsigned int offset;
33
    void *private;
34
    struct list_head node;
35
};
36
37
int __hwdom_init vpci_add_handlers(struct pci_dev *pdev)
38
68
{
39
68
    unsigned int i;
40
68
    int rc = 0;
41
68
42
68
    if ( !has_vpci(pdev->domain) )
43
0
        return 0;
44
68
45
68
    pdev->vpci = xzalloc(struct vpci);
46
68
    if ( !pdev->vpci )
47
0
        return -ENOMEM;
48
68
49
68
    INIT_LIST_HEAD(&pdev->vpci->handlers);
50
68
    spin_lock_init(&pdev->vpci->lock);
51
68
52
272
    for ( i = 0; i < NUM_VPCI_INIT; i++ )
53
204
    {
54
204
        rc = __start_vpci_array[i](pdev);
55
204
        if ( rc )
56
0
            break;
57
204
    }
58
68
59
68
    if ( rc )
60
0
    {
61
0
        while ( !list_empty(&pdev->vpci->handlers) )
62
0
        {
63
0
            struct vpci_register *r = list_first_entry(&pdev->vpci->handlers,
64
0
                                                       struct vpci_register,
65
0
                                                       node);
66
0
67
0
            list_del(&r->node);
68
0
            xfree(r);
69
0
        }
70
0
        xfree(pdev->vpci);
71
0
        pdev->vpci = NULL;
72
0
    }
73
68
74
68
    return rc;
75
68
}
76
77
static int vpci_register_cmp(const struct vpci_register *r1,
78
                             const struct vpci_register *r2)
79
15.5k
{
80
15.5k
    /* Return 0 if registers overlap. */
81
15.5k
    if ( r1->offset < r2->offset + r2->size &&
82
4.78k
         r2->offset < r1->offset + r1->size )
83
1.94k
        return 0;
84
13.6k
    if ( r1->offset < r2->offset )
85
2.84k
        return -1;
86
10.8k
    if ( r1->offset > r2->offset )
87
10.8k
        return 1;
88
10.8k
89
0
    ASSERT_UNREACHABLE();
90
0
    return 0;
91
10.8k
}
92
93
/* Dummy hooks, writes are ignored, reads return 1's */
94
static uint32_t vpci_ignored_read(const struct pci_dev *pdev, unsigned int reg,
95
                                  void *data)
96
0
{
97
0
    return ~(uint32_t)0;
98
0
}
99
100
static void vpci_ignored_write(const struct pci_dev *pdev, unsigned int reg,
101
                               uint32_t val, void *data)
102
0
{
103
0
}
104
105
uint32_t vpci_hw_read16(const struct pci_dev *pdev, unsigned int reg,
106
                        void *data)
107
745
{
108
745
    return pci_conf_read16(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn),
109
745
                           PCI_FUNC(pdev->devfn), reg);
110
745
}
111
112
uint32_t vpci_hw_read32(const struct pci_dev *pdev, unsigned int reg,
113
                        void *data)
114
103
{
115
103
    return pci_conf_read32(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn),
116
103
                           PCI_FUNC(pdev->devfn), reg);
117
103
}
118
119
120
int vpci_add_register(struct vpci *vpci, vpci_read_t *read_handler,
121
                      vpci_write_t *write_handler, unsigned int offset,
122
                      unsigned int size, void *data)
123
198
{
124
198
    struct list_head *prev;
125
198
    struct vpci_register *r;
126
198
127
198
    /* Some sanity checks. */
128
198
    if ( (size != 1 && size != 2 && size != 4) ||
129
198
         offset >= PCI_CFG_SPACE_EXP_SIZE || (offset & (size - 1)) ||
130
198
         (!read_handler && !write_handler) )
131
0
        return -EINVAL;
132
198
133
198
    r = xmalloc(struct vpci_register);
134
198
    if ( !r )
135
0
        return -ENOMEM;
136
198
137
198
    r->read = read_handler ?: vpci_ignored_read;
138
7
    r->write = write_handler ?: vpci_ignored_write;
139
198
    r->size = size;
140
198
    r->offset = offset;
141
198
    r->private = data;
142
198
143
198
    spin_lock(&vpci->lock);
144
198
145
198
    /* The list of handlers must be kept sorted at all times. */
146
198
    list_for_each ( prev, &vpci->handlers )
147
496
    {
148
496
        const struct vpci_register *this =
149
496
            list_entry(prev, const struct vpci_register, node);
150
496
        int cmp = vpci_register_cmp(r, this);
151
496
152
496
        if ( cmp < 0 )
153
56
            break;
154
440
        if ( cmp == 0 )
155
0
        {
156
0
            spin_unlock(&vpci->lock);
157
0
            xfree(r);
158
0
            return -EEXIST;
159
0
        }
160
440
    }
161
198
162
198
    list_add_tail(&r->node, prev);
163
198
    spin_unlock(&vpci->lock);
164
198
165
198
    return 0;
166
198
}
167
168
int vpci_remove_register(struct vpci *vpci, unsigned int offset,
169
                         unsigned int size)
170
0
{
171
0
    const struct vpci_register r = { .offset = offset, .size = size };
172
0
    struct vpci_register *rm;
173
0
174
0
    spin_lock(&vpci->lock);
175
0
    list_for_each_entry ( rm, &vpci->handlers, node )
176
0
    {
177
0
        int cmp = vpci_register_cmp(&r, rm);
178
0
179
0
        /*
180
0
         * NB: do not use a switch so that we can use break to
181
0
         * get out of the list loop earlier if required.
182
0
         */
183
0
        if ( !cmp && rm->offset == offset && rm->size == size )
184
0
        {
185
0
            list_del(&rm->node);
186
0
            spin_unlock(&vpci->lock);
187
0
            xfree(rm);
188
0
            return 0;
189
0
        }
190
0
        if ( cmp <= 0 )
191
0
            break;
192
0
    }
193
0
    spin_unlock(&vpci->lock);
194
0
195
0
    return -ENOENT;
196
0
}
197
198
/* Wrappers for performing reads/writes to the underlying hardware. */
199
static uint32_t vpci_read_hw(pci_sbdf_t sbdf, unsigned int reg,
200
                             unsigned int size)
201
54.9k
{
202
54.9k
    uint32_t data;
203
54.9k
204
54.9k
    switch ( size )
205
54.9k
    {
206
49.8k
    case 4:
207
49.8k
        data = pci_conf_read32(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
208
49.8k
        break;
209
0
    case 3:
210
0
        /*
211
0
         * This is possible because a 4byte read can have 1byte trapped and
212
0
         * the rest passed-through.
213
0
         */
214
0
        if ( reg & 1 )
215
0
        {
216
0
            data = pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
217
0
                                  reg);
218
0
            data |= pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
219
0
                                    reg + 1) << 8;
220
0
        }
221
0
        else
222
0
        {
223
0
            data = pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
224
0
                                   reg);
225
0
            data |= pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
226
0
                                   reg + 2) << 16;
227
0
        }
228
0
        break;
229
2.09k
    case 2:
230
2.09k
        data = pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
231
2.09k
        break;
232
3.05k
    case 1:
233
3.05k
        data = pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
234
3.05k
        break;
235
0
    default:
236
0
        ASSERT_UNREACHABLE();
237
0
        data = ~(uint32_t)0;
238
0
        break;
239
54.9k
    }
240
54.9k
241
54.9k
    return data;
242
54.9k
}
243
244
static void vpci_write_hw(pci_sbdf_t sbdf, unsigned int reg, unsigned int size,
245
                          uint32_t data)
246
1.43k
{
247
1.43k
    switch ( size )
248
1.43k
    {
249
688
    case 4:
250
688
        pci_conf_write32(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
251
688
        break;
252
0
    case 3:
253
0
        /*
254
0
         * This is possible because a 4byte write can have 1byte trapped and
255
0
         * the rest passed-through.
256
0
         */
257
0
        if ( reg & 1 )
258
0
        {
259
0
            pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg,
260
0
                            data);
261
0
            pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg + 1,
262
0
                             data >> 8);
263
0
        }
264
0
        else
265
0
        {
266
0
            pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg,
267
0
                             data);
268
0
            pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg + 2,
269
0
                            data >> 16);
270
0
        }
271
0
        break;
272
141
    case 2:
273
141
        pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
274
141
        break;
275
607
    case 1:
276
607
        pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
277
607
        break;
278
0
    default:
279
0
        ASSERT_UNREACHABLE();
280
0
        break;
281
1.43k
    }
282
1.43k
}
283
284
/*
285
 * Merge new data into a partial result.
286
 *
287
 * Copy the value found in 'new' from [0, size) left shifted by
288
 * 'offset' into 'data'. Note that both 'size' and 'offset' are
289
 * in byte units.
290
 */
291
static uint32_t merge_result(uint32_t data, uint32_t new, unsigned int size,
292
                             unsigned int offset)
293
6.41k
{
294
6.41k
    uint32_t mask = 0xffffffff >> (32 - 8 * size);
295
6.41k
296
6.41k
    return (data & ~(mask << (offset * 8))) | ((new & mask) << (offset * 8));
297
6.41k
}
298
299
uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size)
300
55.8k
{
301
55.8k
    const struct domain *d = current->domain;
302
55.8k
    const struct pci_dev *pdev;
303
55.8k
    const struct vpci_register *r;
304
55.8k
    unsigned int data_offset = 0;
305
55.8k
    uint32_t data = ~(uint32_t)0;
306
55.8k
307
55.8k
    /* Find the PCI dev matching the address. */
308
55.8k
    pdev = pci_get_pdev_by_domain(d, sbdf.seg, sbdf.bus, sbdf.extfunc);
309
55.8k
    if ( !pdev )
310
49.4k
        return vpci_read_hw(sbdf, reg, size);
311
55.8k
312
6.41k
    spin_lock(&pdev->vpci->lock);
313
6.41k
314
6.41k
    /* Read from the hardware or the emulated register handlers. */
315
6.41k
    list_for_each_entry ( r, &pdev->vpci->handlers, node )
316
10.8k
    {
317
10.8k
        const struct vpci_register emu = {
318
10.8k
            .offset = reg + data_offset,
319
10.8k
            .size = size - data_offset
320
10.8k
        };
321
10.8k
        int cmp = vpci_register_cmp(&emu, r);
322
10.8k
        uint32_t val;
323
10.8k
        unsigned int read_size;
324
10.8k
325
10.8k
        if ( cmp < 0 )
326
2.36k
            break;
327
8.45k
        if ( cmp > 0 )
328
7.57k
            continue;
329
8.45k
330
878
        if ( emu.offset < r->offset )
331
0
        {
332
0
            /* Heading gap, read partial content from hardware. */
333
0
            read_size = r->offset - emu.offset;
334
0
            val = vpci_read_hw(sbdf, emu.offset, read_size);
335
0
            data = merge_result(data, val, read_size, data_offset);
336
0
            data_offset += read_size;
337
0
        }
338
878
339
878
        val = r->read(pdev, r->offset, r->private);
340
878
341
878
        /* Check if the read is in the middle of a register. */
342
878
        if ( r->offset < emu.offset )
343
0
            val >>= (emu.offset - r->offset) * 8;
344
878
345
878
        /* Find the intersection size between the two sets. */
346
878
        read_size = min(emu.offset + emu.size, r->offset + r->size) -
347
878
                    max(emu.offset, r->offset);
348
878
        /* Merge the emulated data into the native read value. */
349
878
        data = merge_result(data, val, read_size, data_offset);
350
878
        data_offset += read_size;
351
878
        if ( data_offset == size )
352
878
            break;
353
0
        ASSERT(data_offset < size);
354
0
    }
355
6.41k
356
6.41k
    if ( data_offset < size )
357
5.53k
    {
358
5.53k
        /* Tailing gap, read the remaining. */
359
5.53k
        uint32_t tmp_data = vpci_read_hw(sbdf, reg + data_offset,
360
5.53k
                                         size - data_offset);
361
5.53k
362
5.53k
        data = merge_result(data, tmp_data, size - data_offset, data_offset);
363
5.53k
    }
364
6.41k
    spin_unlock(&pdev->vpci->lock);
365
6.41k
366
6.41k
    return data & (0xffffffff >> (32 - 8 * size));
367
55.8k
}
368
369
/*
370
 * Perform a maybe partial write to a register.
371
 *
372
 * Note that this will only work for simple registers, if Xen needs to
373
 * trap accesses to rw1c registers (like the status PCI header register)
374
 * the logic in vpci_write will have to be expanded in order to correctly
375
 * deal with them.
376
 */
377
static void vpci_write_helper(const struct pci_dev *pdev,
378
                              const struct vpci_register *r, unsigned int size,
379
                              unsigned int offset, uint32_t data)
380
1.06k
{
381
1.06k
    ASSERT(size <= r->size);
382
1.06k
383
1.06k
    if ( size != r->size )
384
0
    {
385
0
        uint32_t val;
386
0
387
0
        val = r->read(pdev, r->offset, r->private);
388
0
        data = merge_result(val, data, size, offset);
389
0
    }
390
1.06k
391
1.06k
    r->write(pdev, r->offset, data & (0xffffffff >> (32 - 8 * r->size)),
392
1.06k
             r->private);
393
1.06k
}
394
395
void vpci_write(pci_sbdf_t sbdf, unsigned int reg, unsigned int size,
396
                uint32_t data)
397
2.50k
{
398
2.50k
    const struct domain *d = current->domain;
399
2.50k
    const struct pci_dev *pdev;
400
2.50k
    const struct vpci_register *r;
401
2.50k
    unsigned int data_offset = 0;
402
2.50k
403
2.50k
    /*
404
2.50k
     * Find the PCI dev matching the address.
405
2.50k
     * Passthrough everything that's not trapped.
406
2.50k
     */
407
2.50k
    pdev = pci_get_pdev_by_domain(d, sbdf.seg, sbdf.bus, sbdf.extfunc);
408
2.50k
    if ( !pdev )
409
0
    {
410
0
        vpci_write_hw(sbdf, reg, size, data);
411
0
        return;
412
0
    }
413
2.50k
414
2.50k
    spin_lock(&pdev->vpci->lock);
415
2.50k
416
2.50k
    /* Write the value to the hardware or emulated registers. */
417
2.50k
    list_for_each_entry ( r, &pdev->vpci->handlers, node )
418
4.27k
    {
419
4.27k
        const struct vpci_register emu = {
420
4.27k
            .offset = reg + data_offset,
421
4.27k
            .size = size - data_offset
422
4.27k
        };
423
4.27k
        int cmp = vpci_register_cmp(&emu, r);
424
4.27k
        unsigned int write_size;
425
4.27k
426
4.27k
        if ( cmp < 0 )
427
422
            break;
428
3.85k
        if ( cmp > 0 )
429
2.78k
            continue;
430
3.85k
431
1.06k
        if ( emu.offset < r->offset )
432
0
        {
433
0
            /* Heading gap, write partial content to hardware. */
434
0
            vpci_write_hw(sbdf, emu.offset, r->offset - emu.offset,
435
0
                          data >> (data_offset * 8));
436
0
            data_offset += r->offset - emu.offset;
437
0
        }
438
1.06k
439
1.06k
        /* Find the intersection size between the two sets. */
440
1.06k
        write_size = min(emu.offset + emu.size, r->offset + r->size) -
441
1.06k
                     max(emu.offset, r->offset);
442
1.06k
        vpci_write_helper(pdev, r, write_size, reg + data_offset - r->offset,
443
1.06k
                          data >> (data_offset * 8));
444
1.06k
        data_offset += write_size;
445
1.06k
        if ( data_offset == size )
446
1.06k
            break;
447
0
        ASSERT(data_offset < size);
448
0
    }
449
2.50k
450
2.50k
    if ( data_offset < size )
451
2.50k
        /* Tailing gap, write the remaining. */
452
1.43k
        vpci_write_hw(sbdf, reg + data_offset, size - data_offset,
453
1.43k
                      data >> (data_offset * 8));
454
2.50k
455
2.50k
    spin_unlock(&pdev->vpci->lock);
456
2.50k
}
457
458
/*
459
 * Local variables:
460
 * mode: C
461
 * c-file-style: "BSD"
462
 * c-basic-offset: 4
463
 * tab-width: 4
464
 * indent-tabs-mode: nil
465
 * End:
466
 */