cregit-Linux how code gets into the kernel

Release 4.14 arch/powerpc/platforms/pseries/vio.c

/*
 * IBM PowerPC Virtual I/O Infrastructure Support.
 *
 *    Copyright (c) 2003,2008 IBM Corp.
 *     Dave Engebretsen engebret@us.ibm.com
 *     Santiago Leon santil@us.ibm.com
 *     Hollis Blanchard <hollisb@us.ibm.com>
 *     Stephen Rothwell
 *     Robert Jennings <rcjenn@us.ibm.com>
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

#include <linux/cpu.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/console.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#include <linux/kobject.h>

#include <asm/iommu.h>
#include <asm/dma.h>
#include <asm/vio.h>
#include <asm/prom.h>
#include <asm/firmware.h>
#include <asm/tce.h>
#include <asm/page.h>
#include <asm/hvcall.h>


static struct vio_dev vio_bus_device  = { /* fake "parent" device */
	.name = "vio",
	.type = "",
	.dev.init_name = "vio",
	.dev.bus = &vio_bus_type,
};

#ifdef CONFIG_PPC_SMLPAR
/**
 * vio_cmo_pool - A pool of IO memory for CMO use
 *
 * @size: The size of the pool in bytes
 * @free: The amount of free memory in the pool
 */

struct vio_cmo_pool {
	
size_t size;
	
size_t free;
};

/* How many ms to delay queued balance work */

#define VIO_CMO_BALANCE_DELAY 100

/* Portion out IO memory to CMO devices by this chunk size */

#define VIO_CMO_BALANCE_CHUNK 131072

/**
 * vio_cmo_dev_entry - A device that is CMO-enabled and requires entitlement
 *
 * @vio_dev: struct vio_dev pointer
 * @list: pointer to other devices on bus that are being tracked
 */

struct vio_cmo_dev_entry {
	
struct vio_dev *viodev;
	
struct list_head list;
};

/**
 * vio_cmo - VIO bus accounting structure for CMO entitlement
 *
 * @lock: spinlock for entire structure
 * @balance_q: work queue for balancing system entitlement
 * @device_list: list of CMO-enabled devices requiring entitlement
 * @entitled: total system entitlement in bytes
 * @reserve: pool of memory from which devices reserve entitlement, incl. spare
 * @excess: pool of excess entitlement not needed for device reserves or spare
 * @spare: IO memory for device hotplug functionality
 * @min: minimum necessary for system operation
 * @desired: desired memory for system operation
 * @curr: bytes currently allocated
 * @high: high water mark for IO data usage
 */

static struct vio_cmo {
	
spinlock_t lock;
	
struct delayed_work balance_q;
	
struct list_head device_list;
	
size_t entitled;
	
struct vio_cmo_pool reserve;
	
struct vio_cmo_pool excess;
	
size_t spare;
	
size_t min;
	
size_t desired;
	
size_t curr;
	
size_t high;

} vio_cmo;

/**
 * vio_cmo_OF_devices - Count the number of OF devices that have DMA windows
 */

