/********************************************************************** * * Copyright (C) Imagination Technologies Ltd. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful but, except * as otherwise stated in writing, 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 * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * * The full GNU General Public License is included in this distribution in * the file called "COPYING". * * Contact Information: * Imagination Technologies Ltd. * Home Park Estate, Kings Langley, Herts, WD4 8LZ, UK * ******************************************************************************/ #include #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)) #ifndef AUTOCONF_INCLUDED #include #endif #endif #include #include #include #include #include #include "services_headers.h" #include "queue.h" #include "resman.h" #include "pvrmmap.h" #include "pvr_debug.h" #include "pvrversion.h" #include "proc.h" #include "perproc.h" #include "env_perproc.h" #include "linkage.h" #include "lists.h" static struct proc_dir_entry * dir; static const IMG_CHAR PVRProcDirRoot[] = "pvr"; static IMG_INT pvr_proc_open(struct inode *inode,struct file *file); static void *pvr_proc_seq_start (struct seq_file *m, loff_t *pos); static void pvr_proc_seq_stop (struct seq_file *m, void *v); static void *pvr_proc_seq_next (struct seq_file *m, void *v, loff_t *pos); static int pvr_proc_seq_show (struct seq_file *m, void *v); static ssize_t pvr_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos); static struct file_operations pvr_proc_operations = { .open = pvr_proc_open, .read = seq_read, .write = pvr_proc_write, .llseek = seq_lseek, .release = seq_release, }; static struct seq_operations pvr_proc_seq_operations = { .start = pvr_proc_seq_start, .next = pvr_proc_seq_next, .stop = pvr_proc_seq_stop, .show = pvr_proc_seq_show, }; static struct proc_dir_entry* g_pProcQueue; static struct proc_dir_entry* g_pProcVersion; static struct proc_dir_entry* g_pProcSysNodes; #ifdef DEBUG static struct proc_dir_entry* g_pProcDebugLevel; #endif #ifdef PVR_MANUAL_POWER_CONTROL static struct proc_dir_entry* g_pProcPowerLevel; #endif static void ProcSeqShowVersion(struct seq_file *sfile,void* el); static void ProcSeqShowSysNodes(struct seq_file *sfile,void* el); static void* ProcSeqOff2ElementSysNodes(struct seq_file * sfile, loff_t off); off_t printAppend(IMG_CHAR * buffer, size_t size, off_t off, const IMG_CHAR * format, ...) { IMG_INT n; size_t space = size - (size_t)off; va_list ap; va_start (ap, format); n = vsnprintf (buffer+off, space, format, ap); va_end (ap); if (n >= (IMG_INT)space || n < 0) { buffer[size - 1] = 0; return (off_t)(size - 1); } else { return (off + (off_t)n); } } void* ProcSeq1ElementOff2Element(struct seq_file *sfile, loff_t off) { PVR_UNREFERENCED_PARAMETER(sfile); if(!off) return (void*)2; return NULL; } void* ProcSeq1ElementHeaderOff2Element(struct seq_file *sfile, loff_t off) { PVR_UNREFERENCED_PARAMETER(sfile); if(!off) { return PVR_PROC_SEQ_START_TOKEN; } if(off == 1) return (void*)2; return NULL; } static IMG_INT pvr_proc_open(struct inode *inode,struct file *file) { IMG_INT ret = seq_open(file, &pvr_proc_seq_operations); struct seq_file *seq = (struct seq_file*)file->private_data; struct proc_dir_entry* pvr_proc_entry = PDE(inode); seq->private = pvr_proc_entry->data; return ret; } static ssize_t pvr_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { struct inode *inode = file->f_path.dentry->d_inode; struct proc_dir_entry * dp; PVR_UNREFERENCED_PARAMETER(ppos); dp = PDE(inode); if (!dp->write_proc) return -EIO; return dp->write_proc(file, buffer, count, dp->data); } static void *pvr_proc_seq_start (struct seq_file *proc_seq_file, loff_t *pos) { PVR_PROC_SEQ_HANDLERS *handlers = (PVR_PROC_SEQ_HANDLERS*)proc_seq_file->private; if(handlers->startstop != NULL) handlers->startstop(proc_seq_file, IMG_TRUE); return handlers->off2element(proc_seq_file, *pos); } static void pvr_proc_seq_stop (struct seq_file *proc_seq_file, void *v) { PVR_PROC_SEQ_HANDLERS *handlers = (PVR_PROC_SEQ_HANDLERS*)proc_seq_file->private; PVR_UNREFERENCED_PARAMETER(v); if(handlers->startstop != NULL) handlers->startstop(proc_seq_file, IMG_FALSE); } static void *pvr_proc_seq_next (struct seq_file *proc_seq_file, void *v, loff_t *pos) { PVR_PROC_SEQ_HANDLERS *handlers = (PVR_PROC_SEQ_HANDLERS*)proc_seq_file->private; (*pos)++; if( handlers->next != NULL) return handlers->next( proc_seq_file, v, *pos ); return handlers->off2element(proc_seq_file, *pos); } static int pvr_proc_seq_show (struct seq_file *proc_seq_file, void *v) { PVR_PROC_SEQ_HANDLERS *handlers = (PVR_PROC_SEQ_HANDLERS*)proc_seq_file->private; handlers->show( proc_seq_file,v ); return 0; } static struct proc_dir_entry* CreateProcEntryInDirSeq( struct proc_dir_entry *pdir, const IMG_CHAR * name, IMG_VOID* data, pvr_next_proc_seq_t next_handler, pvr_show_proc_seq_t show_handler, pvr_off2element_proc_seq_t off2element_handler, pvr_startstop_proc_seq_t startstop_handler, write_proc_t whandler ) { struct proc_dir_entry * file; mode_t mode; if (!dir) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntryInDirSeq: cannot make proc entry /proc/%s/%s: no parent", PVRProcDirRoot, name)); return NULL; } mode = S_IFREG; if (show_handler) { mode |= S_IRUGO; } if (whandler) { mode |= S_IWUSR; } file=create_proc_entry(name, mode, pdir); if (file) { PVR_PROC_SEQ_HANDLERS *seq_handlers; #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) file->owner = THIS_MODULE; #endif file->proc_fops = &pvr_proc_operations; file->write_proc = whandler; file->data = kmalloc(sizeof(PVR_PROC_SEQ_HANDLERS), GFP_KERNEL); if(file->data) { seq_handlers = (PVR_PROC_SEQ_HANDLERS*)file->data; seq_handlers->next = next_handler; seq_handlers->show = show_handler; seq_handlers->off2element = off2element_handler; seq_handlers->startstop = startstop_handler; seq_handlers->data = data; return file; } } PVR_DPF((PVR_DBG_ERROR, "CreateProcEntryInDirSeq: cannot make proc entry /proc/%s/%s: no memory", PVRProcDirRoot, name)); return NULL; } struct proc_dir_entry* CreateProcReadEntrySeq ( const IMG_CHAR * name, IMG_VOID* data, pvr_next_proc_seq_t next_handler, pvr_show_proc_seq_t show_handler, pvr_off2element_proc_seq_t off2element_handler, pvr_startstop_proc_seq_t startstop_handler ) { return CreateProcEntrySeq(name, data, next_handler, show_handler, off2element_handler, startstop_handler, NULL); } struct proc_dir_entry* CreateProcEntrySeq ( const IMG_CHAR * name, IMG_VOID* data, pvr_next_proc_seq_t next_handler, pvr_show_proc_seq_t show_handler, pvr_off2element_proc_seq_t off2element_handler, pvr_startstop_proc_seq_t startstop_handler, write_proc_t whandler ) { return CreateProcEntryInDirSeq( dir, name, data, next_handler, show_handler, off2element_handler, startstop_handler, whandler ); } struct proc_dir_entry* CreatePerProcessProcEntrySeq ( const IMG_CHAR * name, IMG_VOID* data, pvr_next_proc_seq_t next_handler, pvr_show_proc_seq_t show_handler, pvr_off2element_proc_seq_t off2element_handler, pvr_startstop_proc_seq_t startstop_handler, write_proc_t whandler ) { PVRSRV_ENV_PER_PROCESS_DATA *psPerProc; IMG_UINT32 ui32PID; if (!dir) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntrySeq: /proc/%s doesn't exist", PVRProcDirRoot)); return NULL; } ui32PID = OSGetCurrentProcessIDKM(); psPerProc = PVRSRVPerProcessPrivateData(ui32PID); if (!psPerProc) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntrySeq: no per process data")); return NULL; } if (!psPerProc->psProcDir) { IMG_CHAR dirname[16]; IMG_INT ret; ret = snprintf(dirname, sizeof(dirname), "%u", ui32PID); if (ret <=0 || ret >= (IMG_INT)sizeof(dirname)) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: couldn't generate per process proc directory name \"%u\"", ui32PID)); return NULL; } else { psPerProc->psProcDir = proc_mkdir(dirname, dir); if (!psPerProc->psProcDir) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: couldn't create per process proc directory /proc/%s/%u", PVRProcDirRoot, ui32PID)); return NULL; } } } return CreateProcEntryInDirSeq(psPerProc->psProcDir, name, data, next_handler, show_handler,off2element_handler,startstop_handler,whandler); } IMG_VOID RemoveProcEntrySeq( struct proc_dir_entry* proc_entry ) { if (dir) { void* data = proc_entry->data ; PVR_DPF((PVR_DBG_MESSAGE, "Removing /proc/%s/%s", PVRProcDirRoot, proc_entry->name)); remove_proc_entry(proc_entry->name, dir); if( data) kfree( data ); } } IMG_VOID RemovePerProcessProcEntrySeq(struct proc_dir_entry* proc_entry) { PVRSRV_ENV_PER_PROCESS_DATA *psPerProc; psPerProc = LinuxTerminatingProcessPrivateData(); if (!psPerProc) { psPerProc = PVRSRVFindPerProcessPrivateData(); if (!psPerProc) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: can't " "remove %s, no per process data", proc_entry->name)); return; } } if (psPerProc->psProcDir) { void* data = proc_entry->data ; PVR_DPF((PVR_DBG_MESSAGE, "Removing proc entry %s from %s", proc_entry->name, psPerProc->psProcDir->name)); remove_proc_entry(proc_entry->name, psPerProc->psProcDir); if(data) kfree( data ); } } static IMG_INT pvr_read_proc(IMG_CHAR *page, IMG_CHAR **start, off_t off, IMG_INT count, IMG_INT *eof, IMG_VOID *data) { pvr_read_proc_t *pprn = (pvr_read_proc_t *)data; off_t len = pprn (page, (size_t)count, off); if (len == END_OF_FILE) { len = 0; *eof = 1; } else if (!len) { *start = (IMG_CHAR *) 0; } else { *start = (IMG_CHAR *) 1; } return len; } static IMG_INT CreateProcEntryInDir(struct proc_dir_entry *pdir, const IMG_CHAR * name, read_proc_t rhandler, write_proc_t whandler, IMG_VOID *data) { struct proc_dir_entry * file; mode_t mode; if (!pdir) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntryInDir: parent directory doesn't exist")); return -ENOMEM; } mode = S_IFREG; if (rhandler) { mode |= S_IRUGO; } if (whandler) { mode |= S_IWUSR; } file = create_proc_entry(name, mode, pdir); if (file) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) file->owner = THIS_MODULE; #endif file->read_proc = rhandler; file->write_proc = whandler; file->data = data; PVR_DPF((PVR_DBG_MESSAGE, "Created proc entry %s in %s", name, pdir->name)); return 0; } PVR_DPF((PVR_DBG_ERROR, "CreateProcEntry: cannot create proc entry %s in %s", name, pdir->name)); return -ENOMEM; } IMG_INT CreateProcEntry(const IMG_CHAR * name, read_proc_t rhandler, write_proc_t whandler, IMG_VOID *data) { return CreateProcEntryInDir(dir, name, rhandler, whandler, data); } IMG_INT CreatePerProcessProcEntry(const IMG_CHAR * name, read_proc_t rhandler, write_proc_t whandler, IMG_VOID *data) { PVRSRV_ENV_PER_PROCESS_DATA *psPerProc; IMG_UINT32 ui32PID; if (!dir) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: /proc/%s doesn't exist", PVRProcDirRoot)); return -ENOMEM; } ui32PID = OSGetCurrentProcessIDKM(); psPerProc = PVRSRVPerProcessPrivateData(ui32PID); if (!psPerProc) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: no per process data")); return -ENOMEM; } if (!psPerProc->psProcDir) { IMG_CHAR dirname[16]; IMG_INT ret; ret = snprintf(dirname, sizeof(dirname), "%u", ui32PID); if (ret <=0 || ret >= (IMG_INT)sizeof(dirname)) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: couldn't generate per process proc directory name \"%u\"", ui32PID)); return -ENOMEM; } else { psPerProc->psProcDir = proc_mkdir(dirname, dir); if (!psPerProc->psProcDir) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: couldn't create per process proc directory /proc/%s/%u", PVRProcDirRoot, ui32PID)); return -ENOMEM; } } } return CreateProcEntryInDir(psPerProc->psProcDir, name, rhandler, whandler, data); } IMG_INT CreateProcReadEntry(const IMG_CHAR * name, pvr_read_proc_t handler) { struct proc_dir_entry * file; if (!dir) { PVR_DPF((PVR_DBG_ERROR, "CreateProcReadEntry: cannot make proc entry /proc/%s/%s: no parent", PVRProcDirRoot, name)); return -ENOMEM; } file = create_proc_read_entry (name, S_IFREG | S_IRUGO, dir, pvr_read_proc, (IMG_VOID *)handler); if (file) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)) file->owner = THIS_MODULE; #endif return 0; } PVR_DPF((PVR_DBG_ERROR, "CreateProcReadEntry: cannot make proc entry /proc/%s/%s: no memory", PVRProcDirRoot, name)); return -ENOMEM; } IMG_INT CreateProcEntries(IMG_VOID) { dir = proc_mkdir (PVRProcDirRoot, NULL); if (!dir) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntries: cannot make /proc/%s directory", PVRProcDirRoot)); return -ENOMEM; } g_pProcQueue = CreateProcReadEntrySeq("queue", NULL, NULL, ProcSeqShowQueue, ProcSeqOff2ElementQueue, NULL); g_pProcVersion = CreateProcReadEntrySeq("version", NULL, NULL, ProcSeqShowVersion, ProcSeq1ElementHeaderOff2Element, NULL); g_pProcSysNodes = CreateProcReadEntrySeq("nodes", NULL, NULL, ProcSeqShowSysNodes, ProcSeqOff2ElementSysNodes, NULL); if(!g_pProcQueue || !g_pProcVersion || !g_pProcSysNodes) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntries: couldn't make /proc/%s files", PVRProcDirRoot)); return -ENOMEM; } #ifdef DEBUG g_pProcDebugLevel = CreateProcEntrySeq("debug_level", NULL, NULL, ProcSeqShowDebugLevel, ProcSeq1ElementOff2Element, NULL, (IMG_VOID*)PVRDebugProcSetLevel); if(!g_pProcDebugLevel) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntries: couldn't make /proc/%s/debug_level", PVRProcDirRoot)); return -ENOMEM; } #ifdef PVR_MANUAL_POWER_CONTROL g_pProcPowerLevel = CreateProcEntrySeq("power_control", NULL, NULL, ProcSeqShowPowerLevel, ProcSeq1ElementOff2Element, NULL, PVRProcSetPowerLevel); if(!g_pProcPowerLevel) { PVR_DPF((PVR_DBG_ERROR, "CreateProcEntries: couldn't make /proc/%s/power_control", PVRProcDirRoot)); return -ENOMEM; } #endif #endif return 0; } IMG_VOID RemoveProcEntry(const IMG_CHAR * name) { if (dir) { remove_proc_entry(name, dir); PVR_DPF((PVR_DBG_MESSAGE, "Removing /proc/%s/%s", PVRProcDirRoot, name)); } } IMG_VOID RemovePerProcessProcEntry(const IMG_CHAR *name) { PVRSRV_ENV_PER_PROCESS_DATA *psPerProc; psPerProc = LinuxTerminatingProcessPrivateData(); if (!psPerProc) { psPerProc = PVRSRVFindPerProcessPrivateData(); if (!psPerProc) { PVR_DPF((PVR_DBG_ERROR, "CreatePerProcessProcEntries: can't " "remove %s, no per process data", name)); return; } } if (psPerProc->psProcDir) { remove_proc_entry(name, psPerProc->psProcDir); PVR_DPF((PVR_DBG_MESSAGE, "Removing proc entry %s from %s", name, psPerProc->psProcDir->name)); } } IMG_VOID RemovePerProcessProcDir(PVRSRV_ENV_PER_PROCESS_DATA *psPerProc) { if (psPerProc->psProcDir) { while (psPerProc->psProcDir->subdir) { PVR_DPF((PVR_DBG_WARNING, "Belatedly removing /proc/%s/%s/%s", PVRProcDirRoot, psPerProc->psProcDir->name, psPerProc->psProcDir->subdir->name)); RemoveProcEntry(psPerProc->psProcDir->subdir->name); } RemoveProcEntry(psPerProc->psProcDir->name); } } IMG_VOID RemoveProcEntries(IMG_VOID) { #ifdef DEBUG RemoveProcEntrySeq( g_pProcDebugLevel ); #ifdef PVR_MANUAL_POWER_CONTROL RemoveProcEntrySeq( g_pProcPowerLevel ); #endif #endif RemoveProcEntrySeq(g_pProcQueue); RemoveProcEntrySeq(g_pProcVersion); RemoveProcEntrySeq(g_pProcSysNodes); while (dir->subdir) { PVR_DPF((PVR_DBG_WARNING, "Belatedly removing /proc/%s/%s", PVRProcDirRoot, dir->subdir->name)); RemoveProcEntry(dir->subdir->name); } remove_proc_entry(PVRProcDirRoot, NULL); } static void ProcSeqShowVersion(struct seq_file *sfile,void* el) { SYS_DATA *psSysData; IMG_CHAR *pszSystemVersionString = "None"; if(el == PVR_PROC_SEQ_START_TOKEN) { seq_printf(sfile, "Version %s (%s) %s\n", PVRVERSION_STRING, PVR_BUILD_TYPE, PVR_BUILD_DIR); return; } psSysData = SysAcquireDataNoCheck(); if(psSysData != IMG_NULL && psSysData->pszVersionString != IMG_NULL) { pszSystemVersionString = psSysData->pszVersionString; } seq_printf( sfile, "System Version String: %s\n", pszSystemVersionString); } static const IMG_CHAR *deviceTypeToString(PVRSRV_DEVICE_TYPE deviceType) { switch (deviceType) { default: { static IMG_CHAR text[10]; sprintf(text, "?%x", (IMG_UINT)deviceType); return text; } } } static const IMG_CHAR *deviceClassToString(PVRSRV_DEVICE_CLASS deviceClass) { switch (deviceClass) { case PVRSRV_DEVICE_CLASS_3D: { return "3D"; } case PVRSRV_DEVICE_CLASS_DISPLAY: { return "display"; } case PVRSRV_DEVICE_CLASS_BUFFER: { return "buffer"; } default: { static IMG_CHAR text[10]; sprintf(text, "?%x", (IMG_UINT)deviceClass); return text; } } } static IMG_VOID* DecOffPsDev_AnyVaCb(PVRSRV_DEVICE_NODE *psNode, va_list va) { off_t *pOff = va_arg(va, off_t*); if (--(*pOff)) { return IMG_NULL; } else { return psNode; } } static void ProcSeqShowSysNodes(struct seq_file *sfile,void* el) { PVRSRV_DEVICE_NODE *psDevNode; if(el == PVR_PROC_SEQ_START_TOKEN) { seq_printf( sfile, "Registered nodes\n" "Addr Type Class Index Ref pvDev Size Res\n"); return; } psDevNode = (PVRSRV_DEVICE_NODE*)el; seq_printf( sfile, "%p %-8s %-8s %4d %2u %p %3u %p\n", psDevNode, deviceTypeToString(psDevNode->sDevId.eDeviceType), deviceClassToString(psDevNode->sDevId.eDeviceClass), psDevNode->sDevId.eDeviceClass, psDevNode->ui32RefCount, psDevNode->pvDevice, psDevNode->ui32pvDeviceSize, psDevNode->hResManContext); } static void* ProcSeqOff2ElementSysNodes(struct seq_file * sfile, loff_t off) { SYS_DATA *psSysData; PVRSRV_DEVICE_NODE*psDevNode = IMG_NULL; PVR_UNREFERENCED_PARAMETER(sfile); if(!off) { return PVR_PROC_SEQ_START_TOKEN; } psSysData = SysAcquireDataNoCheck(); if (psSysData != IMG_NULL) { psDevNode = (PVRSRV_DEVICE_NODE*) List_PVRSRV_DEVICE_NODE_Any_va(psSysData->psDeviceNodeList, DecOffPsDev_AnyVaCb, &off); } return (void*)psDevNode; }