Monday, March 6, 2017

Running a GDI printer under Linux (part 2)

Running a GDI printer under Linux (part 2)





If you read my previous article, you may have noticed that I haven't given a protocol to send the compressed data to the printer device. By that time, I already have dissected it. Now, I'm going to show you a very hard hack for making this baby work under Linux, with a simple program.
As my program will access directly machine's hardware, including disabling and re-enabling interrupts for the critical parts, you will have to run it as root (superuser). Please don't consider this program a gem, not even an alpha-level software. It's just a hack for showing that is possible to construct a device driver for the printer.

The code

Now let's look at the code. It even starts to print earlier than with the OEM driver distributed by Samsung (M$ is not very good at improving their own software!) for their ML-85G. But don't expect something fancy, as this is very experimental yet. I don't know how to send several pages at once, but I'm working on this "feature".
The fragment of code
  outb(0x7f,LPPORT);
  outb(0x83,LPPORT);
  outb(0x40,LPPORT);
  outb(0x80,LPPORT);
just send a block of white space, as discussed in the previous article. If you change this to something like
  outb(0x7f,LPPORT);
  outb(0xbf,LPPORT);
  outb(0x4f,LPPORT);
  outb(0x80,LPPORT);
you will see a narrow 1-pixel high line. This is what the program actually does now. I will have to write a program to translate pbm or other graphic data format to this compressed data format.
The heart of the program are the command sequences cmd_seq, cmd_seq2, etc. They access the registers fo the chip inside the printer to control it. I don't know exactly what they do, but who cares? I just mimic the flow of data of other programs to tell him to do what I need! This is my "secret".
Each pair of values of cmd_seq are called cmd_code and cmd_type. The cmd_type controls the generation of a different kind of "strobe" give to the parallel port and is really required. Don't ask me why.
Another interesting "feature" is that, if you choose carefully where to stop giving commands to the printer¸ you can. See the commented part where it prints "going to sleep" and sleeps for some seconds. The original driver (from Samsung, I think) never stops giving commands to the printer. Well, this will be subjected to more experimentation. I don't want to have a driver that send data to the device each millisecond or so, just to see if the printer is out-of-paper, or have been disconnected from the computer. It's a terrible waste of time and cpu resources.
Here is the code:
#include 
#include 
#include 
#include 
//#include "ml85p.h" this is already included here :)

#define PIXELS_BY_ROW  4800
#define ROWS_BY_BAND   104
#define MAX_BAND  61

/* We must control the device bypassing the kernel driver,
 * because the interface don't follow any standard handshake
 * procedure. In the future, we can write a real device driver
 * to overcome this inconvenience.
 */
#define LPPORT   0x378

#define COUTLP(d) outb( (d),LPPORT+2 ); usleep(10000)
#define OUTLP(d) outb( (d),LPPORT ); usleep(10000)
#define SINLP() (inb( LPPORT+1 ))


int cmd_seq[] = { 
0x80, 1, 0xa0, 1, 0x00, 0, 0xa0, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8b, 1, 0x8b, 1, -2,
 0x89, 0, 0x8c, 1, 0x8c, 1, 0x04, 0, 0x94, 1, -2, 
 0x3f, 0, 0x95, 1, 0x58, 0, 0x94, 1, 0x95, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8b, 1, 0x8b, 1, 0x9a, 0,
-1 };

int cmd_seq2[] = {
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8b, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, 0x97, 1, 0x00, 0, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8b, 1, 0x8b, 1, 0x9d, 0, -2,
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0xa0, 1, 
 0x00, 0, 0xa0, 1, 0x81, 1, 0x15, 0, 0x82, 1,
 0x00, 0, 0x83, 1, 0x00, 0, 0x84, 1, 0x00, 0,
 0x85, 1, 0x24, 0, 0x86, 1, 0x01, 0, 0x87, 1, 0x3b, 0,
 0x88, 1, 0x0d, 0, 0x81, 1, 0x82, 1, 0x83, 1, 0x84, 1,
 0x85, 1, 0x86, 1, 0x87, 1, 0x88, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, -2,
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, 0x93, 1, 0x00, 0, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x80, 1, -2, 
-1 }; 

int cmd_seq3[] = {
0x89, 1, 0x8a, 1, 0x97, 1, 0x00, 0, 0xa6, 1, 0x07, 0, 0xa7, 1, -2, 
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x8e, 1, 
 0x93, 1, 0x00, 0, 0x80, 1,  
-1 };

int cmd_pause[] = {
0x89, 1, 0x8a, 1, 0xa6, 1, 0x07, 0, 0xa7, 1, 0x80, 1, 
-1 };

static int last_ctrl=0x06;
 
void
sdelay() {
 __sti();
 usleep(5000);
 __cli();
}

void
putmsg (char *msg) {
 __sti();
 printf("%s\n",msg);
 __cli();
}

int 
sinp () {
 return (inb(LPPORT+1));
}

int 
sinpwfast (wait) {
 __sti();
 while (inb(LPPORT+1) != wait) ;
 __cli();
}

int 
sinpw (wait) {
 //int timeout=80000;
 __sti();
 printf("sinpw: %x\n",wait);

 while (inb(LPPORT+1) != wait) ;
 /*{
  if (timeout-- == 0) {
   __sti();
   iopl(0);
   exit(1);
  }
 }*/
 __cli();
}