static int vio_cmo_num_OF_devs(void) { struct device_node *node_vroot; int count = 0; /* * Count the number of vdevice entries with an * ibm,my-dma-window OF property */ node_vroot = of_find_node_by_name(NULL, "vdevice"); if (node_vroot) { struct device_node *of_node; struct property *prop; for_each_child_of_node(node_vroot, of_node) { prop = of_find_property(of_node, "ibm,my-dma-window", NULL); if (prop) count++; } } of_node_put(node_vroot); return count; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings78100.00%1100.00%
Total78100.00%1100.00%

/** * vio_cmo_alloc - allocate IO memory for CMO-enable devices * * @viodev: VIO device requesting IO memory * @size: size of allocation requested * * Allocations come from memory reserved for the devices and any excess * IO memory available to all devices. The spare pool used to service * hotplug must be equal to %VIO_CMO_MIN_ENT for the excess pool to be * made available. * * Return codes: * 0 for successful allocation and -ENOMEM for a failure */
static inline int vio_cmo_alloc(struct vio_dev *viodev, size_t size) { unsigned long flags; size_t reserve_free = 0; size_t excess_free = 0; int ret = -ENOMEM; spin_lock_irqsave(&vio_cmo.lock, flags); /* Determine the amount of free entitlement available in reserve */ if (viodev->cmo.entitled > viodev->cmo.allocated) reserve_free = viodev->cmo.entitled - viodev->cmo.allocated; /* If spare is not fulfilled, the excess pool can not be used. */ if (vio_cmo.spare >= VIO_CMO_MIN_ENT) excess_free = vio_cmo.excess.free; /* The request can be satisfied */ if ((reserve_free + excess_free) >= size) { vio_cmo.curr += size; if (vio_cmo.curr > vio_cmo.high) vio_cmo.high = vio_cmo.curr; viodev->cmo.allocated += size; size -= min(reserve_free, size); vio_cmo.excess.free -= size; ret = 0; } spin_unlock_irqrestore(&vio_cmo.lock, flags); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings170100.00%1100.00%
Total170100.00%1100.00%

/** * vio_cmo_dealloc - deallocate IO memory from CMO-enable devices * @viodev: VIO device freeing IO memory * @size: size of deallocation * * IO memory is freed by the device back to the correct memory pools. * The spare pool is replenished first from either memory pool, then * the reserve pool is used to reduce device entitlement, the excess * pool is used to increase the reserve pool toward the desired entitlement * target, and then the remaining memory is returned to the pools. * */
static inline void vio_cmo_dealloc(struct vio_dev *viodev, size_t size) { unsigned long flags; size_t spare_needed = 0; size_t excess_freed = 0; size_t reserve_freed = size; size_t tmp; int balance = 0; spin_lock_irqsave(&vio_cmo.lock, flags); vio_cmo.curr -= size; /* Amount of memory freed from the excess pool */ if (viodev->cmo.allocated > viodev->cmo.entitled) { excess_freed = min(reserve_freed, (viodev->cmo.allocated - viodev->cmo.entitled)); reserve_freed -= excess_freed; } /* Remove allocation from device */ viodev->cmo.allocated -= (reserve_freed + excess_freed); /* Spare is a subset of the reserve pool, replenish it first. */ spare_needed = VIO_CMO_MIN_ENT - vio_cmo.spare; /* * Replenish the spare in the reserve pool from the excess pool. * This moves entitlement into the reserve pool. */ if (spare_needed && excess_freed) { tmp = min(excess_freed, spare_needed); vio_cmo.excess.size -= tmp; vio_cmo.reserve.size += tmp; vio_cmo.spare += tmp; excess_freed -= tmp; spare_needed -= tmp; balance = 1; } /* * Replenish the spare in the reserve pool from the reserve pool. * This removes entitlement from the device down to VIO_CMO_MIN_ENT, * if needed, and gives it to the spare pool. The amount of used * memory in this pool does not change. */ if (spare_needed && reserve_freed) { tmp = min3(spare_needed, reserve_freed, (viodev->cmo.entitled - VIO_CMO_MIN_ENT)); vio_cmo.spare += tmp; viodev->cmo.entitled -= tmp; reserve_freed -= tmp; spare_needed -= tmp; balance = 1; } /* * Increase the reserve pool until the desired allocation is met. * Move an allocation freed from the excess pool into the reserve * pool and schedule a balance operation. */ if (excess_freed && (vio_cmo.desired > vio_cmo.reserve.size)) { tmp = min(excess_freed, (vio_cmo.desired - vio_cmo.reserve.size)); vio_cmo.excess.size -= tmp; vio_cmo.reserve.size += tmp; excess_freed -= tmp; balance = 1; } /* Return memory from the excess pool to that pool */ if (excess_freed) vio_cmo.excess.free += excess_freed; if (balance) schedule_delayed_work(&vio_cmo.balance_q, VIO_CMO_BALANCE_DELAY); spin_unlock_irqrestore(&vio_cmo.lock, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings32699.69%150.00%
Hagen Paul Pfeifer10.31%150.00%
Total327100.00%2100.00%

/** * vio_cmo_entitlement_update - Manage system entitlement changes * * @new_entitlement: new system entitlement to attempt to accommodate * * Increases in entitlement will be used to fulfill the spare entitlement * and the rest is given to the excess pool. Decreases, if they are * possible, come from the excess pool and from unused device entitlement * * Returns: 0 on success, -ENOMEM when change can not be made */
int vio_cmo_entitlement_update(size_t new_entitlement) { struct vio_dev *viodev; struct vio_cmo_dev_entry *dev_ent; unsigned long flags; size_t avail, delta, tmp; spin_lock_irqsave(&vio_cmo.lock, flags); /* Entitlement increases */ if (new_entitlement > vio_cmo.entitled) { delta = new_entitlement - vio_cmo.entitled; /* Fulfill spare allocation */ if (vio_cmo.spare < VIO_CMO_MIN_ENT) { tmp = min(delta, (VIO_CMO_MIN_ENT - vio_cmo.spare)); vio_cmo.spare += tmp; vio_cmo.reserve.size += tmp; delta -= tmp; } /* Remaining new allocation goes to the excess pool */ vio_cmo.entitled += delta; vio_cmo.excess.size += delta; vio_cmo.excess.free += delta; goto out; } /* Entitlement decreases */ delta = vio_cmo.entitled - new_entitlement; avail = vio_cmo.excess.free; /* * Need to check how much unused entitlement each device can * sacrifice to fulfill entitlement change. */ list_for_each_entry(dev_ent, &vio_cmo.device_list, list) { if (avail >= delta) break; viodev = dev_ent->viodev; if ((viodev->cmo.entitled > viodev->cmo.allocated) && (viodev->cmo.entitled > VIO_CMO_MIN_ENT)) avail += viodev->cmo.entitled - max_t(size_t, viodev->cmo.allocated, VIO_CMO_MIN_ENT); } if (delta <= avail) { vio_cmo.entitled -= delta; /* Take entitlement from the excess pool first */ tmp = min(vio_cmo.excess.free, delta); vio_cmo.excess.size -= tmp; vio_cmo.excess.free -= tmp; delta -= tmp; /* * Remove all but VIO_CMO_MIN_ENT bytes from devices * until entitlement change is served */ list_for_each_entry(dev_ent, &vio_cmo.device_list, list) { if (!delta) break; viodev = dev_ent->viodev; tmp = 0; if ((viodev->cmo.entitled > viodev->cmo.allocated) && (viodev->cmo.entitled > VIO_CMO_MIN_ENT)) tmp = viodev->cmo.entitled - max_t(size_t, viodev->cmo.allocated, VIO_CMO_MIN_ENT); viodev->cmo.entitled -= min(tmp, delta); delta -= min(tmp, delta); } } else { spin_unlock_irqrestore(&vio_cmo.lock, flags); return -ENOMEM; } out: schedule_delayed_work(&vio_cmo.balance_q, 0); spin_unlock_irqrestore(&vio_cmo.lock, flags); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings402100.00%1100.00%
Total402100.00%1100.00%

/** * vio_cmo_balance - Balance entitlement among devices * * @work: work queue structure for this operation * * Any system entitlement above the minimum needed for devices, or * already allocated to devices, can be distributed to the devices. * The list of devices is iterated through to recalculate the desired * entitlement level and to determine how much entitlement above the * minimum entitlement is allocated to devices. * * Small chunks of the available entitlement are given to devices until * their requirements are fulfilled or there is no entitlement left to give. * Upon completion sizes of the reserve and excess pools are calculated. * * The system minimum entitlement level is also recalculated here. * Entitlement will be reserved for devices even after vio_bus_remove to * accommodate reloading the driver. The OF tree is walked to count the * number of devices present and this will remove entitlement for devices * that have actually left the system after having vio_bus_remove called. */
static void vio_cmo_balance(struct work_struct *work) { struct vio_cmo *cmo; struct vio_dev *viodev; struct vio_cmo_dev_entry *dev_ent; unsigned long flags; size_t avail = 0, level, chunk, need; int devcount = 0, fulfilled; cmo = container_of(work, struct vio_cmo, balance_q.work); spin_lock_irqsave(&vio_cmo.lock, flags); /* Calculate minimum entitlement and fulfill spare */ cmo->min = vio_cmo_num_OF_devs() * VIO_CMO_MIN_ENT; BUG_ON(cmo->min > cmo->entitled); cmo->spare = min_t(size_t, VIO_CMO_MIN_ENT, (cmo->entitled - cmo->min)); cmo->min += cmo->spare; cmo->desired = cmo->min; /* * Determine how much entitlement is available and reset device * entitlements */ avail = cmo->entitled - cmo->spare; list_for_each_entry(dev_ent, &vio_cmo.device_list, list) { viodev = dev_ent->viodev; devcount++; viodev->cmo.entitled = VIO_CMO_MIN_ENT; cmo->desired += (viodev->cmo.desired - VIO_CMO_MIN_ENT); avail -= max_t(size_t, viodev->cmo.allocated, VIO_CMO_MIN_ENT); } /* * Having provided each device with the minimum entitlement, loop * over the devices portioning out the remaining entitlement * until there is nothing left. */ level = VIO_CMO_MIN_ENT; while (avail) { fulfilled = 0; list_for_each_entry(dev_ent, &vio_cmo.device_list, list) { viodev = dev_ent->viodev; if (viodev->cmo.desired <= level) { fulfilled++; continue; } /* * Give the device up to VIO_CMO_BALANCE_CHUNK * bytes of entitlement, but do not exceed the * desired level of entitlement for the device. */ chunk = min_t(size_t, avail, VIO_CMO_BALANCE_CHUNK); chunk = min(chunk, (viodev->cmo.desired - viodev->cmo.entitled)); viodev->cmo.entitled += chunk; /* * If the memory for this entitlement increase was * already allocated to the device it does not come * from the available pool being portioned out. */ need = max(viodev->cmo.allocated, viodev->cmo.entitled)- max(viodev->cmo.allocated, level); avail -= need; } if (fulfilled == devcount) break; level += VIO_CMO_BALANCE_CHUNK; } /* Calculate new reserve and excess pool sizes */ cmo->reserve.size = cmo->min; cmo->excess.free = 0; cmo->excess.size = 0; need = 0; list_for_each_entry(dev_ent, &vio_cmo.device_list, list) { viodev = dev_ent->viodev; /* Calculated reserve size above the minimum entitlement */ if (viodev->cmo.entitled) cmo->reserve.size += (viodev->cmo.entitled - VIO_CMO_MIN_ENT); /* Calculated used excess entitlement */ if (viodev->cmo.allocated > viodev->cmo.entitled) need += viodev->cmo.allocated - viodev->cmo.entitled; } cmo->excess.size = cmo->entitled - cmo->reserve.size; cmo->excess.free = cmo->excess.size - need; cancel_delayed_work(to_delayed_work(work)); spin_unlock_irqrestore(&vio_cmo.lock, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings47799.79%150.00%
Jean Delvare10.21%150.00%
Total478100.00%2100.00%


static void *vio_dma_iommu_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); void *ret; if (vio_cmo_alloc(viodev, roundup(size, PAGE_SIZE))) { atomic_inc(&viodev->cmo.allocs_failed); return NULL; } ret = dma_iommu_ops.alloc(dev, size, dma_handle, flag, attrs); if (unlikely(ret == NULL)) { vio_cmo_dealloc(viodev, roundup(size, PAGE_SIZE)); atomic_inc(&viodev->cmo.allocs_failed); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings11594.26%250.00%
Andrzej Pietrasiewicz54.10%125.00%
Krzysztof Kozlowski21.64%125.00%
Total122100.00%4100.00%


static void vio_dma_iommu_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); dma_iommu_ops.free(dev, size, vaddr, dma_handle, attrs); vio_cmo_dealloc(viodev, roundup(size, PAGE_SIZE)); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings5588.71%250.00%
Andrzej Pietrasiewicz58.06%125.00%
Krzysztof Kozlowski23.23%125.00%
Total62100.00%4100.00%


static dma_addr_t vio_dma_iommu_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction direction, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); struct iommu_table *tbl; dma_addr_t ret = IOMMU_MAPPING_ERROR; tbl = get_iommu_table_base(dev); if (vio_cmo_alloc(viodev, roundup(size, IOMMU_PAGE_SIZE(tbl)))) { atomic_inc(&viodev->cmo.allocs_failed); return ret; } ret = dma_iommu_ops.map_page(dev, page, offset, size, direction, attrs); if (unlikely(dma_mapping_error(dev, ret))) { vio_cmo_dealloc(viodev, roundup(size, IOMMU_PAGE_SIZE(tbl))); atomic_inc(&viodev->cmo.allocs_failed); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings11475.50%116.67%
Alistair Popple2013.25%116.67%
Mark Nelson127.95%116.67%
Krzysztof Kozlowski21.32%116.67%
Stephen Rothwell21.32%116.67%
Christoph Hellwig10.66%116.67%
Total151100.00%6100.00%


static void vio_dma_iommu_unmap_page(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); struct iommu_table *tbl; tbl = get_iommu_table_base(dev); dma_iommu_ops.unmap_page(dev, dma_handle, size, direction, attrs); vio_cmo_dealloc(viodev, roundup(size, IOMMU_PAGE_SIZE(tbl))); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings5774.03%125.00%
Alistair Popple1620.78%125.00%
Krzysztof Kozlowski22.60%125.00%
Mark Nelson22.60%125.00%
Total77100.00%4100.00%


static int vio_dma_iommu_map_sg(struct device *dev, struct scatterlist *sglist, int nelems, enum dma_data_direction direction, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); struct iommu_table *tbl; struct scatterlist *sgl; int ret, count; size_t alloc_size = 0; tbl = get_iommu_table_base(dev); for_each_sg(sglist, sgl, nelems, count) alloc_size += roundup(sgl->length, IOMMU_PAGE_SIZE(tbl)); if (vio_cmo_alloc(viodev, alloc_size)) { atomic_inc(&viodev->cmo.allocs_failed); return 0; } ret = dma_iommu_ops.map_sg(dev, sglist, nelems, direction, attrs); if (unlikely(!ret)) { vio_cmo_dealloc(viodev, alloc_size); atomic_inc(&viodev->cmo.allocs_failed); return ret; } for_each_sg(sglist, sgl, ret, count) alloc_size -= roundup(sgl->dma_length, IOMMU_PAGE_SIZE(tbl)); if (alloc_size) vio_cmo_dealloc(viodev, alloc_size); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings15578.68%240.00%
Akinobu Mita2010.15%120.00%
Alistair Popple2010.15%120.00%
Krzysztof Kozlowski21.02%120.00%
Total197100.00%5100.00%


static void vio_dma_iommu_unmap_sg(struct device *dev, struct scatterlist *sglist, int nelems, enum dma_data_direction direction, unsigned long attrs) { struct vio_dev *viodev = to_vio_dev(dev); struct iommu_table *tbl; struct scatterlist *sgl; size_t alloc_size = 0; int count; tbl = get_iommu_table_base(dev); for_each_sg(sglist, sgl, nelems, count) alloc_size += roundup(sgl->dma_length, IOMMU_PAGE_SIZE(tbl)); dma_iommu_ops.unmap_sg(dev, sglist, nelems, direction, attrs); vio_cmo_dealloc(viodev, alloc_size); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings8074.07%125.00%
Alistair Popple1614.81%125.00%
Akinobu Mita109.26%125.00%
Krzysztof Kozlowski21.85%125.00%
Total108100.00%4100.00%


static int vio_dma_iommu_dma_supported(struct device *dev, u64 mask) { return dma_iommu_ops.dma_supported(dev, mask); }

Contributors

PersonTokensPropCommitsCommitProp
Nishanth Aravamudan24100.00%1100.00%
Total24100.00%1100.00%


static u64 vio_dma_get_required_mask(struct device *dev) { return dma_iommu_ops.get_required_mask(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Milton D. Miller II19100.00%1100.00%
Total19100.00%1100.00%

static const struct dma_map_ops vio_dma_mapping_ops = { .alloc = vio_dma_iommu_alloc_coherent, .free = vio_dma_iommu_free_coherent, .mmap = dma_direct_mmap_coherent, .map_sg = vio_dma_iommu_map_sg, .unmap_sg = vio_dma_iommu_unmap_sg, .map_page = vio_dma_iommu_map_page, .unmap_page = vio_dma_iommu_unmap_page, .dma_supported = vio_dma_iommu_dma_supported, .get_required_mask = vio_dma_get_required_mask, .mapping_error = dma_iommu_mapping_error, }; /** * vio_cmo_set_dev_desired - Set desired entitlement for a device * * @viodev: struct vio_dev for device to alter * @desired: new desired entitlement level in bytes * * For use by devices to request a change to their entitlement at runtime or * through sysfs. The desired entitlement level is changed and a balancing * of system resources is scheduled to run in the future. */
void vio_cmo_set_dev_desired(struct vio_dev *viodev, size_t desired) { unsigned long flags; struct vio_cmo_dev_entry *dev_ent; int found = 0; if (!firmware_has_feature(FW_FEATURE_CMO)) return; spin_lock_irqsave(&vio_cmo.lock, flags); if (desired < VIO_CMO_MIN_ENT) desired = VIO_CMO_MIN_ENT; /* * Changes will not be made for devices not in the device list. * If it is not in the device list, then no driver is loaded * for the device and it can not receive entitlement. */ list_for_each_entry(dev_ent, &vio_cmo.device_list, list) if (viodev == dev_ent->viodev) { found = 1; break; } if (!found) { spin_unlock_irqrestore(&vio_cmo.lock, flags); return; } /* Increase/decrease in desired device entitlement */ if (desired >= viodev->cmo.desired) { /* Just bump the bus and device values prior to a balance*/ vio_cmo.desired += desired - viodev->cmo.desired; viodev->cmo.desired = desired; } else { /* Decrease bus and device values for desired entitlement */ vio_cmo.desired -= viodev->cmo.desired - desired; viodev->cmo.desired = desired; /* * If less entitlement is desired than current entitlement, move * any reserve memory in the change region to the excess pool. */ if (viodev->cmo.entitled > desired) { vio_cmo.reserve.size -= viodev->cmo.entitled - desired; vio_cmo.excess.size += viodev->cmo.entitled - desired; /* * If entitlement moving from the reserve pool to the * excess pool is currently unused, add to the excess * free counter. */ if (viodev->cmo.allocated < viodev->cmo.entitled) vio_cmo.excess.free += viodev->cmo.entitled - max(viodev->cmo.allocated, desired); viodev->cmo.entitled = desired; } } schedule_delayed_work(&vio_cmo.balance_q, 0); spin_unlock_irqrestore(&vio_cmo.lock, flags); }

Contributors

PersonTokensPropCommitsCommitProp
Robert Jennings25195.44%150.00%
Julia Lawall124.56%150.00%
Total263100.00%2100.00%

/** * vio_cmo_bus_probe - Handle CMO specific bus probe activities * * @viodev - Pointer to struct vio_dev for device * * Determine the devices IO memory entitlement needs, attempting * to satisfy the system minimum entitlement at first and scheduling * a balance operation to take care of the rest at a later time. * * Returns: 0 on success, -EINVAL when device doesn't support CMO, and * -ENOMEM when entitlement is not available for device or * device entry. * */
static int vio_cmo_bus_probe(struct vio_dev *viodev) { struct vio_cmo_dev_entry *dev_ent; struct device *dev = &viodev->dev; struct iommu_table *tbl; struct vio_driver *viodrv = to_vio_driver(dev->driver); unsigned long flags; size_t size; bool dma_capable = false; tbl = get_iommu_table_base(dev); /* A device requires entitlement if it has a DMA window property */ switch (viodev->family) { case VDEVICE: if (of_get_property(viodev->dev.of_node, "ibm,my-dma-window", NULL)) dma_capable = true; break; case PFO: dma_capable = false; break; default: dev_warn(dev, "unknown device family: %d\n", viodev->family); BUG(); break; } /* Configure entitlement for the device. */ if (dma_capable) { /* Check that the driver is CMO enabled and get desired DMA */ if (!viodrv->get_desired_dma) { dev_err(dev, "%s: device driver does not support CMO\n", __func__); return -EINVAL; } viodev->cmo.desired = IOMMU_PAGE_ALIGN(viodrv->get_desired_dma(viodev), tbl); if (viodev->cmo.desired < VIO_CMO_MIN_ENT) viodev->cmo.desired = VIO_CMO_MIN_ENT; size = VIO_CMO_MIN_ENT; dev_ent = kmalloc(sizeof(struct vio_cmo_dev_entry), GFP_KERNEL); if (!dev_ent) return -ENOMEM; dev_ent->viodev = viodev; spin_lock_irqsave(&vio_cmo.lock, flags); list_add(&dev_ent->list, &vio_cmo.device_list); } else { viodev->cmo.desired = 0; size = 0; spin_lock_irqsave(&vio_cmo.lock, flags); } /* * If the needs for vio_cmo.min have not changed since they * were last set, the number of devices in the OF tree has * been constant and the IO memory for this is already in * the reserve pool. */ if (vio_cmo.min == ((vio_cmo_num_OF_devs() + 1) * VIO_CMO_MIN_ENT)) { /* Updated desired entitlement if device requires it */ if (size) vio_cmo.desired += (viodev->cmo.desired - VIO_CMO_MIN_ENT); } else { size_t