Reply
Thread Tools
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#1
Necessary warnings: This writes to a critical area of the N900 - I'm pretty sure it's safe, but this tool also gives you the potential to do things that could be unsafe or haven't been tested yet. (Though you won't hit that if you're just using it to turn R&D flags on/off, probably).

I have written a command-line program for enabling/disabling R&D mode and setting/clearing R&D mode flags on-device on the N900. I used qwerty's R&D Mode Control (which was GUI-only) for reference with regard to how to use the libcal function, since I could find no documentation anywhere on how to use libcal otherwise, so for that, credit goes to him. However, other than than, a couple of the variable names and error message wordings, my code is my own, hence me not including his name in the copyright statement (edit: except for the "based on work by"). If people more knowledgeable in GPL legalities feel this is wrong, please don't hesitate to inform me.

Source Code (Updated 2013-11-07)
Code:
/*
 * rdmod - Command-Line R&D Mode Control
 * Copyright (C) 2011-2013 Alexander Kozhevnikov <mentalisttraceur@gmail.com>
 * Based on work by Faheem Pervez ("qwerty12")
 * This program depends on the Nokia N900 system library libcal. Many thanks
 * to Ivaylo Dimitrov ("freemangordon") for providing an open source reverse
 * engineered version, which freed this project from depending on the closed
 * source one. It can be found at: <https://gitorious.org/community-ssu/libcal>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but 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, see <http://www.gnu.org/licenses/>,
 * or write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330 Boston MA 02111-1307 USA.
 */

#include <cal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*For ensuring /dev/shm is mounted; it isn't very early during boot up, but
the semaphore functions used in libcal need it.*/
/*cal-tool works around /dev/shm being absent by somehow putting semaphores
into /tmp instead. Would be appreciated if anyone knows how.*/
#include <sys/mount.h>
/*To mkdir /dev/shm without writing to the rootfs, need to mount /dev as tmpfs
as well, which hides what's in /dev, so we also need mknod.*/
#include <sys/types.h>
#include <sys/stat.h>

#define RDMOD_GOOD 0
#define RDMOD_DEVSHM_OPEN_E 1
#define RDMOD_DEVSHM_READ_E 2
#define RDMOD_DEVSHM_MOUNT_E 3
#define RDMOD_DEVSHM_MKDIR_E 4
#define RDMOD_DEVMTD_MKNOD_E 5
#define RDMOD_DEV_OPEN_E 6
#define RDMOD_DEV_READ_E 7
#define RDMOD_DEV_MOUNT_E 8
#define RDMOD_CAL_INIT_E 9
#define RDMOD_CAL_READ_E 10
#define RDMOD_CAL_WRITE_E 11

int streamHasStr(FILE *, char const *);

static char * mounts = "/proc/self/mounts";
static char * devshm = "/dev/shm";
static char * devmtd = "/dev/mtd1";
static char * dev = "/dev";
static char * devtmpfs = "/dev tmpfs";
static char * rd_mode_flags[] =
{
 "master",
 "no-omap-wd",
 "no-ext-wd",
 "no-lifeguard-reset",
 "serial-console",
 "no-usb-timeout",
 "sti-console",
 "no-charging",
 "force-power-key"
};
static char * help_text[] =
{
 "R&D Mode Control",
 "  -q\t\tQuery current R&D Mode flags",
 "  -e\t\tEnable R&D Mode",
 "  -d\t\tDisable R&D Mode",
 "  -s [flags]\tSet specified R&D Mode flags",
 "  -c [flags]\tClear specified R&D Mode flags",
 "  -h\t\tThis help text",
 "  -l\t\tList the valid R&D Mode flags",
 "  -p\t\tPrint the literal R&D Mode CAL area string",
 "  -w [string]\tWrite string directly to the R&D Mode CAL area"
};

