This file is part of Palantir documentation.
Copyright (c) 1998 Dim Zegebart, Russia, Moscow
zager@post.comstar.ru

Palantir - Events kernel for Allegro.
Copyright 1997 Dim Zegebart, Moscow Russia.
E-mail zager@post.comstar.ru
http://www.geocities.com/siliconvalley/pines/7817
Tel. in Moscow (095)9560389
Fax. in Moscow (095)9563540
Version : 0.4

General note to current version :
This document covers Palantir v0.4 Additional information can be
obtained from files located at /palantir/do/lwp, /palantir/doc/dzcomm

Acknowledgements :
 The DJGPP team for making a professional quality compiler.
 Shawn Hargreaves for the excellent Allegro.
 Sam Vincent for creating SVAsync. I use some of his code.
 Bill Currie for creating BCSerio. I use it as help reference.
 Chirayu Krishnappa for many good ideas and fixes.
 Josh Turpen and Sengan Short for creating LWP.
 Paolo De Marino for creating PDMLWP.
 Salvador Eduardo Tropea (SET) for fixing RTS/CTS, handling IRQ8-IRQ15 and
 three sample programm.
 Nils Lohmann for fixing 16550A FIFO stuff.
 All guys who test this library and made feedbacks to me.

#include <std_disclaimer.h>

   "I do not accept responsibility for any effects, adverse or otherwise,
    that this code may have on you, your computer, your sanity, your dog,
    and anything else that you can think of. Use it at your own risk."

===================================
============ Copyright ============
===================================

   Palantir is free-ware. You may use, modify, redistribute, and generally
   hack it about in any way you like. See Allegro copyright notes for more
   information.
   Note : PDMLWP library is under LGPL (GPL) copyright and owned by
   Paolo De Marino.
   Look at 'copying.lib' file to learn more about GPL copyrights.

======================================
============ Introduction ============
======================================

What is Palantir ? Brief description is -

- Pre-emtieve multitasking kernel for Allegro with priorities.
- Mutex-semaphores, yield()
- Universal *DEVICE* interface.(KEYBOARD,MOUSE,COMM)
- Transparent (for developer) collecting of hardware and software events.
  The only thing you need, just create handler for desired device.<br>
  No while(TRUE) { if(mouse); if (keyboard); do_move(); do_...} loop.
  Events driven system, instead of it.
- Developer defined level of task priority.
- Start point for creating events driven GUI, SHELL, etc.
- Include DZComm 0.4.1 ( mean what comm port is device in term of Palantir).

======================================
============ Installation ============
======================================

1. Copy palantir.zip to the root directory where you have Allegro
   distribution (source distribution).
2. Unpack palantir.zip preserving directory structure
   (I suppose, the best command is : pkunzip -d -o dzcomm.zip)
3. If archive is not broken or you don't use arj to unpack it, you
   may type 'make -f makefile.dz' now. I recommend to make backup copy
   of your current files 'liballeg.a' and 'allegro.h' before you install
   Palantir.
   NOTE: my makefile.dz is tab-less, so if you want to edit it just
   run any of your favo(u)rite editors :)
4. If all is OK, you may read 'Library description' chapter,
   else mail me directly ;)

===================================
===== General library notes =======
===================================

 The core of Palantir consists of two parts 'threading' and 'messaging'
Threading based on PDMLWP (LWP extender) and messaging based on totally
'new' mechanism introduced in Palantir. (AFAIK, 'new' for Allegro, at least)
How it works ? Nothing complex ... Here it is :
 LWP (light weight process) provides threading mechanism. Thread is a
peace of code running and sharing CPU time with other threads. Each thread
has it's own stack.  Palantir extends the meaning of thread and introduces
new term 'application'. Application is thread which has msg queue. I'm not
alter LPW data structures and API, so you may use all LWP API as described
in it's manual. (Note : the only changes I made is - in original version
lwp_spawn() returns PID, in Palantir it returns pointers to the lwp data
structure). You may have both LWP threads and Palantir apps in your program.

 All PCs, at least 99% of it, has various I/O devices - HDDs, floppies,