void
outlp( data ) {
 //printf("O0,%x\n",data);
 outb(data,LPPORT);
}

void
coutlp( data ) {
 //printf("O2,%x\n",data);
 outb(data,LPPORT+2);
}

void
lpwait (data) {
 __sti();
 printf("lpwait(%x)\n",data);
 while (SINLP() != 0xff) ;
 __cli();
}

void
toggle_wait (times) {
 int i;
 __sti();
 last_ctrl = inb(LPPORT+2)&2;
 for (i=0;i<9;i++) {
  printf("I9(%x) ",inb(LPPORT+1));
  fflush(stdout);
  coutlp( last_ctrl );
  last_ctrl = last_ctrl ^ 2;
 }
 coutlp(6);
 __cli();
}

void
toggle_control (times) {
 int i;
 __sti();
 for (i=0;i= 0) {
   cmd_type = *cmd++;
  }
  if (cmd_code == -1) {
   break;
  }
  if (cmd_code == -2) {
   __sti();
   usleep(5000);
   __cli();
   continue;
  }
  lpoutw(cmd_code,cmd_type);
 }
}

static int first_block=1;

void
print_band ( int band ) {
 int line;
 int group;
 char msg[50];
 sprintf(msg,"band %d",band);
 putmsg(msg);
 if (first_block) {
  first_block=0;
  
  outb(0x7f,LPPORT);
  outb(0xbf,LPPORT);
  outb(0x4f,LPPORT);
  outb(0x80,LPPORT);

  for (line=1;line<104;line++) {
   for (group=0;group<242;group++) {
    outb(0x7f,LPPORT);
    outb(0x83,LPPORT);
    outb(0x40,LPPORT);
    outb(0x80,LPPORT);
   }
   outb(0x4c,LPPORT);
   outb(0x83,LPPORT);
   outb(0x40,LPPORT);
   outb(0x80,LPPORT);
  }
 }
 else {
  for (line=0;line<104;line++) {
   for (group=0;group<242;group++) {
    outb(0x7f,LPPORT);
    outb(0x83,LPPORT);
    outb(0x40,LPPORT);
    outb(0x80,LPPORT);
   }
   outb(0x4c,LPPORT);
   outb(0x83,LPPORT);
   outb(0x40,LPPORT);
   outb(0x80,LPPORT);
  }
 }
}

void
print_page ( int maxbands, int firstpage ) {
 int band;
 int *cmd;
 int cmd_code,cmd_type;
 
 putmsg("phase 4 *** printing page");
 if (firstpage) {
  cmd = cmd_seq2;
 } 
 else {
  cmd = cmd_seq3;
 }
 while (1) {
  cmd_code = *cmd++;
  if (cmd_code >= 0) {
   cmd_type = *cmd++;
  }
  if (cmd_code == -1) {
   break;
  }
  if (cmd_code == -2) {
   __sti();
   usleep(5000);
   __cli();
   continue;
  }
  lpoutw(cmd_code,cmd_type);
 }
 coutlp(6);
 outlp(0xff);
 coutlp(4);
 coutlp(5);
 
 for (band=0;band= 0) {
    cmd_type = *cmd++;
   }
   if (cmd_code == -1) {
    break;
   }
   if (cmd_code == -2) {
    __sti();
    usleep(1000);
    __cli();
    continue;
   }
   lpoutw(cmd_code,cmd_type);
  }
  coutlp(6);
  outlp(0xff);
  coutlp(4);
  coutlp(5);
 }
}

int
main ( int argc, char *argv[] ) {
 int c;
 int reset_flag=1;
 int maxbands=25;
 int print_flag=1;
 
 while ((c = getopt(argc,argv,"rnb:")) != -1) {
  switch (c) {
   case 'r': {
    reset_flag = 1 - reset_flag; /* toggle it */
    break;
   }
   case 'b': {
    sscanf(optarg,"%d",&maxbands);
    break;
   }
   case 'n': {
    print_flag=0;
    break;
   }
  }
 }
 //ioperm( LPPORT,3,1 );
 iopl(3);
 __cli();
 if (reset_flag)
  reset_printer();

/* printf("going to sleep...\n");
 sleep(10);
 printf("back to work...\n");
*/
 if (print_flag)
  print_page( maxbands, 1 );

 //ioperm( LPPORT,3,1 );
 __sti();
 iopl(0);
 return 0;
}
Now I have to go back to work. At the next time, I will show some of the tools I have used to get to the point. The main idea is to filter things to extract what matters. For instance, I have written several small tcl scripts to extract the commands that write to the ASIC registers reading the large captured files from Bochs. I have also hacked a little Bochs to send the data to the real printer and to write each i/o statement for the range of the printer ports registers to a compacted log file.
I would like to thank all the people that have written to me. Their encouragement is an essential ingredient to keep me with the patience to explore the unknown, running thousand of times the same tests for discovering the workings of this creature. Of course, it was worth the time spent and I hope other laser winprinters will follow. All of you can count on my help and experience to bring their GDI printers working under Linux too. Please, continue writing to me.
Now I look at my little printer and run the driver above to see it singing. It sounds wonderful, specially after the hard work done for getting it to even start to print. And I play with it saying Print, baby, print!, as the leaves of paper runs through its body.
Rildo Pragana <rildo@pragana.net>

No comments:

Post a Comment