main(int argc, char * argv[])
{
 size_t i; /*careful with 'i', it's reused as a loop counter everywhere*/
 if(argc == 1)
 {
  for(i = 0; i < 10; i+=1)
  {
   printf("%s\n", help_text[i]);
  }
  printf("Valid default R&D Mode flags:\n");
  for(i = 1; i < 9; i+=1)
  {
   printf("  %s\n", rd_mode_flags[i]);
  }
  return RDMOD_GOOD;
 }
 
 /*Note: Maemo 5's /proc/mounts is a symlink to /proc/self/mounts anyway*/
 FILE * mountsfile = fopen(mounts, "r");
 if(!mountsfile)
 {
  printf("Error opening %s, cannot determine if %s exists.\n", mounts, devshm);
  return RDMOD_DEVSHM_OPEN_E;
 }

 _Bool neededshm = 0, neededdev = 0;
 int tempint = streamHasStr(mountsfile, devshm);
 if(tempint == EOF)
 {
  printf("Error reading %s, cannot determine if %s exists.\n", mounts, devshm);
  fclose(mountsfile);
  return RDMOD_DEVSHM_READ_E;
 }
 else if(!tempint)
 {
  /*If no /dev/shm tmpfs, check if /dev is a tmpfs for that matter.*/
  fseek(mountsfile, 0, SEEK_SET);
  if(!mountsfile)
  {
   printf("Error opening %s, cannot determine if %s exists.\n", mounts, dev);
   return RDMOD_DEV_OPEN_E;
  }
  int tempint = streamHasStr(mountsfile, devtmpfs);
  {
   if(tempint == EOF)
   {
    printf("Error reading %s, cannot determine if %s exists.\n", mounts, dev);
    fclose(mountsfile);
    return RDMOD_DEV_READ_E;
   }
   else if(!tempint)
   {
    if(mount("none", dev, "tmpfs", MS_NOATIME, ""))
    {
     printf("Error mounting tmpfs %s to make temporary %s on.\n", dev, devshm);
     fclose(mountsfile);
     return RDMOD_DEV_MOUNT_E;
    }
    neededdev = 1;
    if(mkdir(devshm, S_IRWXU))
    {
     printf("Error making directory %s, unable to use semaphores.\n", devshm);
     tempint = RDMOD_DEVSHM_MKDIR_E;
     goto cleanup_files;
    }
    if(mknod(devmtd, S_IFCHR|S_IRUSR|S_IWUSR, makedev(90, 2)))
    {
     printf("Error making %s node, unable to open CAL partition.\n", devmtd);
     tempint = RDMOD_DEVMTD_MKNOD_E;
     goto cleanup_files;
    }
   }
  }
  if(mount("none", devshm, "tmpfs", MS_NOSUID|MS_NODEV|MS_NOATIME, ""))
  {
   printf("Error mounting %s, unable to use semaphores.\n", devshm);
   tempint = RDMOD_DEVSHM_MOUNT_E;
   goto cleanup_files;
  }
  neededshm = 1;
 }
 fclose(mountsfile);
 mountsfile = NULL;
 
 size_t index = 1;
 struct cal * cal_s;
 char * rd_mode_current;
 char * rd_mode_string;
 void * tmp_ptr = NULL;
 unsigned long len = 0;
 if(cal_init(&cal_s) < 0)
 {
  printf("Failed to init CAL.\n");
  return RDMOD_CAL_INIT_E;
 }
 tempint = cal_read_block(cal_s, "r&d_mode", &tmp_ptr, &len, CAL_FLAG_USER);
 if(tempint == CAL_ERROR_NOT_FOUND)
 {
  /*The device has never written a CAL area "r&d_mode" block, so no block was
  found with that name. Safe to proceed.*/
 }
 else if(tempint != CAL_OK)
 {
  cal_finish (cal_s);
  printf ("Error trying to access R&D Mode area from CAL.\n");
  return RDMOD_CAL_READ_E;
 }
 if(len < 1 && !tmp_ptr)
 {
  printf("R&D Mode area seems empty; R&D Mode has likely never been set.\n");
  /*Resist the temptation to optimize in a way that 'fuses' these pointers to
  the same location: if you do, then realloc() of one will leave the other
  dangling.*/
  rd_mode_current = (char *) malloc(1);
  rd_mode_string = (char *) malloc(1);
  *rd_mode_current = '\0';
  *rd_mode_string = '\0';
 } else {
  /*len + 1 to make room for terminating null that C-strings need.*/
  rd_mode_current = (char *) malloc(len + 1);
  rd_mode_string = (char *) malloc(len + 1);
  memcpy(rd_mode_string, (char *) tmp_ptr, len);
  free(tmp_ptr);
  rd_mode_string[len] = '\0';
  strcpy(rd_mode_current, rd_mode_string);
 }

 while(index < argc)
 {
  if(!strcmp(argv[index], "-h"))
  {
   for(i = 1; i < 10; ++i)
   {
    printf("%s\n", help_text[i]);
   }
  }
  if(!strcmp(argv[index], "-l"))
  {
   printf("Valid default R&D Mode flags:\n");
   for(i = 1; i < 9; ++i)
   {
    printf("  %s\n", rd_mode_flags[i]);
   }
  }
  else if(!strcmp(argv[index], "-p"))
  {
   printf("%s\n", rd_mode_string);
  }
  else if(!strcmp(argv[index], "-e"))
  {
   rd_mode_string = (char *) realloc(rd_mode_string, (strlen(rd_mode_flags[0]) + 1));
   rd_mode_string = strcpy(rd_mode_string, rd_mode_flags[0]);
   printf("R&D Mode enabled.\n");
  }
  else if(!strcmp(argv[index], "-d"))
  {
   *rd_mode_string = '\0';
   printf("R&D Mode disabled.\n");
  }
  else if(!strcmp(argv[index], "-q"))
  {
   for(i = 1; i < 9; ++i)
   {
    /*Using rd_mode_string instead of rd_mode_current makes it act like prior
    parameters already took effect.*/
    if(strstr(rd_mode_string, rd_mode_flags[i]))
    {
     printf("%s flag is on.\n", rd_mode_flags[i]);
    }
    else
    {
     printf("%s flag is off.\n", rd_mode_flags[i]);
    }
   }
  }
  else if(!strcmp(argv[index], "-s"))
  {
   ++index;
   while(index < argc)
   {
    for(i = 1; i < 9; ++i)
    {
     if(!strcmp(argv[index], rd_mode_flags[i]))
     {
      if(!strstr(rd_mode_string, rd_mode_flags[i]))
      {
       rd_mode_string = (char *) realloc(rd_mode_string, (strlen(rd_mode_string) + strlen(rd_mode_flags[i]) + 2));
       strcat(rd_mode_string, ",");
       strcat(rd_mode_string, rd_mode_flags[i]);
       printf("%s is now set.\n", rd_mode_flags[i]);
      }
      else
      {
       printf("%s was already set.\n", rd_mode_flags[i]);
      }
      break;
     }
    }
    if(i == 9)
    {
     --index;
     break;
    }
    ++index;
   }
  }
  else if(!strcmp(argv[index], "-c"))
  {
   ++index;
   while(index < argc)
   {
    for(i = 1; i < 9; ++i)
    {
     if(!strcmp(argv[index], rd_mode_flags[i]))
     {
      char *substring;
      if(substring = strstr(rd_mode_string, rd_mode_flags[i]))
      {
       strcpy((substring - 1), (substring + strlen(rd_mode_flags[i])));
       printf("%s is now cleared.\n", rd_mode_flags[i]);
      }
      else
      {
       printf("%s was already cleared.\n", rd_mode_flags[i]);
      }
      break;
     }
    }
    if(i == 9)
    {
     --index;
     break;
    }
    ++index;
   }
  }
  else if(!strcmp(argv[index], "-w"))
  {
   ++index;
   if(index < argc)
   {
    rd_mode_string = (char *) realloc(rd_mode_string, (strlen(argv[index]) + 1));
    rd_mode_string = strcpy(rd_mode_string, argv[index]);
    printf("\"%s\" was written.\n", rd_mode_string);
   }
  }
  ++index;
 }
 
 if(strcmp(rd_mode_string, rd_mode_current))
 {
  if(cal_write_block(cal_s, "r&d_mode", rd_mode_string, strlen(rd_mode_string), CAL_FLAG_USER) < 0)
  {
   printf("Failed to write to the R&D Mode area of CAL.\n");
   tempint = RDMOD_CAL_WRITE_E;
   goto cleanup;
  }
 }
 tempint = RDMOD_GOOD;

cleanup:
 cal_finish(cal_s);
 if(rd_mode_string)
 {
  free(rd_mode_string);
 }
 else
 {
  printf("rd_mode_string is null!\nEverything is fine, but you should yell at the developer to check the code.\n");
 }
 if(rd_mode_current)
 {
  if(rd_mode_current != rd_mode_string)
  {
   free(rd_mode_current);
  }
  else
  {
   printf("rd_mode_current and rd_mode_string pointed to the same spot!\nEverything is fine, but you should yell at the developer to check the code.\n");
  }
 }
 else
 {
  printf("rd_mode_current is null!\n Everything is fine, but you should yell at the developer to check the code.\n");
 }
cleanup_files:
 if(mountsfile)
 {
  fclose(mountsfile);
 }
 if(neededshm)
 {
  umount(devshm);
 }
 if(neededdev)
 {
  umount(dev);
 }
 return tempint;
}