keyboard, mouse, etc. Some of them are read-only (e.g. mouse) others are
read/write devices (e.g. comms). In the terms of Palantir each data unit
(byte, word, block) transferred from/to device treated as message. So, each
device may be treated as 'server' or source (even if device is writeable)
and task processes msg may be treated as 'client'.
In Palantir each device has it's own daemon - process collecting all
data from/to device regardless of presence or absent any others 'clients'.
In other words 'daemon' is the main 'client' for each device.
(Note: I say 'each device' but currently Palantir supports only three
devices keyboard_device, mouse_device, comm_device(s). All other hardware
handles in usual way and can't be process via messaging mechanism).
If you want your task to be a 'client' for a device you should 'register'
your task as 'client' for a given 'device'. To do it call
device_client_task_add(). After that, app begin receives msg with device
specific code, so you can process it as you wish.
 To learn more about using of Palantir see library description and look at
ex22.c and pdemo.c in /palantir/pdemo/ directory. ex22.c is changed example
from Allegro distribution. It shows the difference between usual program and
Palantir based one. pdemo.c is general example of using Palantir.

===================================
====== Library description ========
===================================

//- PALANTIR_INIT
int palantir_init(int devs)
 Initialise Palantir kernel.
 Call this function before all other Palantir related stuff.
 palantir_init calls allegro_init, timer_init and lwp_init, so
 you don't need call this function by yourself.
* Parameters :
 int devs - bit field indicates which device you wish to install.
 use mix of DEV_KEYBOARD|DEV_MOUSE|DEV_COMMS to specify desired
 configuration.
* Return value : 1 if OK, 0 if failed.
* Example :
 {
   if (!palantir_init(DEV_KEYBOARD|DEV_COMMS)) abort(); // installs
   // keyboard and comms without mice support.
 }

//- PALANTIR_DEINIT
void palantir_deinit()
 Post APP_STOP to all running apps. Set global variable exit_program to 1.
 Call this function to terminate system.
* Example :
 Here is the minimal keyboard handler you may wish to have -
 int keyboard_handler(a_message code,int p1,int p2)
 { int c=p1;
   char ch=ascii_(c);
   switch(code)
    { case H_KEYBOARD :
        if (ctrl_(c,'Q'))
         { palantir_deinit();
           return(0);
         }
      default:
       break;
    }
   return(1);
 }

//- DZ_APP_NEW
dz_app* dz_app_new(char *szName,int stack,int priority,
                  int (*app_hnd)(a_message code,int p1,int p2))
 This is, probably, the most important function in Palantir, so don't
 skip it description before you go further.
 First of all lets look closer at app data structure. It's taken from
 palantir.h:
 typedef struct
 { lwp *lwp_thread;
   char szName[64];
   int (*msg_handler)(a_message code,int p1,int p2);
   void *app_usrdata;
   fifo_queue *msg_queue;
 } dz_app;

 *lwp_thread - pointer to lwp data structure. After successful spawning
 of an app it points to the underlying lwp thread. Since each app based
 on app_wrapper() (See app_wrapper() description for more details)
 lwp_thread points to the copy of app_wrapper.

 szName[64] - name of application. You provide this name. Currently not
 used by any function, but future release may work with it. For example :
 my_app=get_app_by_name(app_name)

 int (*msg_handler)(a_message code,int p1,int p2) - function performs
 actual msg handling. Should usually return 1, if you want self terminate
 app just return 0 (See app_wrapper() description for more details)
 code - msg code, p1,p2 - msg parameters. Each msg type has it's own
 parameters, so you should refer to msg section below in this text.
 The minimal recommended msg_handler is :
 int my_hnd(a_message code,int p1,int p2)
 { int tmp; //variable used by lwp_enable(disable) macros
   dz_app *app;

   switch(code)
    { case APP_START:
       app=app_(p1); //it's optional
       break;
      case APP_STOP:
       return(0); // do nothing just terminate app.
       break;
      default:
       break;
    }

  return(1); //always return 1 under normal conditions.
             //Returning 0 will couse app to be terminated by system
             //without posting APP_STOP
 }
 Note : main handler goal is to process one msg per call.
 You may also handle msg queue by yourself :
 int my_hnd(...)
{ dz_app *my_app;
   fifo_queue *msg_q;
  dz_msg msg;

   switch(code)
    { case APP_STAR:
       my_app=app_(p1);
       msg_q=my_app->msg_queue;
      default:
      break;
    }
   // now setup your own msg loop
   while(1)
    { lwp_wait_true((int*)&(msg_q->empt)); //sleep until a new msg in queue
      //alternate way is 'if (msg_q->empt) ...' don't sleep until a new msg
      app_msg_get(my_app,&msg);
      switch(msg.code)
       { ... //do whatever stuff you want
         case APP_STOP:
         return(0);
         default:
         break;
       }
    }
   return(1);
 }

 void *app_usrdata - pointer to some app data area. Each app may hold it's
 own set of data. For example : you have a set of cubes to be rotated by
 a thread per cube, so
 for (i=0;i<NUM_CUBES;i++)
  { my_app=dz_app_new(...);
    my_app->app_usrdata=cubes[i];
  }
 ...
 int cube_app(...)
  { static cube *my_cube;
    dz_app *my_app;
    ...
    case APP_START:
      my_app=app_(p1);
      my_cube=my_app->app_usrdata;
    ...
  }

 fifo_queue *app_msg_queue - queue where msgs will be posts. Default size
 is 12K (4096 msgs in queue)

* Parameters of dz_app_new :
 char *szName - pointer to the app's name (64 bytes maximum). If name len
 greater then 64 symbols all trailing chars will be truncated.

 int stack - stack size allocated to app (lwp_thread). The most
 problematic parameter. If app requires stack larger then allocated,
 the most probably result is awful system crash :( As PDMLWP docs says
 recommended minimal size is 4096 (4K)

 priority - the same as in PDMLWP, thread with priority 1 get one timer
 slice, with 2 - two timer slices (twice more then 1), etc.

 int (*app_hnd)(a_message code,int p1,int p2) - msg handler.

 Return value : pointer to the newly started app (dz_app*), NULL if failed.

* Example :
 int my_handler(a_message code,int p1,int p2)
 { ...
 }

 { dz_app *my_app;
   my_app=dz_app_new("Hello world! app",4096,1,my_handler);
 }

//- DZ_APP_KILL
void dz_app_kill(dz_app* app)
 Posts msg APP_STOP to the msg queue of a given app.
 See also : app_wrapper()
 Example :
 { dz_app *my_app;
   my_app=dz_app_new(...);
   ...
   dz_app_kill(my_app);
 }

//- APP_WRAPPER
void app_wrapper(void)
 This wrapper spawned for each new app. It 'listening' app's msg queue
 and calls handler, provided by you, for each msg in queue. Then start, it
 posts msg APP_START to the queue, so you may process it at handler to
 do some startup settings. Then whole system shutdowns, kernel sends
 msg APP_STOP to each app, so you may process it to do some task specific
 deinitiliasation. By default, app_wrapper do return() on APP_STOP.
 It couses killing process to be performed by kernel (see PDMLWP docs for
 more details on lwp process.) If you need to exit app before APP_STOP,
 just return 0 from handler.
* See also : dz_app_kill(),dz_app_new()
* Example : you should never call this function directly. It always called
 by a kernel.

//- DEVICE CLIENT TASK ADD
client_task *device_client_task_add(device *dev,dz_app *task,
                                    client_status client)
 Add client app to the device. Currently Palantir defines such devices :
 keyboard_device,mouse_device,comm_port. First two devices hardcoded and
 you can use them directly. Since comm_port device defined by user via
 DZComm extension, you should use your own definition of it. You may setup
 as many client apps for one device as you like. Not prohibited to
 install one client app to many devices.
* Parameters :
 device *dev - device - source of data.
 dz_app *task - your app which will receive data from device
 client_status client - may take values READ,WRITE,BOTH
 READ means client needs only data what comes from device.
 For keyboard_device and mouse_device this is the only possible value.
 WRITE means client needs only data what writes to device.
 For comm_port this is more useful value.
 BOTH means client needs both read/write device data streams.
* Return values : NULL if failed; pointer to the structure client_task if
 successful.
* Example :
 {
 keyb_app=dz_app_new("KBD APP",16384,1,keyboard_handler);
 device_client_task_add(keyboard_device,keyb_app,READ);
 }
 Or :
 {
 common_app=dz_app_new("COMMON APP",16384,1,common_handler);

 device_client_task_add(keyboard_device,common_app,READ);
 device_client_task_add(mouse_device,common_app,READ);
 }

//- DEVICE_CLIENT_TASK_REMOVE
void device_client_task_remove(device *dev,dz_app *task,client_status client)
 Removes given app from device clients list.
* Parameters :
 device *dev - device source of data
 dz_app *task - previously installed client app.
 client_status client - READ,WRITE,BOTH.
* Example :
 { dz_app *comm_app;
   my_app=dz_app_new("COMM APP",16384,1,comm_handler);

   device_client_task_add(comm_port,comm_app,BOTH);
   ...
   device_client_task_remove(comm_port,comm_app,READ);
   ...
   device_client_task_remove(comm_port,comm_app,WRITE);
 }

//- APP_MSG_GET
int app_msg_get(dz_app *app,dz_msg *msg)
 Get msg from app->msg_queue.
* Return value: 1 if everything is OK; 0 if queue near full,
 i.e. queue->tail>=queue->fill_level.
 By default fill_level=(2*queue->size)/3
 Zero means what msg get successfully, but, probably, some msgs may be
 lost in the future.
* Example:
 { dz_msg my_msg;
   dz_app *my_app;

   while (!app_msg_get(my_app,&msg)
    { process msg stuff ...;
      ...
    }
 }

//- APP_MSG_PUT
int app_msg_put(dz_app *app,a_message code,int p1,int p2)
 Put msg to the app->msq_queue.
* Return value: 1 if everything is OK; 0 if queue near full,
 i.e. queue->tail>=queue->fill_level.
 By default fill_level=(2*queue->size)/3
 Zero means what msg putted successfully, but, probably, some msgs may be
 lost in the future.
* Parameters :
 dz_app *app - app to which msg posted.
 a_message code - on of the code listed in messages.h
 int p1 - msg parameter 1.
 int p2 - msg parameter 2.
 Note : usually p1 and p2 are pointers and it's meaning depend on msg type.
* Example:
 { dz_app *my_app;
   app_msg_put(my_app,APP_HELLO_BUDY,0,0);
 }

//- IDLE_PROC_ADD
int idle_proc_add(int (*hnd)(void))
 Add function called by idled (idle daemon).
 If you have some function which you wish to call periodically, but
 don't want spawn a whole app or lwp_thread and the purpose of
 this function just to do some computations (for example clock), and it
 isolated from other apps and threads there is so-called idled
 (idle daemon) which call it for you each time it gets CPU time slice.
* Parameters :
 int (*hnd)(void) - pointer to function which you wish to be called by idled
* Return values : 1 if successful, 0 if failed.
* Example :
 int clock(void)
 { ...;
   return(1);
 }
 {
   if (!idle_proc_add(clock));
 }
 Note : if you return 0 from idle_proc, it will be removed from
 function list which called by idled.

//- IDLE_PROC_REMOVE
void idle_proc_remove(int (*hnd)(void))
 Removes functions from idled list previously added by idle_proc_add
* Parameters :
 int (*hnd)(void) - pointer to function which called by idled.
 Note : if you return 0 from idle_proc, it will be removed from
 function list which called by idled,
 so you don't need call idel_proc_remove in such a case.
* Example :
 int clock(void)
 { ...;
   return(1);
 }
 {
   if (!idle_proc_add(clock));
   ...
   idle_proc_remove(clock);
 }

//- SHEDULED_APP_ADD -------------------------
scheduled_app *scheduled_app_add(time_t period,dz_app *app)
 Add app to the sheduled_app_list to be notified with E_TIMER_PERIOD msg.
 Some computations may requires periodical execution. This function allows
 to app know about some period of time have been turn off. You can
 schedule app with many various periods simultaneously. For example add
 1 second period to check alarm, 10 min period to do autosave, etc.
* Parameters :
 time_t period - period in seconds.
 dz_app *app - app which will be notified.
* Return value : NULL if failed ; pointer to scheduled_app structure on
 success.
* Example :
 int my_hnd(a_message code,int p1,int p2)
 {  int period;
    switch(code)
     { case E_TIMER_PERIOD:
         period=p1;
         if (period==1) check_alarm();
         else if (period==600) do_auto_save();
         break;
       case default:
         break;
     }
 }
 { dz_app *my_app;

   my_app=dz_app_new("MY SCHEDULED APP",4096,1,my_hnd);
   scheduled_app_add(1,my_app); //1 seconds period
   scheduled_app_add(600,my_app); //10 minutes period
 }

//- SCHEDULED_APP_REMOVE
 Remove app with given period from scheduled_app_list.
 Since you may schedules app with many periods you should specify which
 period will be removed.
* Parameters :
 dz_app *app - scheduled app.
 time_t period - removing period.
* Example :
 int my_hnd(a_message code,int p1,int p2)
 {  int period;
    static dz_app *my_app;
    switch(code)
     { case APP_START:
         my_app=app_(p1);
         break;
       case E_TIMER_PERIOD:
         period=p1;
         if (period==1)
          { if (check_alarm()==ALARM_OK)
             { scheduled_app_remove(my_app,1); //we don't need 1 period
                                               //msg now
             }
          }
         else if (period==600) do_auto_save();
         break;
       case default:
         break;
     }
 }
 { dz_app *my_app;

   my_app=dz_app_new("MY SCHEDULED APP",4096,1,my_hnd);
   scheduled_app_add(1,my_app); //1 seconds period
   scheduled_app_add(600,my_app); //10 minutes period
 }

EOF
