18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * \file drm_scatter.c
38c2ecf20Sopenharmony_ci * IOCTLs to manage scatter/gather memory
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * \author Gareth Hughes <gareth@valinux.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/*
98c2ecf20Sopenharmony_ci * Created: Mon Dec 18 23:20:54 2000 by gareth@valinux.com
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
128c2ecf20Sopenharmony_ci * All Rights Reserved.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
158c2ecf20Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
168c2ecf20Sopenharmony_ci * to deal in the Software without restriction, including without limitation
178c2ecf20Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
188c2ecf20Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
198c2ecf20Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
208c2ecf20Sopenharmony_ci *
218c2ecf20Sopenharmony_ci * The above copyright notice and this permission notice (including the next
228c2ecf20Sopenharmony_ci * paragraph) shall be included in all copies or substantial portions of the
238c2ecf20Sopenharmony_ci * Software.
248c2ecf20Sopenharmony_ci *
258c2ecf20Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
268c2ecf20Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
278c2ecf20Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
288c2ecf20Sopenharmony_ci * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
298c2ecf20Sopenharmony_ci * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
308c2ecf20Sopenharmony_ci * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
318c2ecf20Sopenharmony_ci * DEALINGS IN THE SOFTWARE.
328c2ecf20Sopenharmony_ci */
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#include <linux/mm.h>
358c2ecf20Sopenharmony_ci#include <linux/slab.h>
368c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#include <drm/drm.h>
398c2ecf20Sopenharmony_ci#include <drm/drm_drv.h>
408c2ecf20Sopenharmony_ci#include <drm/drm_print.h>
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#include "drm_legacy.h"
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define DEBUG_SCATTER 0
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic void drm_sg_cleanup(struct drm_sg_mem * entry)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	struct page *page;
498c2ecf20Sopenharmony_ci	int i;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	for (i = 0; i < entry->pages; i++) {
528c2ecf20Sopenharmony_ci		page = entry->pagelist[i];
538c2ecf20Sopenharmony_ci		if (page)
548c2ecf20Sopenharmony_ci			ClearPageReserved(page);
558c2ecf20Sopenharmony_ci	}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	vfree(entry->virtual);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	kfree(entry->busaddr);
608c2ecf20Sopenharmony_ci	kfree(entry->pagelist);
618c2ecf20Sopenharmony_ci	kfree(entry);
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_civoid drm_legacy_sg_cleanup(struct drm_device *dev)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg &&
678c2ecf20Sopenharmony_ci	    drm_core_check_feature(dev, DRIVER_LEGACY)) {
688c2ecf20Sopenharmony_ci		drm_sg_cleanup(dev->sg);
698c2ecf20Sopenharmony_ci		dev->sg = NULL;
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci#ifdef _LP64
738c2ecf20Sopenharmony_ci# define ScatterHandle(x) (unsigned int)((x >> 32) + (x & ((1L << 32) - 1)))
748c2ecf20Sopenharmony_ci#else
758c2ecf20Sopenharmony_ci# define ScatterHandle(x) (unsigned int)(x)
768c2ecf20Sopenharmony_ci#endif
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ciint drm_legacy_sg_alloc(struct drm_device *dev, void *data,
798c2ecf20Sopenharmony_ci			struct drm_file *file_priv)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct drm_scatter_gather *request = data;
828c2ecf20Sopenharmony_ci	struct drm_sg_mem *entry;
838c2ecf20Sopenharmony_ci	unsigned long pages, i, j;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	DRM_DEBUG("\n");
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	if (!drm_core_check_feature(dev, DRIVER_LEGACY))
888c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	if (!drm_core_check_feature(dev, DRIVER_SG))
918c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	if (request->size > SIZE_MAX - PAGE_SIZE)
948c2ecf20Sopenharmony_ci		return -EINVAL;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	if (dev->sg)
978c2ecf20Sopenharmony_ci		return -EINVAL;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
1008c2ecf20Sopenharmony_ci	if (!entry)
1018c2ecf20Sopenharmony_ci		return -ENOMEM;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	pages = (request->size + PAGE_SIZE - 1) / PAGE_SIZE;
1048c2ecf20Sopenharmony_ci	DRM_DEBUG("size=%ld pages=%ld\n", request->size, pages);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	entry->pages = pages;
1078c2ecf20Sopenharmony_ci	entry->pagelist = kcalloc(pages, sizeof(*entry->pagelist), GFP_KERNEL);
1088c2ecf20Sopenharmony_ci	if (!entry->pagelist) {
1098c2ecf20Sopenharmony_ci		kfree(entry);
1108c2ecf20Sopenharmony_ci		return -ENOMEM;
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	entry->busaddr = kcalloc(pages, sizeof(*entry->busaddr), GFP_KERNEL);
1148c2ecf20Sopenharmony_ci	if (!entry->busaddr) {
1158c2ecf20Sopenharmony_ci		kfree(entry->pagelist);
1168c2ecf20Sopenharmony_ci		kfree(entry);
1178c2ecf20Sopenharmony_ci		return -ENOMEM;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	entry->virtual = vmalloc_32(pages << PAGE_SHIFT);
1218c2ecf20Sopenharmony_ci	if (!entry->virtual) {
1228c2ecf20Sopenharmony_ci		kfree(entry->busaddr);
1238c2ecf20Sopenharmony_ci		kfree(entry->pagelist);
1248c2ecf20Sopenharmony_ci		kfree(entry);
1258c2ecf20Sopenharmony_ci		return -ENOMEM;
1268c2ecf20Sopenharmony_ci	}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	/* This also forces the mapping of COW pages, so our page list
1298c2ecf20Sopenharmony_ci	 * will be valid.  Please don't remove it...
1308c2ecf20Sopenharmony_ci	 */
1318c2ecf20Sopenharmony_ci	memset(entry->virtual, 0, pages << PAGE_SHIFT);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	entry->handle = ScatterHandle((unsigned long)entry->virtual);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	DRM_DEBUG("handle  = %08lx\n", entry->handle);
1368c2ecf20Sopenharmony_ci	DRM_DEBUG("virtual = %p\n", entry->virtual);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	for (i = (unsigned long)entry->virtual, j = 0; j < pages;
1398c2ecf20Sopenharmony_ci	     i += PAGE_SIZE, j++) {
1408c2ecf20Sopenharmony_ci		entry->pagelist[j] = vmalloc_to_page((void *)i);
1418c2ecf20Sopenharmony_ci		if (!entry->pagelist[j])
1428c2ecf20Sopenharmony_ci			goto failed;
1438c2ecf20Sopenharmony_ci		SetPageReserved(entry->pagelist[j]);
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	request->handle = entry->handle;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	dev->sg = entry;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci#if DEBUG_SCATTER
1518c2ecf20Sopenharmony_ci	/* Verify that each page points to its virtual address, and vice
1528c2ecf20Sopenharmony_ci	 * versa.
1538c2ecf20Sopenharmony_ci	 */
1548c2ecf20Sopenharmony_ci	{
1558c2ecf20Sopenharmony_ci		int error = 0;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		for (i = 0; i < pages; i++) {
1588c2ecf20Sopenharmony_ci			unsigned long *tmp;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci			tmp = page_address(entry->pagelist[i]);
1618c2ecf20Sopenharmony_ci			for (j = 0;
1628c2ecf20Sopenharmony_ci			     j < PAGE_SIZE / sizeof(unsigned long);
1638c2ecf20Sopenharmony_ci			     j++, tmp++) {
1648c2ecf20Sopenharmony_ci				*tmp = 0xcafebabe;
1658c2ecf20Sopenharmony_ci			}
1668c2ecf20Sopenharmony_ci			tmp = (unsigned long *)((u8 *) entry->virtual +
1678c2ecf20Sopenharmony_ci						(PAGE_SIZE * i));
1688c2ecf20Sopenharmony_ci			for (j = 0;
1698c2ecf20Sopenharmony_ci			     j < PAGE_SIZE / sizeof(unsigned long);
1708c2ecf20Sopenharmony_ci			     j++, tmp++) {
1718c2ecf20Sopenharmony_ci				if (*tmp != 0xcafebabe && error == 0) {
1728c2ecf20Sopenharmony_ci					error = 1;
1738c2ecf20Sopenharmony_ci					DRM_ERROR("Scatter allocation error, "
1748c2ecf20Sopenharmony_ci						  "pagelist does not match "
1758c2ecf20Sopenharmony_ci						  "virtual mapping\n");
1768c2ecf20Sopenharmony_ci				}
1778c2ecf20Sopenharmony_ci			}
1788c2ecf20Sopenharmony_ci			tmp = page_address(entry->pagelist[i]);
1798c2ecf20Sopenharmony_ci			for (j = 0;
1808c2ecf20Sopenharmony_ci			     j < PAGE_SIZE / sizeof(unsigned long);
1818c2ecf20Sopenharmony_ci			     j++, tmp++) {
1828c2ecf20Sopenharmony_ci				*tmp = 0;
1838c2ecf20Sopenharmony_ci			}
1848c2ecf20Sopenharmony_ci		}
1858c2ecf20Sopenharmony_ci		if (error == 0)
1868c2ecf20Sopenharmony_ci			DRM_ERROR("Scatter allocation matches pagelist\n");
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci#endif
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	return 0;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci      failed:
1938c2ecf20Sopenharmony_ci	drm_sg_cleanup(entry);
1948c2ecf20Sopenharmony_ci	return -ENOMEM;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ciint drm_legacy_sg_free(struct drm_device *dev, void *data,
1988c2ecf20Sopenharmony_ci		       struct drm_file *file_priv)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct drm_scatter_gather *request = data;
2018c2ecf20Sopenharmony_ci	struct drm_sg_mem *entry;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	if (!drm_core_check_feature(dev, DRIVER_LEGACY))
2048c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	if (!drm_core_check_feature(dev, DRIVER_SG))
2078c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	entry = dev->sg;
2108c2ecf20Sopenharmony_ci	dev->sg = NULL;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	if (!entry || entry->handle != request->handle)
2138c2ecf20Sopenharmony_ci		return -EINVAL;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	DRM_DEBUG("virtual  = %p\n", entry->virtual);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	drm_sg_cleanup(entry);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	return 0;
2208c2ecf20Sopenharmony_ci}
221