int streamHasStr(FILE * stream, char const * str)
{
 /*i and tempchar are never in simultaneous use. Union saves drop of memory.*/
 union
 {
  size_t i;
  int tempchar;
 } u;

virtual_loop:
 do
 {
  u.tempchar = fgetc(stream);
 }
 while(u.tempchar != (int) str[0] && u.tempchar != EOF);
 if(feof(stream))
 {
  return 0;
 }
 if(ferror(stream))
 {
  return EOF;
 }
 for(u.i = 1; str[u.i] != '\0'; u.i+=1)
 {
  if(fgetc(stream) != (int) str[u.i])
  {
   goto virtual_loop;
  }
 }
 return 1;
}
Due to the source code growing in size and the forum software being limited to 15000 characters per post (bad coding, indefinite sized posts with splicing in the backend transparent to the user is trivial to do right), I've had to move the remainder of this post to:

http://talk.maemo.org/showpost.php?p...7&postcount=40

Last edited by Mentalist Traceur; 2013-11-08 at 04:10. Reason: Updated source | had to splice post | updated source | updated source
 

The Following 14 Users Say Thank You to Mentalist Traceur For This Useful Post:
hawaii's Avatar
Posts: 1,030 | Thanked: 792 times | Joined on Jun 2009
#2
I think a small hat-tip to q12 would be nice -- but that's at your discretion. I don't suppose you know what other "non-modifiable" information is stored inside CAL? I'd love to have a table with values and address lookups. Maybe I'll find a tattered N900 and start mapping....

