Hello, I modified some things in the VIA DRM driver to get it working properly on my K8M800 Unichrome Pro chipset under Linux. I also got it working on a x86_64 kernel with a 32-bit usermode system.
Could someone on this list please look through my patches and maybe commit some things? I hope my work can be useful to others. Below is a description of each patch. They are largely independent of eachother. All patches are against the current CVS. 01_via_unichrome_pro Trivial patch to make the VIA driver recognize my PCI id as a Unichrome Pro chipset. This makes IRQ handling work properly and avoids the kernel message irq 201: nobody cared (try booting with the "irqpoll" option) 02_via_verifier_bugfix Trivial fix to the VIA command verifier. The bug caused it to accept some invalid commands on i386 and reject valid commands on x86_64. 03_via_mm_cleanup Rework the FB and AGP memory management in the VIA driver so that it no longer blindly passes kernel pointers to and from userspace. This was a security issue on i386 and a fundamental problem with 32-bit compatibility on x86_64. 04_via_ioctl_security Enable privilege checking on those ioctls that seem to be intended for the X server only. Don't know if there was a particular reason why this wasn't done before. 05_via_futex_niceabort Avoid Oops and X server crash when something goes wrong during DRM initialization. 06_via_compat32 Compatibility wrappers around the VIA ioctls to make it work with a 32-bit usermode system on a x86_64 kernel. Thanks, Joris.
diff -urN -U 5 drm.orig/shared-core/via_map.c drm/shared-core/via_map.c --- drm.orig/shared-core/via_map.c 2005-07-15 23:22:51.000000000 +0200 +++ drm/shared-core/via_map.c 2005-08-07 11:34:25.000000000 +0200 @@ -65,11 +65,12 @@ init->sarea_priv_offset); dev_priv->agpAddr = init->agpAddr; via_init_futex( dev_priv ); - dev_priv->pro_group_a = (dev->pdev->device == 0x3118); + dev_priv->pro_group_a = (dev->pdev->device == 0x3118 || + dev->pdev->device == 0x3108); dev->dev_private = (void *)dev_priv; return 0; }
diff -urN -U 5 drm.prev/shared-core/via_verifier.c drm/shared-core/via_verifier.c --- drm.prev/shared-core/via_verifier.c 2005-04-18 10:26:00.000000000 +0200 +++ drm/shared-core/via_verifier.c 2005-08-13 19:17:06.000000000 +0200 @@ -244,11 +244,11 @@ static __inline__ int eat_words(const uint32_t **buf, const uint32_t *buf_end, unsigned num_words) { - if ((*buf - buf_end) >= num_words) { + if ((buf_end - *buf) >= num_words) { *buf += num_words; return 0; } DRM_ERROR("Illegal termination of DMA command buffer\n"); return 1;
diff -urN -U 5 drm.orig/shared-core/via_mm.c drm/shared-core/via_mm.c --- drm.orig/shared-core/via_mm.c 2005-07-15 23:22:51.000000000 +0200 +++ drm/shared-core/via_mm.c 2005-08-07 11:57:33.000000000 +0200 @@ -23,52 +23,124 @@ */ #include "drmP.h" #include "via_drm.h" #include "via_drv.h" #include "via_ds.h" -#include "via_mm.h" #define MAX_CONTEXT 100 +#define MAX_MEMBLOCK_INDEX 5000 + +/* Structure which maps indices to PMemBlock pointers. + index_base is used to make indices globally unique among + multiple mem_block_map_t structures, and to avoid ever using + the special value 0 as an index. */ +typedef struct { + unsigned int index_base; + PMemBlock m[MAX_MEMBLOCK_INDEX]; +} mem_block_map_t; typedef struct { int used; int context; - set_t *sets[2]; /* 0 for frame buffer, 1 for AGP , 2 for System */ + mem_block_map_t *map[2]; /* 0 for frame buffer, 1 for AGP */ } via_context_t; static via_context_t global_ppriv[MAX_CONTEXT]; static int via_agp_alloc(drm_via_mem_t * mem); static int via_agp_free(drm_via_mem_t * mem); static int via_fb_alloc(drm_via_mem_t * mem); static int via_fb_free(drm_via_mem_t * mem); -static int add_alloc_set(int context, int type, unsigned int val) +/* + * Allocate a mem_block_map_t and initialize it to make all indices + * point to NULL. index_base must be non-zero + */ +static mem_block_map_t * via_mem_block_map_init(unsigned int index_base) { - int i, retval = 0; + mem_block_map_t *map; + int i; - for (i = 0; i < MAX_CONTEXT; i++) { - if (global_ppriv[i].used && global_ppriv[i].context == context) { - retval = via_setAdd(global_ppriv[i].sets[type], val); - break; - } + map = drm_alloc(sizeof(mem_block_map_t), DRM_MEM_DRIVER); + if (map) { + map->index_base = index_base; + for (i = 0; i < MAX_MEMBLOCK_INDEX; i++) + map->m[i] = NULL; } - - return retval; + return map; } -static int del_alloc_set(int context, int type, unsigned int val) +/* + * Destroy a mem_block_map_t and release allocated memory. + */ +static void via_mem_block_map_destroy(mem_block_map_t *map) { - int i, retval = 0; + drm_free(map, sizeof(mem_block_map_t), DRM_MEM_DRIVER); +} - for (i = 0; i < MAX_CONTEXT; i++) - if (global_ppriv[i].used && global_ppriv[i].context == context) { - retval = via_setDel(global_ppriv[i].sets[type], val); - break; + +/* + * Add a pointer to a mem_block_map_t and return the new index, + * or return 0 if all slots are in use. + */ +static unsigned int via_mem_block_map_add(mem_block_map_t *map, PMemBlock block) +{ + int i; + for (i = 0; i < MAX_MEMBLOCK_INDEX; i++) + if (!map->m[i]) { + map->m[i] = block; + return i + map->index_base; } + return 0; +} - return retval; +/* + * Remove a pointer from a given index in a mem_block_map_t and return + * the removed pointer, or return NULL if no pointer was present. + */ +static PMemBlock via_mem_block_map_remove(mem_block_map_t *map, + unsigned int index) +{ + PMemBlock block; + if (index < map->index_base) + return NULL; + index -= map->index_base; + if (index >= MAX_MEMBLOCK_INDEX) + return NULL; + block = map->m[index]; + map->m[index] = NULL; + return block; +} + +/* + * Add this block pointer to the allocation map of the specified context + * and return the block index, or return 0 for failure. + */ +static unsigned int add_alloc_map(int context, int type, PMemBlock block) +{ + int i; + for (i = 0; i < MAX_CONTEXT; i++) { + via_context_t *ctx = &global_ppriv[i]; + if (ctx->used && ctx->context == context) + return via_mem_block_map_add(ctx->map[type], block); + } + return 0; +} + +/* + * Lookup and remove the specified block index from the context allocation map, + * and return the corresponding block pointer, or return NULL for not found. + */ +static PMemBlock del_alloc_map(int context, int type, unsigned int index) +{ + int i; + for (i = 0; i < MAX_CONTEXT; i++) { + via_context_t *ctx = &global_ppriv[i]; + if (ctx->used && ctx->context == context) + return via_mem_block_map_remove(ctx->map[type], index); + } + return NULL; } /* agp memory management */ static memHeap_t *AgpHeap = NULL; @@ -107,32 +179,37 @@ int i; for (i = 0; i < MAX_CONTEXT; i++) if (global_ppriv[i].used && (global_ppriv[i].context == context)) - break; + return 1; - if (i >= MAX_CONTEXT) { - for (i = 0; i < MAX_CONTEXT; i++) { - if (!global_ppriv[i].used) { - global_ppriv[i].context = context; - global_ppriv[i].used = 1; - global_ppriv[i].sets[0] = via_setInit(); - global_ppriv[i].sets[1] = via_setInit(); - DRM_DEBUG("init allocation set, socket=%d," - " context = %d\n", i, context); - break; + for (i = 0; i < MAX_CONTEXT; i++) { + if (!global_ppriv[i].used) { + global_ppriv[i].map[0] = via_mem_block_map_init(2*i*MAX_MEMBLOCK_INDEX + 1); + global_ppriv[i].map[1] = via_mem_block_map_init((2*i+1)*MAX_MEMBLOCK_INDEX + 1); + if ( global_ppriv[i].map[0] == NULL || + global_ppriv[i].map[1] == NULL ) { + if (global_ppriv[i].map[0] != NULL) + via_mem_block_map_destroy(global_ppriv[i].map[0]); + if (global_ppriv[i].map[1] != NULL) + via_mem_block_map_destroy(global_ppriv[i].map[1]); + DRM_DEBUG("init allocation set failed," + " no memory, context=%d\n", context); + return 0; } - } - - if ((i >= MAX_CONTEXT) || (global_ppriv[i].sets[0] == NULL) || - (global_ppriv[i].sets[1] == NULL)) { - return 0; + global_ppriv[i].context = context; + global_ppriv[i].used = 1; + DRM_DEBUG("init allocation set, socket=%d," + " context = %d\n", i, context); + return 1; } } - return 1; + DRM_DEBUG("init allocation set failed, no free socket, context=%d\n", + context); + return 0; } int via_final_context(struct drm_device *dev, int context) { int i; @@ -142,35 +219,36 @@ if (global_ppriv[i].used && (global_ppriv[i].context == context)) break; if (i < MAX_CONTEXT) { - set_t *set; - ITEM_TYPE item; - int retval; + mem_block_map_t *map; + int index; DRM_DEBUG("find socket %d, context = %d\n", i, context); /* Video Memory */ - set = global_ppriv[i].sets[0]; - retval = via_setFirst(set, &item); - while (retval) { - DRM_DEBUG("free video memory 0x%lx\n", item); - via_mmFreeMem((PMemBlock) item); - retval = via_setNext(set, &item); + map = global_ppriv[i].map[0]; + for (index = 0; index < MAX_MEMBLOCK_INDEX; index++) { + PMemBlock item = map->m[index]; + if (item) { + DRM_DEBUG("free video memory %p\n", item); + via_mmFreeMem(item); + } } - via_setDestroy(set); + via_mem_block_map_destroy(map); /* AGP Memory */ - set = global_ppriv[i].sets[1]; - retval = via_setFirst(set, &item); - while (retval) { - DRM_DEBUG("free agp memory 0x%lx\n", item); - via_mmFreeMem((PMemBlock) item); - retval = via_setNext(set, &item); + map = global_ppriv[i].map[1]; + for (index = 0; index < MAX_MEMBLOCK_INDEX; index++) { + PMemBlock item = map->m[index]; + if (item) { + DRM_DEBUG("free agp memory %p\n", item); + via_mmFreeMem(item); + } } - via_setDestroy(set); + via_mem_block_map_destroy(map); global_ppriv[i].used = 0; } via_release_futex(dev_priv, context); @@ -215,77 +293,67 @@ return -EFAULT; } static int via_fb_alloc(drm_via_mem_t * mem) { - drm_via_mm_t fb; PMemBlock block; int retval = 0; if (!FBHeap) return -1; - fb.size = mem->size; - fb.context = mem->context; - - block = via_mmAllocMem(FBHeap, fb.size, 5, 0); + block = via_mmAllocMem(FBHeap, mem->size, 5, 0); if (block) { - fb.offset = block->ofs; - fb.free = (unsigned long)block; - if (!add_alloc_set(fb.context, VIDEO, fb.free)) { + int index = add_alloc_map(mem->context, VIDEO, block); + if (!index) { DRM_DEBUG("adding to allocation set fails\n"); - via_mmFreeMem((PMemBlock) fb.free); + via_mmFreeMem(block); retval = -1; + } else { + mem->offset = block->ofs; + mem->index = index; } } else { - fb.offset = 0; - fb.size = 0; - fb.free = 0; + mem->offset = 0; + mem->index = 0; retval = -1; } - mem->offset = fb.offset; - mem->index = fb.free; - - DRM_DEBUG("alloc fb, size = %d, offset = %d\n", fb.size, - (int)fb.offset); + DRM_DEBUG("alloc fb, size = %d, offset = %d\n", + mem->size, (unsigned int)mem->offset); return retval; } static int via_agp_alloc(drm_via_mem_t * mem) { - drm_via_mm_t agp; PMemBlock block; int retval = 0; if (!AgpHeap) return -1; - agp.size = mem->size; - agp.context = mem->context; - - block = via_mmAllocMem(AgpHeap, agp.size, 5, 0); + block = via_mmAllocMem(AgpHeap, mem->size, 5, 0); if (block) { - agp.offset = block->ofs; - agp.free = (unsigned long)block; - if (!add_alloc_set(agp.context, AGP, agp.free)) { + int index = add_alloc_map(mem->context, AGP, block); + if (!index) { DRM_DEBUG("adding to allocation set fails\n"); - via_mmFreeMem((PMemBlock) agp.free); + via_mmFreeMem(block); retval = -1; + } else { + mem->offset = block->ofs; + mem->index = index; } } else { - agp.offset = 0; - agp.size = 0; - agp.free = 0; + mem->offset = 0; + mem->index = 0; + /* should we set retval = -1 ?? */ } - mem->offset = agp.offset; - mem->index = agp.free; + DRM_DEBUG("alloc agp, size = %d, offset = %d\n", + mem->size, (unsigned int)mem->offset); - DRM_DEBUG("alloc agp, size = %d, offset = %d\n", agp.size, - (unsigned int)agp.offset); return retval; } int via_mem_free(DRM_IOCTL_ARGS) { @@ -309,53 +377,48 @@ return -EFAULT; } static int via_fb_free(drm_via_mem_t * mem) { - drm_via_mm_t fb; + PMemBlock block; int retval = 0; if (!FBHeap) { return -1; } - fb.free = mem->index; - fb.context = mem->context; - - if (!fb.free) { + if (!mem->index) { return -1; } - via_mmFreeMem((PMemBlock) fb.free); - - if (!del_alloc_set(fb.context, VIDEO, fb.free)) { + block = del_alloc_map(mem->context, VIDEO, mem->index); + if (!block) { retval = -1; + } else { + via_mmFreeMem(block); } - DRM_DEBUG("free fb, free = %ld\n", fb.free); + DRM_DEBUG("free fb, index = %ld\n", mem->index); return retval; } static int via_agp_free(drm_via_mem_t * mem) { - drm_via_mm_t agp; - + PMemBlock block; int retval = 0; - agp.free = mem->index; - agp.context = mem->context; - - if (!agp.free) + if (!mem->index) return -1; - via_mmFreeMem((PMemBlock) agp.free); - - if (!del_alloc_set(agp.context, AGP, agp.free)) { + block = del_alloc_map(mem->context, AGP, mem->index); + if (!block) { retval = -1; + } else { + via_mmFreeMem(block); } - DRM_DEBUG("free agp, free = %ld\n", agp.free); + DRM_DEBUG("free agp, index = %ld\n", mem->index); return retval; } diff -urN -U 5 drm.orig/shared-core/via_mm.h drm/shared-core/via_mm.h --- drm.orig/shared-core/via_mm.h 2005-05-23 22:56:54.000000000 +0200 +++ drm/shared-core/via_mm.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,40 +0,0 @@ -/* - * Copyright 1998-2003 VIA Technologies, Inc. All Rights Reserved. - * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved. - * - * 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, sub license, - * 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * VIA, S3 GRAPHICS, AND/OR ITS SUPPLIERS 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. - */ -#ifndef _via_drm_mm_h_ -#define _via_drm_mm_h_ - -typedef struct { - unsigned int context; - unsigned int size; - unsigned long offset; - unsigned long free; -} drm_via_mm_t; - -typedef struct { - unsigned int size; - unsigned long handle; - void *virtual; -} drm_via_dma_t; - -#endif
diff -urN -U 5 drm.orig/shared-core/via_drv.c drm/shared-core/via_drv.c --- drm.orig/shared-core/via_drv.c 2005-08-05 05:50:23.000000000 +0200 +++ drm/shared-core/via_drv.c 2005-08-07 12:09:07.000000000 +0200 @@ -38,22 +38,22 @@ static struct pci_device_id pciidlist[] = { viadrv_PCI_IDS }; static drm_ioctl_desc_t ioctls[] = { - [DRM_IOCTL_NR(DRM_VIA_ALLOCMEM)] = {via_mem_alloc, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_FREEMEM)] = {via_mem_free, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_AGP_INIT)] = {via_agp_init, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_FB_INIT)] = {via_fb_init, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_MAP_INIT)] = {via_map_init, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_DEC_FUTEX)] = {via_decoder_futex, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_DMA_INIT)] = {via_dma_init, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_CMDBUFFER)] = {via_cmdbuffer, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_FLUSH)] = {via_flush_ioctl, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_PCICMD)] = {via_pci_cmdbuffer, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_CMDBUF_SIZE)] = {via_cmdbuf_size, 1, 0, 0}, - [DRM_IOCTL_NR(DRM_VIA_WAIT_IRQ)] = {via_wait_irq, 1, 0, 0} + [DRM_IOCTL_NR(DRM_VIA_ALLOCMEM)] = {via_mem_alloc, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_FREEMEM)] = {via_mem_free, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_AGP_INIT)] = {via_agp_init, 1, 1, 1}, + [DRM_IOCTL_NR(DRM_VIA_FB_INIT)] = {via_fb_init, 1, 1, 1}, + [DRM_IOCTL_NR(DRM_VIA_MAP_INIT)] = {via_map_init, 1, 1, 1}, + [DRM_IOCTL_NR(DRM_VIA_DEC_FUTEX)] = {via_decoder_futex, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_DMA_INIT)] = {via_dma_init, 1, 1, 1}, + [DRM_IOCTL_NR(DRM_VIA_CMDBUFFER)] = {via_cmdbuffer, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_FLUSH)] = {via_flush_ioctl, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_PCICMD)] = {via_pci_cmdbuffer, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_CMDBUF_SIZE)] = {via_cmdbuf_size, 1, 0, 0}, + [DRM_IOCTL_NR(DRM_VIA_WAIT_IRQ)] = {via_wait_irq, 1, 0, 0} }; static int probe(struct pci_dev *pdev, const struct pci_device_id *ent); static struct drm_driver driver = { .driver_features =
diff -urN -U 5 drm.prev/shared-core/via_video.c drm/shared-core/via_video.c --- drm.prev/shared-core/via_video.c 2005-07-15 23:22:51.000000000 +0200 +++ drm/shared-core/via_video.c 2005-08-13 19:24:55.000000000 +0200 @@ -51,10 +51,14 @@ via_release_futex(drm_via_private_t *dev_priv, int context) { unsigned int i; volatile int *lock; + /* don't do anything if initialization was not completed */ + if (!dev_priv->sarea_priv) + return; + for (i=0; i < VIA_NR_XVMC_LOCKS; ++i) { lock = (int *) XVMCLOCKPTR(dev_priv->sarea_priv, i); if ( (_DRM_LOCKING_CONTEXT( *lock ) == context)) { if (_DRM_LOCK_IS_HELD( *lock ) && (*lock & _DRM_LOCK_CONT)) { DRM_WAKEUP( &(dev_priv->decoder_queue[i]));
diff -urN -U 5 drm.prev/linux-core/Makefile.kernel drm/linux-core/Makefile.kernel --- drm.prev/linux-core/Makefile.kernel 2005-07-20 23:17:47.000000000 +0200 +++ drm/linux-core/Makefile.kernel 2005-08-13 19:26:22.000000000 +0200 @@ -30,10 +30,11 @@ drm-objs += drm_ioc32.o radeon-objs += radeon_ioc32.o mga-objs += mga_ioc32.o r128-objs += r128_ioc32.o i915-objs += i915_ioc32.o +via-objs += via_ioc32.o endif obj-m += drm.o obj-$(CONFIG_DRM_TDFX) += tdfx.o obj-$(CONFIG_DRM_R128) += r128.o diff -urN -U 5 drm.prev/shared-core/via_drv.c drm/shared-core/via_drv.c --- drm.prev/shared-core/via_drv.c 2005-08-13 19:22:57.000000000 +0200 +++ drm/shared-core/via_drv.c 2005-08-13 19:26:22.000000000 +0200 @@ -79,10 +79,13 @@ .release = drm_release, .ioctl = drm_ioctl, .mmap = drm_mmap, .poll = drm_poll, .fasync = drm_fasync, +#ifdef CONFIG_COMPAT + .compat_ioctl = via_compat_ioctl, +#endif }, .pci_driver = { .name = DRIVER_NAME, .id_table = pciidlist, .probe = probe, diff -urN -U 5 drm.prev/shared-core/via_drv.h drm/shared-core/via_drv.h --- drm.prev/shared-core/via_drv.h 2005-08-12 16:19:33.000000000 +0200 +++ drm/shared-core/via_drv.h 2005-08-13 19:26:22.000000000 +0200 @@ -112,7 +112,9 @@ extern int via_driver_dma_quiescent(drm_device_t * dev); extern void via_init_futex(drm_via_private_t *dev_priv); extern void via_cleanup_futex(drm_via_private_t *dev_priv); extern void via_release_futex(drm_via_private_t *dev_priv, int context); +extern long via_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); #endif diff -urN -U 5 drm.prev/shared-core/via_ioc32.c drm/shared-core/via_ioc32.c --- drm.prev/shared-core/via_ioc32.c 1970-01-01 01:00:00.000000000 +0100 +++ drm/shared-core/via_ioc32.c 2005-08-13 19:27:25.000000000 +0200 @@ -0,0 +1,241 @@ +/* + * 32-bit ioctl compatibility routines for the VIA DRM driver. + */ + +#include <linux/compat.h> +#include <linux/ioctl32.h> + +#include "drmP.h" +#include "drm.h" +#include "via_drm.h" +#include "via_drv.h" + +#define VIA_IOCTL32_ARGS struct file *file, unsigned int cmd, unsigned long arg + +/* allocate memory for 64-bit structure */ +#define VIA_IOCTL32_INIT() { \ + req32 = (__typeof(*req32) __user *) arg; \ + req = compat_alloc_user_space(sizeof(*req)); \ + if (!access_ok(VERIFY_WRITE, req, sizeof(*req))) \ + return -EFAULT; \ +} + +/* copy simple field from 32-bit to 64-bit structure */ +#define VIA_IOCTL32_LOAD(f) { \ + __typeof__(req32->f) __val; \ + if (__get_user(__val, &(req32->f)) || \ + __put_user(__val, &(req->f))) \ + return -EFAULT; \ +} + +/* copy pointer from 32-bit to 64-bit structure */ +#define VIA_IOCTL32_LOAD_POINTER(f) { \ + uint32_t __val; \ + if (__get_user(__val, &(req32->f)) || \ + __put_user((__typeof__(req->f))(unsigned long)__val, &(req->f))) \ + return -EFAULT; \ +} + +/* call 64-bit drm ioctl */ +#define VIA_IOCTL32_CALL(fn) \ + drm_ioctl(file->f_dentry->d_inode, file, (fn), (unsigned long) req) + +typedef struct { + uint32_t context; + uint32_t type; + uint32_t size; + uint32_t index; + uint32_t offset; +} drm_compat_via_mem_t; + +typedef struct { + int32_t func; + uint32_t sarea_priv_offset; + uint32_t fb_offset; + uint32_t mmio_offset; + uint32_t agpAddr; +} drm_compat_via_init_t; + +typedef struct { + int32_t func; + uint32_t offset; + uint32_t size; + uint32_t reg_pause_addr; +} drm_compat_via_dma_init_t; + +typedef struct { + uint32_t buf; + uint32_t size; +} drm_compat_via_cmdbuffer_t; + +static int compat_via_mem_alloc(VIA_IOCTL32_ARGS) +{ + drm_compat_via_mem_t __user *req32; + drm_via_mem_t __user *req; + drm_via_mem_t mem; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD(context) + VIA_IOCTL32_LOAD(type) + VIA_IOCTL32_LOAD(size) + VIA_IOCTL32_LOAD_POINTER(index) + VIA_IOCTL32_LOAD_POINTER(offset) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_ALLOCMEM); + + if (ret >= 0) { + /* convert modified request buffer back to 32-bit */ + + if (copy_from_user(&mem, req, sizeof(mem))) + return -EFAULT; + + if ( (mem.index | mem.offset) & 0xffffffff00000000L ) { + /* got out-of-range return value */ + VIA_IOCTL32_CALL(DRM_IOCTL_VIA_FREEMEM); + DRM_ERROR("via_ioc32: " + "out-of-range return value from via_mem_alloc\n"); + return -EFAULT; + } + + if ( __put_user(mem.context, &(req32->context)) || + __put_user(mem.type, &(req32->type)) || + __put_user(mem.size, &(req32->size)) || + __put_user((uint32_t)mem.index, &(req32->index)) || + __put_user((uint32_t)mem.offset, &(req32->offset)) ) + return -EFAULT; + } + + return ret; +} + +static int compat_via_mem_free(VIA_IOCTL32_ARGS) +{ + drm_compat_via_mem_t __user *req32; + drm_via_mem_t __user *req; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD(context) + VIA_IOCTL32_LOAD(type) + VIA_IOCTL32_LOAD(size) + VIA_IOCTL32_LOAD_POINTER(index) + VIA_IOCTL32_LOAD_POINTER(offset) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_FREEMEM); + + return ret; +} + +static int compat_via_map_init(VIA_IOCTL32_ARGS) +{ + drm_compat_via_init_t __user *req32; + drm_via_init_t __user *req; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD(func) + VIA_IOCTL32_LOAD_POINTER(sarea_priv_offset) + VIA_IOCTL32_LOAD_POINTER(fb_offset) + VIA_IOCTL32_LOAD_POINTER(mmio_offset) + VIA_IOCTL32_LOAD_POINTER(agpAddr) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_MAP_INIT); + + return ret; +} + +static int compat_via_dma_init(VIA_IOCTL32_ARGS) +{ + drm_compat_via_dma_init_t __user *req32; + drm_via_dma_init_t __user *req; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD(func) + VIA_IOCTL32_LOAD_POINTER(offset) + VIA_IOCTL32_LOAD_POINTER(size) + VIA_IOCTL32_LOAD_POINTER(reg_pause_addr) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_DMA_INIT); + + return ret; +} + +static int compat_via_cmdbuffer(VIA_IOCTL32_ARGS) +{ + drm_compat_via_cmdbuffer_t __user *req32; + drm_via_cmdbuffer_t __user *req; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD_POINTER(buf) + VIA_IOCTL32_LOAD_POINTER(size) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_CMDBUFFER); + + return ret; +} + +static int compat_via_pci_cmdbuffer(VIA_IOCTL32_ARGS) +{ + drm_compat_via_cmdbuffer_t __user *req32; + drm_via_cmdbuffer_t __user *req; + int ret; + + VIA_IOCTL32_INIT() + VIA_IOCTL32_LOAD_POINTER(buf) + VIA_IOCTL32_LOAD_POINTER(size) + + ret = VIA_IOCTL32_CALL(DRM_IOCTL_VIA_PCICMD); + + return ret; +} + +static drm_ioctl_compat_t *via_compat_ioctls[] = { + [DRM_VIA_ALLOCMEM] = compat_via_mem_alloc, + [DRM_VIA_FREEMEM] = compat_via_mem_free, + [DRM_VIA_AGP_INIT] = NULL, + [DRM_VIA_FB_INIT] = NULL, + [DRM_VIA_MAP_INIT] = compat_via_map_init, + [DRM_VIA_DEC_FUTEX] = NULL, + [DRM_VIA_DMA_INIT] = compat_via_dma_init, + [DRM_VIA_CMDBUFFER] = compat_via_cmdbuffer, + [DRM_VIA_FLUSH] = NULL, + [DRM_VIA_PCICMD] = compat_via_pci_cmdbuffer, + [DRM_VIA_CMDBUF_SIZE] = NULL, + [DRM_VIA_WAIT_IRQ] = NULL +}; + +/** + * Called whenever a 32-bit process running under a 64-bit kernel + * performs an ioctl on /dev/dri/card<n>. + * + * \param filp file pointer. + * \param cmd command. + * \param arg user argument. + * \return zero on success or negative number on failure. + */ +long via_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + unsigned int nr = DRM_IOCTL_NR(cmd); + drm_ioctl_compat_t *fn = NULL; + int ret; + + if (nr < DRM_COMMAND_BASE) + return drm_compat_ioctl(filp, cmd, arg); + + if (nr < DRM_COMMAND_BASE + DRM_ARRAY_SIZE(via_compat_ioctls)) + fn = via_compat_ioctls[nr - DRM_COMMAND_BASE]; + + lock_kernel(); /* XXX for now */ + if (fn != NULL) + ret = (*fn)(filp, cmd, arg); + else + ret = drm_ioctl(filp->f_dentry->d_inode, filp, cmd, arg); + unlock_kernel(); + + return ret; +} +