/* * Copyright 2012 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: Ben Skeggs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "priv.h" static DEFINE_MUTEX(os_mutex); static LIST_HEAD(os_device_list); static int os_client_nr = 0; /****************************************************************************** * horrific stuff to implement linux's ioremap interface on top of pciaccess *****************************************************************************/ static DEFINE_MUTEX(os_ioremap_mutex); static struct os_ioremap_info { struct pci_device *pdev; int refs; u64 addr; u64 size; void *ptr; } os_ioremap[32]; void __iomem * nvos_ioremap_bar(struct pci_device *pdev, int bar, u64 addr) { u64 base = pdev->regions[bar].base_addr; u64 size = pdev->regions[bar].size; u64 offset = addr - base; void __iomem *ptr = NULL; int i; mutex_lock(&os_ioremap_mutex); for (i = 0; !ptr && i < ARRAY_SIZE(os_ioremap); i++) { if (os_ioremap[i].refs && os_ioremap[i].addr == base) { os_ioremap[i].refs++; ptr = os_ioremap[i].ptr + offset; } } for (i = 0; !ptr && i < ARRAY_SIZE(os_ioremap); i++) { if (!os_ioremap[i].refs && !pci_device_map_range(pdev, base, size, PCI_DEV_MAP_FLAG_WRITABLE, &os_ioremap[i].ptr)) { os_ioremap[i].pdev = pdev; os_ioremap[i].refs = 1; os_ioremap[i].addr = base; os_ioremap[i].size = size; ptr = os_ioremap[i].ptr + offset; } } mutex_unlock(&os_ioremap_mutex); return ptr; } void __iomem * nvos_ioremap(u64 addr, u64 size) { struct os_device *odev; int i; list_for_each_entry(odev, &os_device_list, head) { struct pci_device *pdev = odev->pdev.pdev; for (i = 0; i < ARRAY_SIZE(pdev->regions); i++) { if (addr >= pdev->regions[i].base_addr && addr + size <= pdev->regions[i].base_addr + pdev->regions[i].size) { return nvos_ioremap_bar(pdev, i, addr); } } } return NULL; } void nvos_iounmap(void __iomem *ptr) { int i; mutex_lock(&os_ioremap_mutex); for (i = 0; ptr && i < ARRAY_SIZE(os_ioremap); i++) { if (os_ioremap[i].refs && ptr >= os_ioremap[i].ptr && ptr < os_ioremap[i].ptr + os_ioremap[i].size) { if (!--os_ioremap[i].refs) { pci_device_unmap_range(os_ioremap[i].pdev, os_ioremap[i].ptr, os_ioremap[i].size); } break; } } mutex_unlock(&os_ioremap_mutex); } /****************************************************************************** * client interfaces *****************************************************************************/ static void os_fini_device(struct os_device *odev) { nvkm_device_del(&odev->device); list_del(&odev->head); kfree(odev); } static int os_init_device(struct pci_device *pdev, u64 handle, const char *cfg, const char *dbg) { struct os_device *odev; int ret; ret = pci_device_probe(pdev); if (ret) { fprintf(stderr, "pci_device_probe failed, %d\n", ret); return ret; } odev = calloc(1, sizeof(*odev)); if (!odev) return -ENOMEM; snprintf(odev->pdev.dev.name, sizeof(odev->pdev.dev.name), "%04x:%02x:%02x.%1x", pdev->domain, pdev->bus, pdev->dev, pdev->func); odev->pdev.pdev = pdev; odev->pdev.device = pdev->vendor_id; odev->pdev.subsystem_vendor = pdev->subvendor_id; odev->pdev.subsystem_device = pdev->subdevice_id; list_add_tail(&odev->head, &os_device_list); ret = nvkm_device_new(&odev->pdev, NVKM_BUS_PCI, handle, odev->pdev.dev.name, cfg, dbg, &odev->device); if (ret) { fprintf(stderr, "failed to create device, %d\n", ret); os_fini_device(odev); return ret; } return 0; } static int os_init(const char *cfg, const char *dbg, bool init) { struct pci_device_iterator *iter; struct pci_device *pdev; u64 handle; int ret, n = 0; ret = pci_system_init(); if (ret) { fprintf(stderr, "pci_system_init failed, %d\n", ret); return ret; } iter = pci_slot_match_iterator_create(NULL); while ((pdev = pci_device_next(iter))) { if ((pdev->device_class & 0x00ff0000) != 0x00030000) continue; if (pdev->vendor_id != 0x10de) continue; handle = ((u64)pdev->domain << 32) | (pdev->bus << 16) | (pdev->dev << 8) | pdev->func; if (!init) { printf("%d: 0x%010llx PCI:%04x:%02x:%02x:%02x " "(%04x:%04x)\n", n++, handle, pdev->domain, pdev->bus, pdev->dev, pdev->func, pdev->vendor_id, pdev->device_id); continue; } os_init_device(pdev, handle, cfg, dbg); } pci_iterator_destroy(iter); return 0; } static void os_fini(void) { struct os_device *odev, *temp; list_for_each_entry_safe(odev, temp, &os_device_list, head) { os_fini_device(odev); } pci_system_cleanup(); } static void os_client_unmap(void *priv, void *ptr, u32 size) { iounmap(ptr); } static void * os_client_map(void *priv, u64 handle, u32 size) { return ioremap(handle, size); } static int os_client_ioctl(void *priv, bool super, void *data, u32 size, void **hack) { return nvkm_ioctl(priv, super, data, size, hack); } static int os_client_resume(void *priv) { return nvkm_client_init(priv); } static int os_client_suspend(void *priv) { return nvkm_client_fini(priv, true); } static void os_client_fini(void *priv) { struct nvkm_client *client = priv; nvkm_client_del(&client); mutex_lock(&os_mutex); if (--os_client_nr == 0) os_fini(); mutex_unlock(&os_mutex); } static int os_client_init(const char *name, u64 device, const char *cfg, const char *dbg, void **ppriv) { struct nvkm_client *client; int ret; mutex_lock(&os_mutex); if (os_client_nr++ == 0) os_init(cfg, dbg, true); mutex_unlock(&os_mutex); ret = nvkm_client_new(name, device, cfg, dbg, &client); *ppriv = client; if (ret == 0) client->ntfy = nvif_notify; return ret; } const struct nvif_driver nvif_driver_lib = { .name = "lib", .init = os_client_init, .fini = os_client_fini, .suspend = os_client_suspend, .resume = os_client_resume, .ioctl = os_client_ioctl, .map = os_client_map, .unmap = os_client_unmap, .keep = false, };