Last edited by hawaii; 2011-08-09 at 23:16.
 

The Following 2 Users Say Thank You to hawaii For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#3
I edited the source to include some more printf's, indicating when something is enabled/disabled/etc. (I do want to credit qwerty12 in some way, but I'm not sure I want to do it in-code, and I can't really think of a good place in-code-comment to do it, or a proper way to word it. "Credit goes to [qwerty12's name here] for writing the code that was used for libcal reference" is wordy, methinks, but I can't think of an efficient-er way of writing the same meaning.)

Also, a clarification (for those who don't read or understand the code): although you can do something like rdmod -e -e -d -e -d -w abcdefghijklmnopqrstuvwxyz -d -e -s [all flags] -e -d, it won't actually write to the CAL area until it's done processing all the steps - so you'll never get more than at most one write to flash memory per execution, AND, if it finds the original string it reads from cal is identical to the one you entered, it won't write anything, saving flash writes on those stages.

As for Hawaii's question I don't know much about CAL - I know somewhere in there it stores the lock code for the device, and if you look at the original R&D Mode Control thread by qwerty12, someone did some straces of libcal. Another person in some thread I don't remember claimed to have worked on an open source dsme replacement, and had succeeded in reverse-engineering read-only functionality of libcal.

I'll gather the links and sources to everything I can find on the subject to a wiki page and link that here when I get the time.
 

The Following 4 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 69 | Thanked: 55 times | Joined on Nov 2009
#4
Usually "Based on work by XXXXXX" under your copyright line or as comment in the relevant code should be ok.
 

The Following 3 Users Say Thank You to farmatito For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#5
Originally Posted by farmatito View Post
Usually "Based on work by XXXXXX" under your copyright line or as comment in the relevant code should be ok.
I like that. Edited accordingly.
 

The Following 2 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#6
*Facepalm* I finally got to test it in my boot-shell /sbin/preinit mod, and... it segmentation faults. Works just fine outside of that shell. Just breaks in the utterly basic environment provided by my /sbin/preinit shell.

Anyone mind checking if this works in pali's recovery shell bootmenu item?

- Edit -
I would've tested earlier, even before posting, but the then-current busybox shell wasn't capable of running that early in the boot due to the interim file-in-tmp solution to the history bugs.

Last edited by Mentalist Traceur; 2011-08-23 at 23:24.
 

The Following 2 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#7
I'm not sure if this is of any interest to anyone but me, BUT the cal-tool works from the /sbin/preinit shell. So rdmod -p segmentation faults, but cal-tool -f (which does the same thing, prints the R&D mode string) works.

Eventually I'll start comparing straces and see what I can figure out, but it doesn't make sense to me why my program would fail at that stage of boot, as far as I can deduce, at the cal.h derived functions.
 

The Following 3 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#8
Been bouncing around ideas in my head about this. First of all, this might be the reason why qwerty12's R&D Mode Control (non-CLI) segfaults when ran in the /sbin/preinit launched shell. (Until I noticed this I blamed it on trying to load graphical elements from within a framebuffer console.)

Another thought - the open source getbootstate binary pali made uses libcal for reading the R&D mode flags. Which suggests somewhere I'm doing something wrong, because getbootstate is executed by /sbin/preinit before my shell runs, if I recall correctly. (Or maybe just after, but they're executed close together.) So it's not libcal by itself at fault, which is what I was thinking might be involved.

