Browse Source

Merge commit '24316fc' into bfgminer

Luke Dashjr 13 years ago
parent
commit
c9d99925a4
6 changed files with 369 additions and 56 deletions
  1. 2 0
      api.c
  2. 1 1
      driver-opencl.c
  3. 2 1
      driver-ztex.c
  4. 30 2
      fpgautils.c
  5. 30 22
      miner.c
  6. 304 30
      miner.php

+ 2 - 0
api.c

@@ -1149,10 +1149,12 @@ static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 		return;
 	}
 
+#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL? */
 	if (cgpu->status != LIFE_WELL) {
 		strcpy(io_buffer, message(MSG_PGAUNW, id, NULL, isjson));
 		return;
 	}
+#endif
 
 	for (i = 0; i < mining_threads; i++) {
 		pga = thr_info[i].cgpu->device_id;

+ 1 - 1
driver-opencl.c

@@ -1614,7 +1614,7 @@ static uint64_t opencl_scanhash(struct thr_info *thr, struct work *work,
 		suseconds_t gpu_us;
 
 		timersub(&gpu->tv_gpuend, &gpu->tv_gpustart, &diff);
-		gpu_us = diff.tv_sec * 1000 + diff.tv_usec;
+		gpu_us = diff.tv_sec * 1000000 + diff.tv_usec;
 		if (likely(gpu_us > 0)) {
 			gpu->gpu_us_average = (gpu->gpu_us_average + gpu_us * 0.63) / 1.63;
 

+ 2 - 1
driver-ztex.c

@@ -63,7 +63,8 @@ static void ztex_detect(void)
 	struct cgpu_info *ztex;
 
 	cnt = libztex_scanDevices(&ztex_devices);
-	applog(LOG_WARNING, "Found %d ztex board(s)", cnt);
+	if (cnt > 0)
+		applog(LOG_WARNING, "Found %d ztex board%s", cnt, cnt > 1 ? "s" : "");
 
 	for (i = 0; i < cnt; i++) {
 		ztex = calloc(1, sizeof(struct cgpu_info));

+ 30 - 2
fpgautils.c

@@ -15,6 +15,7 @@
 #include <string.h>
 
 #ifndef WIN32
+#include <errno.h>
 #include <termios.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -148,7 +149,21 @@ serial_open(const char*devpath, unsigned long baud, signed short timeout, bool p
 #ifdef WIN32
 	HANDLE hSerial = CreateFile(devpath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
 	if (unlikely(hSerial == INVALID_HANDLE_VALUE))
+	{
+		DWORD e = GetLastError();
+		switch (e) {
+		case ERROR_ACCESS_DENIED:
+			applog(LOG_ERR, "Do not have user privileges required to open %s", devpath);
+			break;
+		case ERROR_SHARING_VIOLATION:
+			applog(LOG_ERR, "%s is already in use by another process", devpath);
+			break;
+		default:
+			applog(LOG_DEBUG, "Open %s failed, GetLastError:%u", devpath, e);
+			break;
+		}
 		return -1;
+	}
 
 	// thanks to af_newbie for pointers about this
 	COMMCONFIG comCfg = {0};
@@ -179,15 +194,28 @@ serial_open(const char*devpath, unsigned long baud, signed short timeout, bool p
 	int fdDev = open(devpath, O_RDWR | O_CLOEXEC | O_NOCTTY);
 
 	if (unlikely(fdDev == -1))
+	{
+		if (errno == EACCES)
+			applog(LOG_ERR, "Do not have user privileges required to open %s", devpath);
+		else
+			applog(LOG_DEBUG, "Open %s failed, errno:%d", devpath, errno);
+
 		return -1;
+	}
 
 	struct termios my_termios;
 
 	tcgetattr(fdDev, &my_termios);
 
 	switch (baud) {
-	case 0: break;
-	case 115200: my_termios.c_cflag = B115200; break;
+	case 0:
+		break;
+	case 115200:
+		my_termios.c_cflag &= ~CBAUD;
+		my_termios.c_cflag |= B115200;
+		break;
+	// TODO: try some higher speeds with the Icarus and BFL to see
+	// if they support them and if setting them makes any difference
 	default:
 		applog(LOG_WARNING, "Unrecognized baud rate: %lu", baud);
 	}

+ 30 - 22
miner.c

@@ -69,9 +69,7 @@ enum workio_commands {
 struct workio_cmd {
 	enum workio_commands	cmd;
 	struct thr_info		*thr;
-	union {
-		struct work	*work;
-	} u;
+	struct work		*work;
 	bool			lagging;
 };
 
@@ -1919,7 +1917,7 @@ static void workio_cmd_free(struct workio_cmd *wc)
 
 	switch (wc->cmd) {
 	case WC_SUBMIT_WORK:
-		free_work(wc->u.work);
+		free_work(wc->work);
 		break;
 	default: /* do nothing */
 		break;
@@ -2182,22 +2180,29 @@ static bool stale_work(struct work *work, bool share)
 	if (work->mandatory)
 		return false;
 
-	if (share)
-		work_expiry = opt_expiry;
-	else if (work->rolltime)
-		work_expiry = work->rolltime;
-	else
-		work_expiry = opt_scantime;
+	if (share) {
+		/* Technically the rolltime should be correct but some pools
+		 * advertise a broken expire= that is lower than a meaningful
+		 * scantime */
+		if (work->rolltime > opt_scantime)
+			work_expiry = work->rolltime;
+		else
+			work_expiry = opt_expiry;
+	} else {
+		/* Don't keep rolling work right up to the expiration */
+		if (work->rolltime > opt_scantime)
+			work_expiry = (work->rolltime - opt_scantime) * 2 / 3 + opt_scantime;
+		else /* Shouldn't happen unless someone increases scantime */
+			work_expiry = opt_scantime;
+	}
+
 	pool = work->pool;
 	/* Factor in the average getwork delay of this pool, rounding it up to
 	 * the nearest second */
-	getwork_delay = (pool->cgminer_pool_stats.getwork_wait_rolling + 1) * 5;
-	if (!share) {
-		work_expiry -= getwork_delay;
-		if (unlikely(work_expiry < 5))
-			work_expiry = 5;
-	} else
-		work_expiry += getwork_delay;
+	getwork_delay = pool->cgminer_pool_stats.getwork_wait_rolling * 5 + 1;
+	work_expiry -= getwork_delay;
+	if (unlikely(work_expiry < 5))
+		work_expiry = 5;
 
 	gettimeofday(&now, NULL);
 	if ((now.tv_sec - work->tv_staged.tv_sec) >= work_expiry)
@@ -2229,7 +2234,7 @@ static void check_solve(struct work *work)
 static void *submit_work_thread(void *userdata)
 {
 	struct workio_cmd *wc = (struct workio_cmd *)userdata;
-	struct work *work = wc->u.work;
+	struct work *work = wc->work;
 	struct pool *pool = work->pool;
 	struct curl_ent *ce;
 	int failures = 0;
@@ -3675,9 +3680,12 @@ static inline bool should_roll(struct work *work)
 	return false;
 }
 
+/* Limit rolls to 7000 to not beyond 2 hours in the future where bitcoind will
+ * reject blocks as invalid. */
 static inline bool can_roll(struct work *work)
 {
-	return (work->pool && !stale_work(work, false) && work->rolltime && !work->clone);
+	return (work->pool && work->rolltime && !work->clone &&
+		work->rolls < 7000 && !stale_work(work, false));
 }
 
 static void roll_work(struct work *work)
@@ -3881,11 +3889,11 @@ bool submit_work_sync(struct thr_info *thr, const struct work *work_in)
 		return false;
 	}
 
-	wc->u.work = make_work();
+	wc->work = make_work();
 	wc->cmd = WC_SUBMIT_WORK;
 	wc->thr = thr;
-	memcpy(wc->u.work, work_in, sizeof(*work_in));
-	wc->u.work->share_found_time = time(NULL);
+	memcpy(wc->work, work_in, sizeof(*work_in));
+	wc->work->share_found_time = time(NULL);
 
 	applog(LOG_DEBUG, "Pushing submit work to work thread");
 

+ 304 - 30
miner.php

@@ -4,6 +4,7 @@ session_start();
 global $miner, $port, $readonly, $notify, $rigs, $socktimeoutsec;
 global $checklastshare, $hidefields;
 global $ignorerefresh, $changerefresh, $autorefresh;
+global $allowcustompages, $customsummarypages;
 #
 # Don't touch these 2 - see $rigs below
 $miner = null;
@@ -59,6 +60,32 @@ $ignorerefresh = false;
 $changerefresh = true;
 $autorefresh = 0;
 #
+# Should we allow custom pages?
+# (or just completely ignore then and don't display the buttons)
+$allowcustompages = true;
+#
+# OK this is a bit more complex item: Custom Summary Pages
+# A custom summary page in an array of 'section' => array('FieldA','FieldB'...)
+# This makes up what is displayed with each 'section' separately as a table
+# - empty tables are not shown
+# - empty columns (an unknown field) are not shown
+# - and missing field data shows as blank
+# There is a second array, listing fields to be totaled for each section
+# see the example below (if there is no matching data, no total will show)
+$mobilepage = array(
+ 'SUMMARY' => array('Elapsed', 'MHS av', 'Found Blocks', 'Accepted', 'Rejected', 'Utility'),
+ 'GPU' => array('GPU', 'Status', 'MHS av', 'Accepted', 'Rejected', 'Utility'),
+ 'PGA' => array('ID', 'Name', 'Status', 'MHS av', 'Accepted', 'Rejected', 'Utility'),
+ 'POOL' => array('POOL', 'Status', 'Accepted', 'Rejected', 'Last Share Time'));
+$mobilesum = array(
+ 'SUMMARY' => array('MHS av' => 1, 'Found Blocks' => 1, 'Accepted' => 1, 'Rejected' => 1, 'Utility' => 1),
+ 'GPU' => array('MHS av' => 1, 'Accepted' => 1, 'Rejected' => 1, 'Utility' => 1),
+ 'PGA' => array('MHS av' => 1, 'Accepted' => 1, 'Rejected' => 1, 'Utility' => 1),
+ 'POOL' => array('Accepted' => 1, 'Rejected' => 1));
+#
+# customsummarypages is an array of these Custom Summary Pages
+$customsummarypages = array('Mobile' => array($mobilepage, $mobilesum));
+#
 $here = $_SERVER['PHP_SELF'];
 #
 global $tablebegin, $tableend, $warnfont, $warnoff, $dfmt;
@@ -90,26 +117,30 @@ $showndate = false;
 global $rigerror;
 $rigerror = array();
 #
-function htmlhead($checkapi, $rig)
+function htmlhead($checkapi, $rig, $pg = null)
 {
  global $miner_font_family, $miner_font_size;
  global $error, $readonly, $here;
  global $ignorerefresh, $autorefresh;
 
- $paramrig = '';
+ $extraparams = '';
  if ($rig != null && $rig != '')
-	$paramrig = "&rig=$rig";
+	$extraparams = "&rig=$rig";
+ else
+	if ($pg != null && $pg != '')
+		$extraparams = "&pg=$pg";
 
  if ($ignorerefresh == true || $autorefresh == 0)
 	$refreshmeta = '';
  else
  {
-	$url = "$here?ref=$autorefresh$paramrig";
+	$url = "$here?ref=$autorefresh$extraparams";
 	$refreshmeta = "\n<meta http-equiv='refresh' content='$autorefresh;url=$url'>";
  }
 
  if ($readonly === false && $checkapi === true)
  {
+	$error = null;
 	$access = api('privileged');
 	if ($error != null
 	||  !isset($access['STATUS']['STATUS'])
@@ -134,7 +165,7 @@ td.lst { color:blue; $miner_font background:#ffffdd }
 function pr(a,m){if(m!=null){if(!confirm(m+'?'))return}window.location='$here?ref=$autorefresh'+a}\n";
 
 if ($ignorerefresh == false)
- echo "function prr(a){if(a){v=document.getElementById('refval').value}else{v=0}window.location='$here?ref='+v+'$paramrig'}\n";
+ echo "function prr(a){if(a){v=document.getElementById('refval').value}else{v=0}window.location='$here?ref='+v+'$extraparams'}\n";
 
  if ($readonly === false && $checkapi === true)
  {
@@ -150,17 +181,20 @@ function prs2(a,n,r){var v=document.getElementById('gi'+n).value;var c=a.substr(
 <?php
 }
 #
-global $error;
+global $haderror, $error;
+$haderror = false;
 $error = null;
 #
 function getsock($addr, $port)
 {
- global $error, $socktimeoutsec;
+ global $haderror, $error, $socktimeoutsec;
 
+ $error = null;
  $socket = null;
  $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  if ($socket === false || $socket === null)
  {
+	$haderror = true;
 	$error = socket_strerror(socket_last_error());
 	$msg = "socket create(TCP) failed";
 	$error = "ERR: $msg '$error'\n";
@@ -175,6 +209,7 @@ function getsock($addr, $port)
  $res = socket_connect($socket, $addr, $port);
  if ($res === false)
  {
+	$haderror = true;
 	$error = socket_strerror(socket_last_error());
 	$msg = "socket connect($addr,$port) failed";
 	$error = "ERR: $msg '$error'\n";
@@ -201,6 +236,7 @@ function readsockline($socket)
 #
 function api($cmd)
 {
+ global $haderror, $error;
  global $miner, $port, $hidefields;
 
  $socket = getsock($miner, $port);
@@ -212,6 +248,7 @@ function api($cmd)
 
 	if (strlen($line) == 0)
 	{
+		$haderror = true;
 		$error = "WARN: '$cmd' returned nothing\n";
 		return $line;
 	}
@@ -519,7 +556,7 @@ $poolcmd = array(	'Switch to'	=> 'switchpool',
 			'Enable'	=> 'enablepool',
 			'Disable'	=> 'disablepool' );
 #
-function showhead($cmd, $item, $values)
+function showhead($cmd, $values, $justnames = false)
 {
  global $poolcmd, $readonly;
 
@@ -532,7 +569,7 @@ function showhead($cmd, $item, $values)
 	echo "<td valign=bottom class=h>$name</td>";
  }
 
- if ($cmd == 'pools' && $readonly === false)
+ if ($justnames === false && $cmd == 'pools' && $readonly === false)
 	foreach ($poolcmd as $name => $pcmd)
 		echo "<td valign=bottom class=h>$name</td>";
 
@@ -588,7 +625,7 @@ function details($cmd, $list, $rig)
 	if ($sectionname != $section)
 	{
 		echo $tableend.$tablebegin;
-		showhead($cmd, $item, $values);
+		showhead($cmd, $values);
 		$section = $sectionname;
 	}
 
@@ -796,7 +833,7 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
 	echo $tableend.$tablebegin;
 
 	$dthead = array('' => 1, 'STATUS' => 1, 'Description' => 1, 'When' => 1, 'API' => 1, 'CGMiner' => 1);
-	showhead('', null, $dthead);
+	showhead('', $dthead);
 
 	foreach ($anss as $rig => $ans)
 	{
@@ -865,7 +902,7 @@ function doforeach($cmd, $des, $sum, $head, $datetime)
  if ($sum != null)
 	$anss['total']['total'] = $total;
 
- showhead('', null, $header);
+ showhead('', $header);
 
  $section = '';
 
@@ -924,26 +961,38 @@ function refreshbuttons()
  if ($ignorerefresh == false && $changerefresh == true)
  {
 	echo '&nbsp;&nbsp;&nbsp;&nbsp;';
-	echo "<input type=button value='Refresh:' onclick='prr(true)'>";
+	echo "<input type=button value='Auto Refresh:' onclick='prr(true)'>";
 	echo "<input type=text name='refval' id='refval' size=2 value='$autorefresh'>";
 	echo "<input type=button value='Off' onclick='prr(false)'>";
  }
 }
 #
-function doOne($rig, $preprocess)
+function pagetop($rig, $pg)
 {
- global $error, $readonly, $notify, $rigs;
-
- htmlhead(true, $rig);
+ global $readonly, $rigs;
+ global $allowcustompages, $customsummarypages;
 
- $error = null;
+ if ($rig === null)
+ {
+	if ($pg === null)
+		$refresh = '';
+	else
+		$refresh = "&pg=$pg";
+ }
+ else
+	$refresh = "&rig=$rig";
 
- echo "<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td>";
- echo "<input type=button value='Refresh' onclick='pr(\"&rig=$rig\",null)'></td>";
+ echo '<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td nowrap>';
+ echo "<input type=button value='Refresh' onclick='pr(\"$refresh\",null)'>&nbsp;";
  if (count($rigs) > 1)
-	echo "<td><input type=button value='Summary' onclick='pr(\"\",null)'></td>";
- echo "<td width=100%>&nbsp;</td><td nowrap>";
- if ($readonly === false)
+	echo "<input type=button value='Summary' onclick='pr(\"\",null)'>&nbsp;";
+
+ if ($allowcustompages === true)
+	foreach ($customsummarypages as $pagename => $data)
+		echo "<input type=button value='$pagename' onclick='pr(\"&pg=$pagename\",null)'>&nbsp;";
+
+ echo '</td><td width=100%>&nbsp;</td><td nowrap>';
+ if ($rig !== null && $readonly === false)
  {
 	$rg = '';
 	if (count($rigs) > 1)
@@ -953,6 +1002,15 @@ function doOne($rig, $preprocess)
  }
  refreshbuttons();
  echo "</td></tr></table></td></tr>";
+}
+#
+function doOne($rig, $preprocess)
+{
+ global $haderror, $readonly, $notify, $rigs;
+
+ htmlhead(true, $rig);
+
+ pagetop($rig, null);
 
  if ($preprocess != null)
 	process(array($preprocess => $preprocess), $rig);
@@ -968,16 +1026,226 @@ function doOne($rig, $preprocess)
 
  process($cmds, $rig);
 
- if ($error == null && $readonly === false)
+ if ($haderror == false && $readonly === false)
 	processgpus($rig);
 }
 #
+global $sectionmap;
+# map sections to their api command
+$sectionmap = array(
+	'SUMMARY' => 'summary',
+	'POOL' => 'pools',
+	'GPU' => 'devs',
+	'PGA' => 'devs',
+	'NOTIFY' => 'notify',
+	'CONFIG' => 'config');
+#
+function customset($showfields, $sum, $section, $num, $result, $total)
+{
+ foreach ($result as $sec => $row)
+ {
+	$secname = preg_replace('/\d/', '', $sec);
+
+	if ($sec != 'total')
+	{
+		if ($secname != $section)
+			continue;
+	}
+
+	echo '<tr>';
+
+	$when = 0;
+	if (isset($result['STATUS']['When']))
+		$when = $result['STATUS']['When'];
+
+	if ($sec === 'total')
+		$class = ' class=tot';
+	else
+		$class = '';
+
+	echo "<td align=middle$class>$num</td>";
+
+	foreach ($showfields as $name => $one)
+	{
+		if (isset($row[$name]))
+		{
+			$value = $row[$name];
+
+			if (isset($sum[$secname][$name]))
+			{
+				if (isset($total[$name]))
+					$total[$name] += $value;
+				else
+					$total[$name] = $value;
+			}
+		}
+		else
+		{
+			if ($sec == 'total' && isset($total[$name]))
+				$value = $total[$name];
+			else
+				$value = null;
+		}
+
+		list($showvalue, $class) = fmt($section, $name, $value, $when, $row);
+
+		if ($sec === 'total' and $class == '')
+			$class = ' class=tot';
+
+
+		echo "<td$class align=right>$showvalue</td>";
+	}
+
+	echo '</tr>';
+ }
+ return $total;
+}
+#
+function processcustompage($pagename, $sections, $sum)
+{
+ global $sectionmap;
+ global $miner, $port;
+ global $rigs, $error;
+ global $warnfont, $warnoff;
+ global $tablebegin, $tableend, $dfmt;
+ global $readonly, $showndate;
+
+ $cmds = array();
+ $errors = array();
+ foreach ($sections as $section => $fields)
+ {
+	if (isset($sectionmap[$section]))
+	{
+		$cmd = $sectionmap[$section];
+		if (!isset($cmds[$cmd]))
+			$cmds[$cmd] = 1;
+	}
+	else
+		$errors[] = "Error: unknown section '$section' in custom summary page '$pagename'";
+ }
+
+ $results = array();
+ foreach ($rigs as $num => $rig)
+ {
+	$parts = explode(':', $rig, 2);
+	if (count($parts) == 2)
+	{
+		$miner = $parts[0];
+		$port = $parts[1];
+
+		foreach ($cmds as $cmd => $one)
+		{
+			$process = api($cmd);
+
+			if ($error != null)
+			{
+				$errors[] = "Error getting $cmd for $rig $warnfont$error$warnoff";
+				break;
+			}
+			else
+				$results[$cmd][$num] = $process;
+		}
+	}
+ }
+
+ if (count($results) > 0)
+ {
+	$first = true;
+	foreach ($sections as $section => $fields)
+	{
+		if (isset($results[$sectionmap[$section]]))
+		{
+			$rigresults = $results[$sectionmap[$section]];
+			$showfields = array();
+			foreach ($fields as $field)
+				foreach ($rigresults as $result)
+					foreach ($result as $sec => $row)
+					{
+						$secname = preg_replace('/\d/', '', $sec);
+						if ($secname == $section && isset($row[$field]))
+							$showfields[$field] = 1;
+					}
+
+			if (count($showfields) > 0)
+			{
+				if ($first === false)
+					echo '<tr><td>&nbsp;</td></tr>';
+
+				echo $tablebegin;
+
+				showhead('', array('Rig'=>1)+$showfields, true);
+
+				$total = array();
+				$add = array('total' => array());
+
+				foreach ($rigresults as $num => $result)
+				{
+					$rg = "<input type=button value='$num' onclick='pr(\"&rig=$num\",null)'>";
+					$total = customset($showfields, $sum, $section, $rg, $result, $total);
+				}
+
+				if (count($total) > 0)
+					customset($showfields, $sum, $section, '&Sigma;', $add, $total);
+
+				$first = false;
+
+				echo $tableend;
+			}
+		}
+	}
+ }
+
+ if (count($errors) > 0)
+ {
+	if (count($results) > 0)
+		echo '<tr><td>&nbsp;</td></tr>';
+
+	foreach ($errors as $err)
+		echo "<tr><td colspan=100>$err</td></tr>";
+ }
+}
+#
+function showcustompage($pagename)
+{
+ global $customsummarypages;
+
+ htmlhead(false, null, $pagename);
+
+ pagetop(null, $pagename);
+
+ if (!isset($customsummarypages[$pagename]))
+ {
+	echo "<tr><td colspan=100>Unknown custom summary page '$pagename'</td></tr>";
+	return;
+ }
+
+ if (count($customsummarypages[$pagename]) != 2)
+ {
+	echo "<tr><td colspan=100>Invalid custom summary page '$pagename' (".count($customsummarypages[$pagename]).")</td></tr>";
+	return;
+ }
+
+ $page = $customsummarypages[$pagename][0];
+ $sum = $customsummarypages[$pagename][1];
+ if ($sum === null)
+	$sum = array();
+
+ if (count($page) <= 1)
+ {
+	echo "<tr><td colspan=100>Invalid custom summary page '$pagename' no content </td></tr>";
+	return;
+ }
+
+ processcustompage($pagename, $page, $sum);
+}
+#
 function display()
 {
  global $tablebegin, $tableend;
  global $miner, $port;
- global $error, $readonly, $notify, $rigs;
+ global $readonly, $notify, $rigs;
  global $ignorerefresh, $autorefresh;
+ global $allowcustompages;
 
  if ($ignorerefresh == false)
  {
@@ -1021,6 +1289,16 @@ function display()
 	return;
  }
 
+ if ($allowcustompages === true)
+ {
+	$pg = trim(getparam('pg', true));
+	if ($pg != null && $pg != '')
+	{
+		showcustompage($pg);
+		return;
+	}
+ }
+
  if (count($rigs) == 1)
  {
 	$parts = explode(':', $rigs[0], 2);
@@ -1055,11 +1333,7 @@ function display()
 
  htmlhead(false, null);
 
- echo "<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td>";
- echo "<input type=button value='Refresh' onclick='pr(\"\",null)'>";
- echo "<td width=100%>&nbsp;</td><td nowrap>";
- refreshbuttons();
- echo "</td></tr></table></td></tr>";
+ pagetop(null, null);
 
  if ($preprocess != null)
 	process(array($preprocess => $preprocess), $rig);