blob: 1df8aff8251d4cf0ef6a1b0b43281e3ede33aca9 [file] [log] [blame]
/**********************************************************************
*
* 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. <gpl-support@imgtec.com>
* Home Park Estate, Kings Langley, Herts, WD4 8LZ, UK
*
******************************************************************************/
#include <linux/version.h>
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38))
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#endif
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#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;
}