0c7cf61fa3cf3c241a2b72dd3c53d0c5a8be1406
[platform/upstream/libpciaccess.git] / src / freebsd_pci.c
1 /*
2  * (C) Copyright Eric Anholt 2006
3  * (C) Copyright IBM Corporation 2006
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * on the rights to use, copy, modify, merge, publish, distribute, sub
10  * license, and/or sell copies of the Software, and to permit persons to whom
11  * the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.  IN NO EVENT SHALL
20  * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24  */
25
26 /**
27  * \file freebsd_pci.c
28  *
29  * Access the kernel PCI support using /dev/pci's ioctl and mmap interface.
30  *
31  * \author Eric Anholt <eric@anholt.net>
32  */
33
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/param.h>
42 #include <sys/pciio.h>
43 #include <sys/mman.h>
44 #include <sys/memrange.h>
45
46 #if __FreeBSD_version >= 700053
47 #define DOMAIN_SUPPORT 1
48 #else
49 #define DOMAIN_SUPPORT 0
50 #endif
51
52 #include "pciaccess.h"
53 #include "pciaccess_private.h"
54
55 #define PCIC_DISPLAY    0x03
56 #define PCIS_DISPLAY_VGA        0x00
57 #define PCIS_DISPLAY_XGA        0x01
58 #define PCIS_DISPLAY_3D         0x02
59 #define PCIS_DISPLAY_OTHER      0x80
60
61 /**
62  * FreeBSD private pci_system structure that extends the base pci_system
63  * structure.
64  *
65  * It is initialized once and used as a global, just as pci_system is used.
66  */
67 struct freebsd_pci_system {
68     /* This must be the first entry in the structure, as pci_system_cleanup()
69      * frees pci_sys.
70      */
71     struct pci_system pci_sys;
72
73     int pcidev; /**< fd for /dev/pci */
74 } *freebsd_pci_sys;
75
76 /**
77  * Map a memory region for a device using /dev/mem.
78  * 
79  * \param dev   Device whose memory region is to be mapped.
80  * \param map   Parameters of the mapping that is to be created.
81  * 
82  * \return
83  * Zero on success or an \c errno value on failure.
84  */
85 static int
86 pci_device_freebsd_map_range(struct pci_device *dev,
87                              struct pci_device_mapping *map)
88 {
89     const int prot = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0) 
90         ? (PROT_READ | PROT_WRITE) : PROT_READ;
91     struct mem_range_desc mrd;
92     struct mem_range_op mro;
93
94     int fd, err = 0;
95
96     fd = open("/dev/mem", O_RDWR);
97     if (fd == -1)
98         return errno;
99
100     map->memory = mmap(NULL, map->size, prot, MAP_SHARED, fd, map->base);
101
102     if (map->memory == MAP_FAILED) {
103         err = errno;
104     }
105
106     mrd.mr_base = map->base;
107     mrd.mr_len = map->size;
108     strncpy(mrd.mr_owner, "pciaccess", sizeof(mrd.mr_owner));
109     if (map->flags & PCI_DEV_MAP_FLAG_CACHABLE)
110         mrd.mr_flags = MDF_WRITEBACK;
111     else if (map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE)
112         mrd.mr_flags = MDF_WRITECOMBINE;
113     else
114         mrd.mr_flags = MDF_UNCACHEABLE;
115     mro.mo_desc = &mrd;
116     mro.mo_arg[0] = MEMRANGE_SET_UPDATE;
117
118     /* No need to set an MTRR if it's the default mode. */
119     if (mrd.mr_flags != MDF_UNCACHEABLE) {
120         if (ioctl(fd, MEMRANGE_SET, &mro)) {
121             fprintf(stderr, "failed to set mtrr: %s\n", strerror(errno));
122         }
123     }
124
125     close(fd);
126
127     return err;
128 }
129
130 static int
131 pci_device_freebsd_unmap_range( struct pci_device *dev,
132                                 struct pci_device_mapping *map )
133 {
134     struct mem_range_desc mrd;
135     struct mem_range_op mro;
136     int fd;
137
138     if ((map->flags & PCI_DEV_MAP_FLAG_CACHABLE) ||
139         (map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE))
140     {
141         fd = open("/dev/mem", O_RDWR);
142         if (fd != -1) {
143             mrd.mr_base = map->base;
144             mrd.mr_len = map->size;
145             strncpy(mrd.mr_owner, "pciaccess", sizeof(mrd.mr_owner));
146             mrd.mr_flags = MDF_UNCACHEABLE;
147             mro.mo_desc = &mrd;
148             mro.mo_arg[0] = MEMRANGE_SET_REMOVE;
149
150             if (ioctl(fd, MEMRANGE_SET, &mro)) {
151                 fprintf(stderr, "failed to unset mtrr: %s\n", strerror(errno));
152             }
153
154             close(fd);
155         } else {
156             fprintf(stderr, "Failed to open /dev/mem\n");
157         }
158     }
159
160     return pci_device_generic_unmap_range(dev, map);
161 }
162
163 static int
164 pci_device_freebsd_read( struct pci_device * dev, void * data,
165                          pciaddr_t offset, pciaddr_t size,
166                          pciaddr_t * bytes_read )
167 {
168     struct pci_io io;
169
170 #if DOMAIN_SUPPORT
171     io.pi_sel.pc_domain = dev->domain;
172 #endif
173     io.pi_sel.pc_bus = dev->bus;
174     io.pi_sel.pc_dev = dev->dev;
175     io.pi_sel.pc_func = dev->func;
176
177     *bytes_read = 0;
178     while ( size > 0 ) {
179         int toread = (size < 4) ? size : 4;
180
181         /* Only power of two allowed. */
182         if (toread == 3)
183             toread = 2;
184
185         io.pi_reg = offset;
186         io.pi_width = toread;
187
188         if ( ioctl( freebsd_pci_sys->pcidev, PCIOCREAD, &io ) < 0 ) 
189             return errno;
190
191         memcpy(data, &io.pi_data, toread );
192
193         offset += toread;
194         data = (char *)data + toread;
195         size -= toread;
196         *bytes_read += toread;
197     }
198
199     return 0;
200 }
201
202
203 static int
204 pci_device_freebsd_write( struct pci_device * dev, const void * data,
205                           pciaddr_t offset, pciaddr_t size,
206                           pciaddr_t * bytes_written )
207 {
208     struct pci_io io;
209
210 #if DOMAIN_SUPPORT
211     io.pi_sel.pc_domain = dev->domain;
212 #endif
213     io.pi_sel.pc_bus = dev->bus;
214     io.pi_sel.pc_dev = dev->dev;
215     io.pi_sel.pc_func = dev->func;
216
217     *bytes_written = 0;
218     while ( size > 0 ) {
219         int towrite = (size < 4 ? size : 4);
220
221         io.pi_reg = offset;
222         io.pi_width = towrite;
223         memcpy( &io.pi_data, data, towrite );
224
225         if ( ioctl( freebsd_pci_sys->pcidev, PCIOCWRITE, &io ) < 0 ) 
226             return errno;
227
228         offset += towrite;
229         data = (char *)data + towrite;
230         size -= towrite;
231         *bytes_written += towrite;
232     }
233
234     return 0;
235 }
236
237 /**
238  * Read a VGA rom using the 0xc0000 mapping.
239  *
240  * This function should be extended to handle access through PCI resources,
241  * which should be more reliable when available.
242  */
243 static int
244 pci_device_freebsd_read_rom( struct pci_device * dev, void * buffer )
245 {
246     void *bios;
247     int memfd;
248
249     if ( ( dev->device_class & 0x00ffff00 ) !=
250          ( ( PCIC_DISPLAY << 16 ) | ( PCIS_DISPLAY_VGA << 8 ) ) )
251     {
252         return ENOSYS;
253     }
254
255     memfd = open( "/dev/mem", O_RDONLY );
256     if ( memfd == -1 )
257         return errno;
258
259     bios = mmap( NULL, dev->rom_size, PROT_READ, 0, memfd, 0xc0000 );
260     if ( bios == MAP_FAILED ) {
261         close( memfd );
262         return errno;
263     }
264
265     memcpy( buffer, bios, dev->rom_size );
266
267     munmap( bios, dev->rom_size );
268     close( memfd );
269
270     return 0;
271 }
272
273 /** Returns the number of regions (base address registers) the device has */
274
275 static int
276 pci_device_freebsd_get_num_regions( struct pci_device * dev )
277 {
278     struct pci_device_private *priv = (struct pci_device_private *) dev;
279
280     switch (priv->header_type & 0x7f) {
281     case 0:
282         return 6;
283     case 1:
284         return 2;
285     case 2:
286         return 1;
287     default:
288         printf("unknown header type %02x\n", priv->header_type);
289         return 0;
290     }
291 }
292
293 /** Masks out the flag bigs of the base address register value */
294 static uint32_t
295 get_map_base( uint32_t val )
296 {
297     if (val & 0x01)
298         return val & ~0x03;
299     else
300         return val & ~0x0f;
301 }
302
303 /** Returns the size of a region based on the all-ones test value */
304 static int
305 get_test_val_size( uint32_t testval )
306 {
307     int size = 1;
308
309     if (testval == 0)
310         return 0;
311
312     /* Mask out the flag bits */
313     testval = get_map_base( testval );
314
315     while ((testval & 1) == 0) {
316         size <<= 1;
317         testval >>= 1;
318     }
319
320     return size;
321 }
322
323 /**
324  * Sets the address and size information for the region from config space
325  * registers.
326  *
327  * This would be much better provided by a kernel interface.
328  *
329  * \return 0 on success, or an errno value.
330  */
331 static int
332 pci_device_freebsd_get_region_info( struct pci_device * dev, int region,
333                                     int bar )
334 {
335     uint32_t addr, testval;
336     int err;
337
338     /* Get the base address */
339     err = pci_device_cfg_read_u32( dev, &addr, bar );
340     if (err != 0)
341         return err;
342
343     /* Test write all ones to the register, then restore it. */
344     err = pci_device_cfg_write_u32( dev, 0xffffffff, bar );
345     if (err != 0)
346         return err;
347     pci_device_cfg_read_u32( dev, &testval, bar );
348     err = pci_device_cfg_write_u32( dev, addr, bar );
349
350     if (addr & 0x01)
351         dev->regions[region].is_IO = 1;
352     if (addr & 0x04)
353         dev->regions[region].is_64 = 1;
354     if (addr & 0x08)
355         dev->regions[region].is_prefetchable = 1;
356
357     /* Set the size */
358     dev->regions[region].size = get_test_val_size( testval );
359
360     /* Set the base address value */
361     if (dev->regions[region].is_64) {
362         uint32_t top;
363
364         err = pci_device_cfg_read_u32( dev, &top, bar + 4 );
365         if (err != 0)
366             return err;
367
368         dev->regions[region].base_addr = ((uint64_t)top << 32) |
369                                           get_map_base(addr);
370     } else {
371         dev->regions[region].base_addr = get_map_base(addr);
372     }
373
374     return 0;
375 }
376
377 static int
378 pci_device_freebsd_probe( struct pci_device * dev )
379 {
380     struct pci_device_private *priv = (struct pci_device_private *) dev;
381     uint8_t irq;
382     int err, i, bar;
383
384     /* Many of the fields were filled in during initial device enumeration.
385      * At this point, we need to fill in regions, rom_size, and irq.
386      */
387
388     err = pci_device_cfg_read_u8( dev, &irq, 60 );
389     if (err)
390         return errno;
391     dev->irq = irq;
392
393     err = pci_device_cfg_read_u8( dev, &priv->header_type, 0x0e );
394     if (err)
395         return errno;
396
397     bar = 0x10;
398     for (i = 0; i < pci_device_freebsd_get_num_regions( dev ); i++) {
399         pci_device_freebsd_get_region_info( dev, i, bar );
400         if (dev->regions[i].is_64) {
401             bar += 0x08;
402             i++;
403         } else
404             bar += 0x04;
405     }
406
407     /* If it's a VGA device, set up the rom size for read_rom using the
408      * 0xc0000 mapping.
409      */
410     if ((dev->device_class & 0x00ffff00) ==
411         ((PCIC_DISPLAY << 16) | (PCIS_DISPLAY_VGA << 8)))
412     {
413         dev->rom_size = 64 * 1024;
414     }
415
416     return 0;
417 }
418
419 static void
420 pci_system_freebsd_destroy(void)
421 {
422     close(freebsd_pci_sys->pcidev);
423     free(freebsd_pci_sys->pci_sys.devices);
424     freebsd_pci_sys = NULL;
425 }
426
427 static const struct pci_system_methods freebsd_pci_methods = {
428     .destroy = pci_system_freebsd_destroy,
429     .destroy_device = NULL, /* nothing to do for this */
430     .read_rom = pci_device_freebsd_read_rom,
431     .probe = pci_device_freebsd_probe,
432     .map_range = pci_device_freebsd_map_range,
433     .unmap_range = pci_device_freebsd_unmap_range,
434     .read = pci_device_freebsd_read,
435     .write = pci_device_freebsd_write,
436     .fill_capabilities = pci_fill_capabilities_generic,
437 };
438
439 /**
440  * Attempt to access the FreeBSD PCI interface.
441  */
442 int
443 pci_system_freebsd_create( void )
444 {
445     struct pci_conf_io pciconfio;
446     struct pci_conf pciconf[255];
447     int pcidev;
448     int i;
449
450     /* Try to open the PCI device */
451     pcidev = open( "/dev/pci", O_RDWR );
452     if ( pcidev == -1 )
453         return ENXIO;
454
455     freebsd_pci_sys = calloc( 1, sizeof( struct freebsd_pci_system ) );
456     if ( freebsd_pci_sys == NULL ) {
457         close( pcidev );
458         return ENOMEM;
459     }
460     pci_sys = &freebsd_pci_sys->pci_sys;
461
462     pci_sys->methods = & freebsd_pci_methods;
463     freebsd_pci_sys->pcidev = pcidev;
464
465     /* Probe the list of devices known by the system */
466     bzero( &pciconfio, sizeof( struct pci_conf_io ) );
467     pciconfio.match_buf_len = sizeof(pciconf);
468     pciconfio.matches = pciconf;
469
470     if ( ioctl( pcidev, PCIOCGETCONF, &pciconfio ) == -1) {
471         free( pci_sys );
472         close( pcidev );
473         return errno;
474     }
475
476     if (pciconfio.status == PCI_GETCONF_ERROR ) {
477         free( pci_sys );
478         close( pcidev );
479         return EINVAL;
480     }
481
482     /* Translate the list of devices into pciaccess's format. */
483     pci_sys->num_devices = pciconfio.num_matches;
484     pci_sys->devices = calloc( pciconfio.num_matches,
485                                sizeof( struct pci_device_private ) );
486
487     for ( i = 0; i < pciconfio.num_matches; i++ ) {
488         struct pci_conf *p = &pciconf[ i ];
489
490 #if DOMAIN_SUPPORT
491         pci_sys->devices[ i ].base.domain = p->pc_sel.pc_domain;
492 #else
493         pci_sys->devices[ i ].base.domain = 0;
494 #endif
495         pci_sys->devices[ i ].base.bus = p->pc_sel.pc_bus;
496         pci_sys->devices[ i ].base.dev = p->pc_sel.pc_dev;
497         pci_sys->devices[ i ].base.func = p->pc_sel.pc_func;
498         pci_sys->devices[ i ].base.vendor_id = p->pc_vendor;
499         pci_sys->devices[ i ].base.device_id = p->pc_device;
500         pci_sys->devices[ i ].base.subvendor_id = p->pc_subvendor;
501         pci_sys->devices[ i ].base.subdevice_id = p->pc_subdevice;
502         pci_sys->devices[ i ].base.device_class = (uint32_t)p->pc_class << 16 |
503             (uint32_t)p->pc_subclass << 8 | (uint32_t)p->pc_progif;
504     }
505
506     return 0;
507 }