/* * mods_pci.c - This file is part of NVIDIA MODS kernel driver. * * Copyright (c) 2008-2018, NVIDIA CORPORATION. All rights reserved. * * NVIDIA MODS kernel driver is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * NVIDIA MODS kernel driver is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NVIDIA MODS kernel driver. * If not, see . */ #include "mods_internal.h" #include #include #if defined(MODS_HAS_DMA_OPS) #include #endif /************************ * PCI ESCAPE FUNCTIONS * ************************/ static int mods_find_pci_dev(struct file *pfile, struct MODS_FIND_PCI_DEVICE_2 *p, int enum_non_zero_dom) { struct pci_dev *dev = NULL; int index = -1; mods_debug_printk(DEBUG_PCI, "find pci dev %04x:%04x, index %d\n", (int) p->vendor_id, (int) p->device_id, (int) p->index); do { dev = pci_get_device(p->vendor_id, p->device_id, dev); if (!dev) return -EINVAL; if (enum_non_zero_dom || !pci_domain_nr(dev->bus)) ++index; } while (index < (int)(p->index)); p->pci_device.domain = pci_domain_nr(dev->bus); p->pci_device.bus = dev->bus->number; p->pci_device.device = PCI_SLOT(dev->devfn); p->pci_device.function = PCI_FUNC(dev->devfn); return OK; } int esc_mods_find_pci_dev_2(struct file *pfile, struct MODS_FIND_PCI_DEVICE_2 *p) { return mods_find_pci_dev(pfile, p, 1); } int esc_mods_find_pci_dev(struct file *pfile, struct MODS_FIND_PCI_DEVICE *p) { struct MODS_FIND_PCI_DEVICE_2 p2; int ret; p2.device_id = p->device_id; p2.vendor_id = p->vendor_id; p2.index = p->index; ret = mods_find_pci_dev(pfile, &p2, 0); if (!ret) { p->bus_number = p2.pci_device.bus; p->device_number = p2.pci_device.device; p->function_number = p2.pci_device.function; } return ret; } static int mods_find_pci_class_code(struct file *pfile, struct MODS_FIND_PCI_CLASS_CODE_2 *p, int enum_non_zero_dom) { struct pci_dev *dev = NULL; int index = -1; mods_debug_printk(DEBUG_PCI, "find pci class code %04x, index %d\n", (int) p->class_code, (int) p->index); do { dev = pci_get_class(p->class_code, dev); if (!dev) return -EINVAL; if (enum_non_zero_dom || !pci_domain_nr(dev->bus)) ++index; } while (index < (int)(p->index)); p->pci_device.domain = pci_domain_nr(dev->bus); p->pci_device.bus = dev->bus->number; p->pci_device.device = PCI_SLOT(dev->devfn); p->pci_device.function = PCI_FUNC(dev->devfn); return OK; } int esc_mods_find_pci_class_code_2(struct file *pfile, struct MODS_FIND_PCI_CLASS_CODE_2 *p) { return mods_find_pci_class_code(pfile, p, 1); } int esc_mods_find_pci_class_code(struct file *pfile, struct MODS_FIND_PCI_CLASS_CODE *p) { struct MODS_FIND_PCI_CLASS_CODE_2 p2; int ret; p2.class_code = p->class_code; p2.index = p->index; ret = mods_find_pci_class_code(pfile, &p2, 0); if (!ret) { p->bus_number = p2.pci_device.bus; p->device_number = p2.pci_device.device; p->function_number = p2.pci_device.function; } return ret; } int esc_mods_pci_get_bar_info_2(struct file *pfile, struct MODS_PCI_GET_BAR_INFO_2 *p) { struct pci_dev *dev; unsigned int devfn, bar_resource_offset, i; #if !defined(MODS_HAS_IORESOURCE_MEM_64) __u32 temp; #endif devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); if (dev == NULL) return -EINVAL; mods_debug_printk(DEBUG_PCI, "pci get bar info %04x:%x:%02x:%x, bar index %d\n", (int) p->pci_device.domain, (int) p->pci_device.bus, (int) p->pci_device.device, (int) p->pci_device.function, (int) p->bar_index); #if defined(CONFIG_PPC64) if (unlikely(mutex_lock_interruptible(mods_get_irq_mutex()))) { LOG_EXT(); return -EINTR; } /* Enable device on the PCI bus */ if (mods_enable_device(pfile->private_data, dev) == 0) { mods_error_printk( "unable to enable dev %04x:%02x:%02x.%x\n", (unsigned int)p->pci_device.domain, (unsigned int)p->pci_device.bus, (unsigned int)p->pci_device.device, (unsigned int)p->pci_device.function); mutex_unlock(mods_get_irq_mutex()); return -EINVAL; } mutex_unlock(mods_get_irq_mutex()); #endif bar_resource_offset = 0; for (i = 0; i < p->bar_index; i++) { #if defined(MODS_HAS_IORESOURCE_MEM_64) if (pci_resource_flags(dev, bar_resource_offset) & IORESOURCE_MEM_64) { #else pci_read_config_dword(dev, (PCI_BASE_ADDRESS_0 + (bar_resource_offset * 4)), &temp); if (temp & PCI_BASE_ADDRESS_MEM_TYPE_64) { #endif bar_resource_offset += 2; } else { bar_resource_offset += 1; } } p->base_address = pci_resource_start(dev, bar_resource_offset); p->bar_size = pci_resource_len(dev, bar_resource_offset); return OK; } int esc_mods_pci_get_bar_info(struct file *pfile, struct MODS_PCI_GET_BAR_INFO *p) { int retval; struct MODS_PCI_GET_BAR_INFO_2 get_bar_info = { {0} }; get_bar_info.pci_device.domain = 0; get_bar_info.pci_device.bus = p->pci_device.bus; get_bar_info.pci_device.device = p->pci_device.device; get_bar_info.pci_device.function = p->pci_device.function; get_bar_info.bar_index = p->bar_index; retval = esc_mods_pci_get_bar_info_2(pfile, &get_bar_info); if (retval) return retval; p->base_address = get_bar_info.base_address; p->bar_size = get_bar_info.bar_size; return OK; } int esc_mods_pci_get_irq_2(struct file *pfile, struct MODS_PCI_GET_IRQ_2 *p) { struct pci_dev *dev; unsigned int devfn; devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); if (dev == NULL) return -EINVAL; mods_debug_printk(DEBUG_PCI, "pci get irq %04x:%x:%02x:%x\n", (int) p->pci_device.domain, (int) p->pci_device.bus, (int) p->pci_device.device, (int) p->pci_device.function); p->irq = dev->irq; return OK; } int esc_mods_pci_get_irq(struct file *pfile, struct MODS_PCI_GET_IRQ *p) { int retval; struct MODS_PCI_GET_IRQ_2 get_irq = { {0} }; get_irq.pci_device.domain = 0; get_irq.pci_device.bus = p->pci_device.bus; get_irq.pci_device.device = p->pci_device.device; get_irq.pci_device.function = p->pci_device.function; retval = esc_mods_pci_get_irq_2(pfile, &get_irq); if (retval) return retval; p->irq = get_irq.irq; return OK; } int esc_mods_pci_read_2(struct file *pfile, struct MODS_PCI_READ_2 *p) { struct pci_dev *dev; unsigned int devfn; devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); if (dev == NULL) return -EINVAL; mods_debug_printk(DEBUG_PCI, "pci read %04x:%x:%02x.%x, addr 0x%04x, size %d\n", (int) p->pci_device.domain, (int) p->pci_device.bus, (int) p->pci_device.device, (int) p->pci_device.function, (int) p->address, (int) p->data_size); p->data = 0; switch (p->data_size) { case 1: { u8 value; pci_read_config_byte(dev, p->address, &value); p->data = value; } break; case 2: { u16 value; pci_read_config_word(dev, p->address, &value); p->data = value; } break; case 4: pci_read_config_dword(dev, p->address, (u32 *) &p->data); break; default: return -EINVAL; } return OK; } int esc_mods_pci_read(struct file *pfile, struct MODS_PCI_READ *p) { int retval; struct MODS_PCI_READ_2 pci_read = { {0} }; pci_read.pci_device.domain = 0; pci_read.pci_device.bus = p->bus_number; pci_read.pci_device.device = p->device_number; pci_read.pci_device.function = p->function_number; pci_read.address = p->address; pci_read.data_size = p->data_size; retval = esc_mods_pci_read_2(pfile, &pci_read); if (retval) return retval; p->data = pci_read.data; return OK; } int esc_mods_pci_write_2(struct file *pfile, struct MODS_PCI_WRITE_2 *p) { struct pci_dev *dev; unsigned int devfn; mods_debug_printk(DEBUG_PCI, "pci write %04x:%x:%02x.%x, addr 0x%04x, size %d, data 0x%x\n", (int) p->pci_device.domain, (int) p->pci_device.bus, (int) p->pci_device.device, (int) p->pci_device.function, (int) p->address, (int) p->data_size, (int) p->data); devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); if (dev == NULL) { mods_error_printk( "pci write to %04x:%x:%02x.%x, addr 0x%04x, size %d failed\n", (unsigned int)p->pci_device.domain, (unsigned int)p->pci_device.bus, (unsigned int)p->pci_device.device, (unsigned int)p->pci_device.function, (unsigned int)p->address, (int)p->data_size); return -EINVAL; } switch (p->data_size) { case 1: pci_write_config_byte(dev, p->address, p->data); break; case 2: pci_write_config_word(dev, p->address, p->data); break; case 4: pci_write_config_dword(dev, p->address, p->data); break; default: return -EINVAL; } return OK; } int esc_mods_pci_write(struct file *pfile, struct MODS_PCI_WRITE *p) { struct MODS_PCI_WRITE_2 pci_write = { {0} }; pci_write.pci_device.domain = 0; pci_write.pci_device.bus = p->bus_number; pci_write.pci_device.device = p->device_number; pci_write.pci_device.function = p->function_number; pci_write.address = p->address; pci_write.data = p->data; pci_write.data_size = p->data_size; return esc_mods_pci_write_2(pfile, &pci_write); } int esc_mods_pci_bus_add_dev(struct file *pfile, struct MODS_PCI_BUS_ADD_DEVICES *scan) { #if defined(CONFIG_PCI) mods_info_printk("scanning pci bus %x\n", scan->bus); /* initiate a PCI bus scan to find hotplugged PCI devices in domain 0 */ pci_scan_child_bus(pci_find_bus(0, scan->bus)); /* add newly found devices */ pci_bus_add_devices(pci_find_bus(0, scan->bus)); return OK; #else return -EINVAL; #endif } int esc_mods_pci_hot_reset(struct file *pfile, struct MODS_PCI_HOT_RESET *p) { #if defined(CONFIG_PPC64) struct pci_dev *dev; unsigned int devfn; int retval; mods_debug_printk(DEBUG_PCI, "pci_hot_reset %04x:%x:%02x.%x\n", (int) p->pci_device.domain, (int) p->pci_device.bus, (int) p->pci_device.device, (int) p->pci_device.function); devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); if (dev == NULL) { mods_error_printk( "pci_hot_reset cannot find pci device %04x:%x:%02x.%x\n", (unsigned int)p->pci_device.domain, (unsigned int)p->pci_device.bus, (unsigned int)p->pci_device.device, (unsigned int)p->pci_device.function); return -EINVAL; } retval = pci_set_pcie_reset_state(dev, pcie_hot_reset); if (retval) { mods_error_printk( "pci_hot_reset failed on %04x:%x:%02x.%x\n", (unsigned int)p->pci_device.domain, (unsigned int)p->pci_device.bus, (unsigned int)p->pci_device.device, (unsigned int)p->pci_device.function); return retval; } retval = pci_set_pcie_reset_state(dev, pcie_deassert_reset); if (retval) { mods_error_printk( "pci_hot_reset deassert failed on %04x:%x:%02x.%x\n", (unsigned int)p->pci_device.domain, (unsigned int)p->pci_device.bus, (unsigned int)p->pci_device.device, (unsigned int)p->pci_device.function); return retval; } return OK; #else return -EINVAL; #endif } /************************ * PIO ESCAPE FUNCTIONS * ************************/ int esc_mods_pio_read(struct file *pfile, struct MODS_PIO_READ *p) { LOG_ENT(); switch (p->data_size) { case 1: p->data = inb(p->port); break; case 2: p->data = inw(p->port); break; case 4: p->data = inl(p->port); break; default: return -EINVAL; } LOG_EXT(); return OK; } int esc_mods_pio_write(struct file *pfile, struct MODS_PIO_WRITE *p) { LOG_ENT(); switch (p->data_size) { case 1: outb(p->data, p->port); break; case 2: outw(p->data, p->port); break; case 4: outl(p->data, p->port); break; default: return -EINVAL; } LOG_EXT(); return OK; } int esc_mods_device_numa_info_2(struct file *fp, struct MODS_DEVICE_NUMA_INFO_2 *p) { #ifdef MODS_HAS_DEV_TO_NUMA_NODE unsigned int devfn = PCI_DEVFN(p->pci_device.device, p->pci_device.function); struct pci_dev *dev = MODS_PCI_GET_SLOT(p->pci_device.domain, p->pci_device.bus, devfn); LOG_ENT(); if (dev == NULL) { mods_error_printk("PCI device %04x:%x:%02x.%x not found\n", p->pci_device.domain, p->pci_device.bus, p->pci_device.device, p->pci_device.function); LOG_EXT(); return -EINVAL; } p->node = dev_to_node(&dev->dev); if (-1 != p->node) { const unsigned long *maskp = cpumask_bits(cpumask_of_node(p->node)); unsigned int i, word, bit, maskidx; if (((nr_cpumask_bits + 31) / 32) > MAX_CPU_MASKS) { mods_error_printk("too many CPUs (%d) for mask bits\n", nr_cpumask_bits); LOG_EXT(); return -EINVAL; } for (i = 0, maskidx = 0; i < nr_cpumask_bits; i += 32, maskidx++) { word = i / BITS_PER_LONG; bit = i % BITS_PER_LONG; p->node_cpu_mask[maskidx] = (maskp[word] >> bit) & 0xFFFFFFFFUL; } } p->node_count = num_possible_nodes(); p->cpu_count = num_possible_cpus(); LOG_EXT(); return OK; #else return -EINVAL; #endif } int esc_mods_device_numa_info(struct file *fp, struct MODS_DEVICE_NUMA_INFO *p) { int retval, i; struct MODS_DEVICE_NUMA_INFO_2 numa_info = { {0} }; numa_info.pci_device.domain = 0; numa_info.pci_device.bus = p->pci_device.bus; numa_info.pci_device.device = p->pci_device.device; numa_info.pci_device.function = p->pci_device.function; retval = esc_mods_device_numa_info_2(fp, &numa_info); if (retval) return retval; p->node = numa_info.node; p->node_count = numa_info.node_count; for (i = 0; i < MAX_CPU_MASKS; i++) p->node_cpu_mask[i] = numa_info.node_cpu_mask[i]; p->cpu_count = numa_info.cpu_count; return OK; } int esc_mods_get_iommu_state(struct file *pfile, struct MODS_GET_IOMMU_STATE *state) { int err = esc_mods_get_iommu_state_2(pfile, state); if (!err) state->state = (state->state == MODS_SWIOTLB_DISABLED) ? 1 : 0; return err; } int esc_mods_get_iommu_state_2(struct file *pfile, struct MODS_GET_IOMMU_STATE *state) { #if !defined(CONFIG_SWIOTLB) state->state = MODS_SWIOTLB_DISABLED; #elif defined(MODS_HAS_DMA_OPS) && \ (defined(MODS_HAS_NONCOH_DMA_OPS) || defined(MODS_HAS_MAP_SG_ATTRS)) unsigned int devfn = PCI_DEVFN(state->pci_device.device, state->pci_device.function); struct pci_dev *dev = MODS_PCI_GET_SLOT(state->pci_device.domain, state->pci_device.bus, devfn); const struct dma_map_ops *ops = get_dma_ops(&dev->dev); #if defined(MODS_HAS_NONCOH_DMA_OPS) state->state = (ops != &noncoherent_swiotlb_dma_ops && ops != &coherent_swiotlb_dma_ops) ? MODS_SWIOTLB_DISABLED : MODS_SWIOTLB_ACTIVE; #else state->state = ops->map_sg != swiotlb_map_sg_attrs ? MODS_SWIOTLB_DISABLED : MODS_SWIOTLB_ACTIVE; #endif #elif defined(CONFIG_PPC64) || defined(CONFIG_ARM64) /* No way to detect, assume SW I/O TLB is disabled on ppc64/arm64 */ state->state = MODS_SWIOTLB_DISABLED; #else /* No way to detect on old kernel */ state->state = MODS_SWIOTLB_INDETERMINATE; #endif return OK; } int esc_mods_pci_set_dma_mask(struct file *file, struct MODS_PCI_DMA_MASK *dma_mask) { int err; unsigned int devfn = PCI_DEVFN(dma_mask->pci_device.device, dma_mask->pci_device.function); struct pci_dev *dev = MODS_PCI_GET_SLOT(dma_mask->pci_device.domain, dma_mask->pci_device.bus, devfn); u64 mask; if (dma_mask->num_bits > 64) return -EINVAL; mask = dma_mask->num_bits == 64 ? ~0ULL : (1ULL<num_bits)-1; err = pci_set_dma_mask(dev, mask); if (err) { mods_error_printk("failed to set dma mask 0x%llx for dev %04x:%x:%02x.%x\n", mask, (unsigned int)dma_mask->pci_device.domain, (unsigned int)dma_mask->pci_device.bus, (unsigned int)dma_mask->pci_device.device, (unsigned int)dma_mask->pci_device.function); #if defined(CONFIG_PPC64) /* Ignore error if TCE bypass is on */ if (dev->dma_mask == ~0ULL) err = OK; #endif } else err = pci_set_consistent_dma_mask(dev, mask); return err; }