/* * 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
*/
/* * gsp_logs - list of nvif_log GSP-RM logging buffers * * Head pointer to a a list of nvif_log buffers that is created for each GPU * upon GSP shutdown if the "keep_gsp_logging" command-line parameter is * specified. This is used to track the alternative debugfs entries for the * GSP-RM logs.
*/
NVIF_LOGS_DECLARE(gsp_logs); #endif
static u64
nouveau_pci_name(struct pci_dev *pdev)
{
u64 name = (u64)pci_domain_nr(pdev->bus) << 32;
name |= pdev->bus->number << 16;
name |= PCI_SLOT(pdev->devfn) << 8; return name | PCI_FUNC(pdev->devfn);
}
/* All our channels are dead now, which means all the fences they * own are signalled, and all callback functions have been called. * * So, after flushing the workqueue, there should be nothing left.
*/
flush_work(&cli->work);
WARN_ON(!list_empty(&cli->worker));
if (cli->sched)
nouveau_sched_destroy(&cli->sched); if (uvmm)
nouveau_uvmm_fini(uvmm);
nouveau_vmm_fini(&cli->svm);
nouveau_vmm_fini(&cli->vmm);
nvif_mmu_dtor(&cli->mmu);
cli->device.object.map.ptr = NULL;
nvif_device_dtor(&cli->device);
mutex_lock(&cli->drm->client_mutex);
nvif_client_dtor(&cli->base);
mutex_unlock(&cli->drm->client_mutex);
}
ret = nvif_mmu_ctor(&cli->device.object, "drmMmu", drm->mmu.object.oclass,
&cli->mmu); if (ret) {
NV_PRINTK(err, cli, "MMU allocation failed: %d\n", ret); goto done;
}
ret = nvif_mclass(&cli->mmu.object, vmms); if (ret < 0) {
NV_PRINTK(err, cli, "No supported VMM class\n"); goto done;
}
ret = nouveau_vmm_init(cli, vmms[ret].oclass, &cli->vmm); if (ret) {
NV_PRINTK(err, cli, "VMM allocation failed: %d\n", ret); goto done;
}
ret = nvif_mclass(&cli->mmu.object, mems); if (ret < 0) {
NV_PRINTK(err, cli, "No supported MEM class\n"); goto done;
}
cli->mem = &mems[ret];
/* Don't pass in the (shared) sched_wq in order to let * nouveau_sched_create() create a dedicated one for VM_BIND jobs. * * This is required to ensure that for VM_BIND jobs free_job() work and * run_job() work can always run concurrently and hence, free_job() work * can never stall run_job() work. For EXEC jobs we don't have this * requirement, since EXEC job's free_job() does not require to take any * locks which indirectly or directly are held for allocations * elsewhere.
*/
ret = nouveau_sched_create(&cli->sched, drm, NULL, 1); if (ret) goto done;
return 0;
done: if (ret)
nouveau_cli_fini(cli); return ret;
}
staticvoid
nouveau_accel_ce_init(struct nouveau_drm *drm)
{ struct nvif_device *device = &drm->client.device;
u64 runm; int ret = 0;
/* Allocate channel that has access to a (preferably async) copy * engine, to use for TTM buffer moves.
*/
runm = nvif_fifo_runlist_ce(device); if (!runm) {
NV_DEBUG(drm, "no ce runlist\n"); return;
}
ret = nouveau_channel_new(&drm->client, true, runm, NvDmaFB, NvDmaTT, &drm->cechan); if (ret)
NV_ERROR(drm, "failed to create ce channel, %d\n", ret);
}
/* Allocate channel that has access to the graphics engine. */
runm = nvif_fifo_runlist(device, NV_DEVICE_HOST_RUNLIST_ENGINES_GR); if (!runm) {
NV_DEBUG(drm, "no gr runlist\n"); return;
}
ret = nouveau_channel_new(&drm->client, false, runm, NvDmaFB, NvDmaTT, &drm->channel); if (ret) {
NV_ERROR(drm, "failed to create kernel channel, %d\n", ret);
nouveau_accel_gr_fini(drm); return;
}
/* A SW class is used on pre-NV50 HW to assist with handling the * synchronisation of page flips, as well as to implement fences * on TNT/TNT2 HW that lacks any kind of support in host.
*/ if (!drm->channel->nvsw.client && device->info.family < NV_DEVICE_INFO_V0_TESLA) {
ret = nvif_object_ctor(&drm->channel->user, "drmNvsw",
NVDRM_NVSW, nouveau_abi16_swclass(drm),
NULL, 0, &drm->channel->nvsw);
if (ret == 0 && device->info.chipset >= 0x11) {
ret = nvif_object_ctor(&drm->channel->user, "drmBlit",
0x005f, 0x009f,
NULL, 0, &drm->channel->blit);
}
if (ret == 0) { struct nvif_push *push = &drm->channel->chan.push;
if (ret) {
NV_ERROR(drm, "failed to allocate sw or blit class, %d\n", ret);
nouveau_accel_gr_fini(drm); return;
}
}
/* NvMemoryToMemoryFormat requires a notifier ctxdma for some reason, * even if notification is never requested, so, allocate a ctxdma on * any GPU where it's possible we'll end up using M2MF for BO moves.
*/ if (device->info.family < NV_DEVICE_INFO_V0_FERMI) {
ret = nvkm_gpuobj_new(nvxx_device(drm), 32, 0, false, NULL, &drm->notify); if (ret) {
NV_ERROR(drm, "failed to allocate notifier, %d\n", ret);
nouveau_accel_gr_fini(drm); return;
}
staticvoid
nouveau_accel_init(struct nouveau_drm *drm)
{ struct nvif_device *device = &drm->client.device; struct nvif_sclass *sclass; int ret, i, n;
if (nouveau_noaccel) return;
/* Initialise global support for channels, and synchronisation. */
ret = nouveau_channels_init(drm); if (ret) return;
/*XXX: this is crap, but the fence/channel stuff is a little * backwards in some places. this will be fixed.
*/
ret = n = nvif_object_sclass_get(&device->object, &sclass); if (ret < 0) return;
for (ret = -ENOSYS, i = 0; i < n; i++) { switch (sclass[i].oclass) { case NV03_CHANNEL_DMA:
ret = nv04_fence_create(drm); break; case NV10_CHANNEL_DMA:
ret = nv10_fence_create(drm); break; case NV17_CHANNEL_DMA: case NV40_CHANNEL_DMA:
ret = nv17_fence_create(drm); break; case NV50_CHANNEL_GPFIFO:
ret = nv50_fence_create(drm); break; case G82_CHANNEL_GPFIFO:
ret = nv84_fence_create(drm); break; case FERMI_CHANNEL_GPFIFO: case KEPLER_CHANNEL_GPFIFO_A: case KEPLER_CHANNEL_GPFIFO_B: case MAXWELL_CHANNEL_GPFIFO_A: case PASCAL_CHANNEL_GPFIFO_A:
ret = nvc0_fence_create(drm); break; case VOLTA_CHANNEL_GPFIFO_A: case TURING_CHANNEL_GPFIFO_A: case AMPERE_CHANNEL_GPFIFO_A: case AMPERE_CHANNEL_GPFIFO_B: case HOPPER_CHANNEL_GPFIFO_A: case BLACKWELL_CHANNEL_GPFIFO_A: case BLACKWELL_CHANNEL_GPFIFO_B:
ret = gv100_fence_create(drm); break; default: break;
}
}
nvif_object_sclass_put(&sclass); if (ret) {
NV_ERROR(drm, "failed to initialise sync subsystem, %d\n", ret);
nouveau_accel_fini(drm); return;
}
/* Volta requires access to a doorbell register for kickoff. */ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_VOLTA) {
ret = nvif_user_ctor(device, "drmUsermode"); if (ret) return;
}
/* Allocate channels we need to support various functions. */
nouveau_accel_gr_init(drm);
nouveau_accel_ce_init(drm);
/* * There may be existing clients from as-yet unclosed files. For now, * clean them up here rather than deferring until the file is closed, * but this likely not correct if we want to support hot-unplugging * properly.
*/
mutex_lock(&drm->clients_lock);
list_for_each_entry_safe(cli, temp_cli, &drm->clients, head) {
list_del(&cli->head);
mutex_lock(&cli->mutex); if (cli->abi16)
nouveau_abi16_fini(cli->abi16);
mutex_unlock(&cli->mutex);
nouveau_cli_fini(cli);
kfree(cli);
}
mutex_unlock(&drm->clients_lock);
/* workaround an odd issue on nvc1 by disabling the device's * nosnoop capability. hopefully won't cause issues until a * better fix is found - assuming there is one...
*/ if (drm->client.device.info.chipset == 0xc1)
nvif_mask(&drm->client.device.object, 0x00088080, 0x00000800, 0x00000000);
nouveau_vga_init(drm);
ret = nouveau_ttm_init(drm); if (ret) goto fail_ttm;
ret = nouveau_bios_init(dev); if (ret) goto fail_bios;
nouveau_accel_init(drm);
ret = nouveau_display_create(dev); if (ret) goto fail_dispctor;
if (dev->mode_config.num_crtc) {
ret = nouveau_display_init(dev, false, false); if (ret) goto fail_dispinit;
}
ret = nvif_driver_init(NULL, nouveau_config, nouveau_debug, "drm",
nouveau_name(drm->dev), &drm->_client); if (ret) goto done;
ret = nvif_device_ctor(&drm->_client, "drmDevice", &drm->device); if (ret) {
NV_ERROR(drm, "Device allocation failed: %d\n", ret); goto done;
}
ret = nvif_device_map(&drm->device); if (ret) {
NV_ERROR(drm, "Failed to map PRI: %d\n", ret); goto done;
}
ret = nvif_mclass(&drm->device.object, mmus); if (ret < 0) {
NV_ERROR(drm, "No supported MMU class\n"); goto done;
}
ret = nvif_mmu_ctor(&drm->device.object, "drmMmu", mmus[ret].oclass, &drm->mmu); if (ret) {
NV_ERROR(drm, "MMU allocation failed: %d\n", ret); goto done;
}
done: if (ret) {
nouveau_drm_device_del(drm);
drm = NULL;
}
return ret ? ERR_PTR(ret) : drm;
}
/* * On some Intel PCIe bridge controllers doing a * D0 -> D3hot -> D3cold -> D0 sequence causes Nvidia GPUs to not reappear. * Skipping the intermediate D3hot step seems to make it work again. This is * probably caused by not meeting the expectation the involved AML code has * when the GPU is put into D3hot state before invoking it. * * This leads to various manifestations of this issue: * - AML code execution to power on the GPU hits an infinite loop (as the * code waits on device memory to change). * - kernel crashes, as all PCI reads return -1, which most code isn't able * to handle well enough. * * In all cases dmesg will contain at least one line like this: * 'nouveau 0000:01:00.0: Refused to change power state, currently in D3' * followed by a lot of nouveau timeouts. * * In the \_SB.PCI0.PEG0.PG00._OFF code deeper down writes bit 0x80 to the not * documented PCI config space register 0x248 of the Intel PCIe bridge * controller (0x1901) in order to change the state of the PCIe link between * the PCIe port and the GPU. There are alternative code paths using other * registers, which seem to work fine (executed pre Windows 8): * - 0xbc bit 0x20 (publicly available documentation claims 'reserved') * - 0xb0 bit 0x10 (link disable) * Changing the conditions inside the firmware by poking into the relevant * addresses does resolve the issue, but it seemed to be ACPI private memory * and not any device accessible memory at all, so there is no portable way of * changing the conditions. * On a XPS 9560 that means bits [0,3] on \CPEX need to be cleared. * * The only systems where this behavior can be seen are hybrid graphics laptops * with a secondary Nvidia Maxwell, Pascal or Turing GPU. It's unclear whether * this issue only occurs in combination with listed Intel PCIe bridge * controllers and the mentioned GPUs or other devices as well. * * documentation on the PCIe bridge controller can be found in the * "7th Generation Intel® Processor Families for H Platforms Datasheet Volume 2" * Section "12 PCI Express* Controller (x16) Registers"
*/
if (vga_switcheroo_client_probe_defer(pdev)) return -EPROBE_DEFER;
/* We need to check that the chipset is supported before booting * fbdev off the hardware, as there's no way to put it back.
*/
ret = nvkm_device_pci_new(pdev, nouveau_config, nouveau_debug, &device); if (ret) return ret;
/* Remove conflicting drivers (vesafb, efifb etc). */
ret = aperture_remove_conflicting_pci_devices(pdev, driver_pci.name); if (ret) return ret;
pci_set_master(pdev);
if (nouveau_atomic)
driver_pci.driver_features |= DRIVER_ATOMIC;
drm = nouveau_drm_device_new(&driver_pci, &pdev->dev, device); if (IS_ERR(drm)) {
ret = PTR_ERR(drm); goto fail_nvkm;
}
ret = pci_enable_device(pdev); if (ret) goto fail_drm;
ret = nouveau_drm_device_init(drm); if (ret) goto fail_pci;
if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
format = drm_format_info(DRM_FORMAT_C8); else
format = NULL;
if (dev->mode_config.num_crtc) {
NV_DEBUG(drm, "suspending display...\n");
ret = nouveau_display_suspend(dev, runtime); if (ret) return ret;
}
NV_DEBUG(drm, "evicting buffers...\n");
man = ttm_manager_type(&drm->ttm.bdev, TTM_PL_VRAM);
ttm_resource_manager_evict_all(&drm->ttm.bdev, man);
NV_DEBUG(drm, "waiting for kernel channels to go idle...\n"); if (drm->cechan) {
ret = nouveau_channel_idle(drm->cechan); if (ret) goto fail_display;
}
if (drm->channel) {
ret = nouveau_channel_idle(drm->channel); if (ret) goto fail_display;
}
NV_DEBUG(drm, "suspending fence...\n"); if (drm->fence && nouveau_fence(drm)->suspend) { if (!nouveau_fence(drm)->suspend(drm)) {
ret = -ENOMEM; goto fail_display;
}
}
NV_DEBUG(drm, "suspending object tree...\n");
ret = nvif_client_suspend(&drm->_client); if (ret) goto fail_client;
return 0;
fail_client: if (drm->fence && nouveau_fence(drm)->resume)
nouveau_fence(drm)->resume(drm);
pm_runtime_mark_last_busy(dev);
pm_runtime_autosuspend(dev); /* we don't want the main rpm_idle to call suspend - we want to autosuspend */ return 1;
}
/* need to bring up power immediately if opening device */
ret = pm_runtime_get_sync(dev->dev); if (ret < 0 && ret != -EACCES) {
pm_runtime_put_autosuspend(dev->dev); return ret;
}
/* * The device is gone, and as it currently stands all clients are * cleaned up in the removal codepath. In the future this may change * so that we can support hot-unplugging, but for now we immediately * return to avoid a double-free situation.
*/ if (!drm_dev_enter(dev, &dev_index)) return;
pm_runtime_get_sync(dev->dev);
mutex_lock(&cli->mutex); if (cli->abi16)
nouveau_abi16_fini(cli->abi16);
mutex_unlock(&cli->mutex);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.