#include <stdio.h>
#include <asm/io.h>

/* why aren't these in io.h? */
static inline void outw(unsigned short value, unsigned short port) {
  asm volatile ("outw %w0,%w1" : :"a" (value),"d" (port));
}

static inline unsigned int inw (unsigned short port) {
  unsigned _v;
  asm volatile ("inw %w1,%w0" :"=a" (_v):"d" (port),"0" (0));
  return _v;
}

static inline void unlock_cpupwrmode () {
  outb (0, 0x23); outb (0x80, 0x22); outw (0, 0x22);
}

static inline void lock_cpupwrmode () { outw (0x100, 0x22); }

static inline void open_360sl () {
  (void) inb (0xfc23);
  (void) inb (0xf023);
  (void) inb (0xc023);
  (void) inb (0x0023);
}

#define IDXLCK		0xfa
#define CFGINDEX	0x24
#define CFGDATA		0x25

static unsigned char read_360sl (unsigned char port) {
  outb (port, CFGINDEX);
  return inb (CFGDATA);
}

static void write_360sl (unsigned char port, unsigned char data) {
  outb (port, CFGINDEX);
  outb (data, CFGDATA);
}

static inline void close_360sl () { write_360sl (IDXLCK, 1); }

#define ASMI_ADDRL	0x84
#define ASMI_ADDRH	0x85
#define SYS_EVNT_CFG2	0xb5

int get_magic_port () {
  unsigned char low, high;
  unsigned char cfg2;
  int port;
  open_360sl ();
  low = read_360sl (ASMI_ADDRL);
  high = read_360sl (ASMI_ADDRH);
  cfg2 = read_360sl (SYS_EVNT_CFG2);
  close_360sl ();
  port = low + 256 * high;
  if (cfg2 & 4)
    return port;
  else
    return -1;
}

int asmi_port;
struct asmi_regs {
  unsigned ax, bx, cx, dx;
};

int asmi (struct asmi_regs *r) {
  int carry;
  asm ("outb %%al,%%dx ; movl %%eax,%0 ; movl $0,%%eax ; adc $0,%%eax"
       : "=g" (r->ax), "=b" (r->bx), "=c" (r->cx), "=d" (r->dx), "=a" (carry)
       : "d" (asmi_port), "a" (r->ax), "b" (r->bx), "c" (r->cx));
  return carry;
}

const char *const apm_error_table[] = {
  0,
  "Power Management functionality disabled",
  "Interface connection already in effect",
  "Interface not connected",
  "Real mode connection not established",
  "16-bit protected mode interface already established",
  "16-bit protected mode interface not supported",
  "32-bit protected mode interface already established",
  "32-bit protected mode interface not supported",
  "Unrecognized Device ID",
  "Parameter value in CX out of range",
  0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  "Can't enter requested state",
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, "No APM present",
                       0, 0, 0, 0, 0, 0, 0, 0, 0,
};

const char *apm_error (ax) {
  const char *msg;
  static char buf[60];
  ax >>= 8;
  if (ax > sizeof (apm_error_table) / sizeof (apm_error_table[0]))
    goto bad_value;
  msg = apm_error_table[ax];
  if (msg)
    return msg;
 bad_value:
  sprintf (buf, "Unknown APM error %x", ax);
  return buf;
}

struct asmi_regs r;

void disconnect() {
  r.ax = 0x5304;		/* disconnect */
  r.bx = 0;
  if (asmi (&r))
    printf ("disconnect: %s\n", apm_error (r.ax));
  else
    printf ("disconnect successful\n");
  /*end untested*/
}	

main () {
  if (iopl (3) < 0)
    {
      perror ("iopl");
      return 1;
    }

  asmi_port = get_magic_port ();
  if (asmi_port == -1)
    {
      printf ("ASMI not enabled\n");
      return 1;
    }
  printf ("ASMI port is 0x%x\n", asmi_port);

  r.ax = 0x5300;
  r.bx = 0;
  if (asmi (&r) != 0)
    printf ("APM install check: %s\n", apm_error (r.ax));
  else
    {
      printf ("APM install check: v%02x.%02x%c%c flags %x [",
	      r.ax >> 8, r.ax & 0xff, r.bx >> 8, r.bx & 0xff, r.cx);
      /* now print flags */
      if (r.cx & 1) printf (" 16");
      if (r.cx & 2) printf (" 32");
      printf (" idle=%s bios-pm-%s ]\n",
	      (r.cx & 4) ? "slow" : "stop",
	      (r.cx & 8) ? "disabled" : "enabled");
    }

  r.ax = 0x5301;
  r.bx = 0;
  if (asmi (&r) == 0)
    printf ("interface connect successful\n");
  else
    printf ("interface connect: %s\n", apm_error (r.ax));

  r.ax = 0x530a;		/* check power status */
  r.bx = 1;			/* whole system */
  if (asmi (&r))
    printf ("battery check: %s\n", apm_error (r.ax));
  else
    {
      int ac = r.bx >> 8;
      int batt = r.bx & 0xff;
      int pct = r.cx & 0xff;
      printf ("battery check: AC status %s, battery status %s, ",
	      (ac == 0 ? "off-line"
	       : ac == 1 ? "on-line"
	       : ac == 255 ? "unknown"
	       : "???"),
	      (batt == 0 ? "high"
	       : batt == 1 ? "low"
	       : batt == 2 ? "critical"
	       : batt == 3 ? "charging"
	       : batt == 255 ? "unknown"
	       : "???"));
      if (pct <= 100)
	printf ("remaining charge %d%%\n", pct);
      else if (pct == 255)
	printf ("remaining charge unknown\n");
      else
	printf ("remaining charge ???\n");
    }

#ifdef SHOW_PM_EVENTS
  printf ("Looking for PM events...\n");
  /* Should put in a SIGINT,SIGHUP,SIGTERM handler to disconnect...  */
  while (1) {
    fflush (stdout);
    sleep (1);
    r.ax = 0x530b;
    if (asmi (&r)) {
      if (r.ax >> 8 == 0x80)
	/* no events were available, poll again */
	continue;
      if (r.ax >> 8 == 0x03) {
	      r.ax = 0x5301;
	      r.bx = 0;
	      if (asmi (&r) != 0)
		      printf ("interface connect: %s\n", apm_error (r.ax));
	      continue;
      }
      printf ("PM event fetch: %s\n", apm_error (r.ax));
    } else {
      /* type 1 - standby request
	 type 2 - suspend request
	 type 3 - normal resume
	 type 4 - critical resume (or, "something happened and the bios dealt")
	 type 5 - battery low */
      printf ("PM event type %x\n", r.bx);
      switch (r.bx) {
      case 1:
	      r.ax = 0x5307;
	      r.bx = 1;
	      r.cx = 1;
	      asmi(&r);
	      break;
      case 2:
	      sync();
	      sleep(1);
	      r.ax = 0x5307;
	      r.bx = 1;
	      r.cx = 2;
	      asmi(&r);
	      break;
      case 3:
      case 4:
	      system("/etc/clock -s");
	      break;
      }
    }
  }
#endif

  disconnect();
  
  return 0;
}

/* Results:

   Dell 325SLi (386SL/25; mine)
			Phoenix 80386 ROM BIOS PLUS v 1.10 A06
			port 0x26, v01.00PM flags 7
			all(?) events are type 4
   AST PowerExec (Ted Ts'o's)
			??
   			port 0xe6, v01.00PM flags b
   CompuDyne SubNote 486SL/25 (Mark Eichin's)
			??
			hangs (sigh)

   What's yours?  Please e-mail raeburn@cygnus.com.  */