I will solve this eventually, when life gives me enough time again, the few users of my code have my word on that. My current plan is to start by looking at Pali's open source getbootstate code and see how he uses the libcal stuff.
 

The Following 2 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 2,225 | Thanked: 3,822 times | Joined on Jun 2010 @ Florida
#9
I'm just going to stream-of-consciousness here, both for personal benefit and because it might help someone else some day.

I noted another thing getting my second N900 set-up: "Failed to read cal area" error message comes out when trying to run my rdmod (which for those not following is what I'm calling the compiled result of the code above on my N900s) binary.

Here's the interesting bit - the N900 is freshly flashed, and has had nothing done to the R&D Mode flag area done since then. Thinking back, I had this occur on my first N900 before, back when I first compiled qwerty12's code. Then I noticed disabling the R&D mode with the flasher tool served to make qwerty12's R&D Mode Control work. I shrugged it off and assumed it meant that my CAL area was messed up earlier at the time. But this happening on a completely newly reflashed N900 suggests that a 'virgin' N900 doesn't have the R&D mode part of the CAL area set up the same as normal. Probably minor irrelevant trivia to most people, but I thought it was interesting. Now the question is, does 'virgin' in this context mean just 'freshly reflashed', 'freshly totally reflashed, eMMC included', or 'never R&D Mode configured ever before'?

Anyone who has N900s which have been freshly fiasco flashed, freshly eMMC flashed, and never put into R&D mode ever, who is interested in helping me by running my code on their device and reporting back, would be greatly appreciated. You don't even have to try to change the R&D flags, just "rdmod -q" will suffice for what I'm looking for. Actually, I'm even willing to post my code pre-compiled for people who are willing to test but haven't the desire to compile, because experience has thus far shown both mine and qwerty12's GUI predecessor to be safe at changing R&D mode flags.

On my side of things when I get the time I'm going to throw caution to the wind here and test what happens if I compile a version of the program with that check removed, and if it fails the check after that, with that check removed too. It's possible those checks are unnecessary in practice for one reason or another.
 

The Following 4 Users Say Thank You to Mentalist Traceur For This Useful Post:
Posts: 1,808 | Thanked: 4,272 times | Joined on Feb 2011 @ Germany
#10
@Mentalist Traceur,

I'd be willing to try that, but better if you provide a compiled binary (I'm just starting to learn to use scratchbox, and anyway have no time when I'm at home..)

My N900 was bought (new) in Jan 2011. Update to PR1.3 was OTA. I have kernel-power v47 installed (no CSSU, no other low-level tweaks).

It's never been reflashed (yet . The only "flashing" was the OTA 1.3 update and the kernel-power flashing, so I'd assume the R&D area has never been touched.

If that's OK for testing, then please post the rdmod binary!
 

The Following 2 Users Say Thank You to reinob For This Useful Post:
Reply

Tags
command line, on device, r&d mode

Thread Tools

 
Forum Jump


All times are GMT. The time now is 12:00.