diff --git a/iocCorePort.html b/iocCorePort.html
deleted file mode 100644
index 261e17ee6..000000000
--- a/iocCorePort.html
+++ /dev/null
@@ -1,705 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Porting iocCore
-
-
-
-Nov 17, 1999
-
-
-
-
-
-
-iocCore includes the following components of epics base:
-
--
-Database locking, scanning, and processing
-
--
-Channel access client and server support
-
--
-Standard record types and soft device support
-
--
-Access security
-
--
-Other non-hardware support presently packaged with base
-
-
-
The port is based on the following assumptions:
-
--
-All hardware support is unbundled from base and thus does not need
-to be ported.
-
--
-For iocCore a multithreaded environment is necessary.
-
--
-osi components are defined such that
-
-
--
-vxWorks implementation has minimal overhead compared to vxWorks specific
-calls
-
--
-The components can be implemented via a combination of POSIX, POSIX.4 (posix
-real time), and POSIX threads (pthreads).
-
-
--
-For components that require different implementation for different environments
-
-
--
-The implementation may be via header and/or source files as long as user
-code can use the "prototype" header files.
-
-
-
-
-
-
-Overview of Changes
-
-
-
-
-Replacements for existing vxWorks and epics components
-The following Operating System Independent libraries replace vxWorks specific
-libraries.
-
--
-osiClock
-
-
replaces tickGet, sysClockRateGet, TSgetTimeStamp and TSgetCurrentTime.
-
A generic and a vxWorks version is available
--
-osiFindGlobalSymbol
-
-
A generic version is provided that always returns failure.
-
a vxWorks version is provided that calls symFindByName.
--
-osiInterrupt
-
-
Replaces intLib
-
A generic version is provided that uses a global semaphore. It can
-be used for winXX, Unix, Linux, etc.
-
A vxWorks specific version is provided.
--
-osiRing
-
-
Replaces rngLib.
-
A generic version usable on all platforms is provided.
-
A vxWorks specific version is provided.
--
-osiSem
-
-
replaces semLib.h
-
A vxWorks version is provided.
-
No version has been written for other platforms.
--
-osiThread
-
-
replaces taskLib.h
-
A vxWorks version is provided.
-
No version exists for other platforms.
--
-osiWatchdog
-
-
replaces wdLib.h
-
A vxWorks version is provided.
-
No version exists for other platforms.
-In addition the following new base components are provided
-
--
-cantProceed
-
-
This is called by code that doesnt know what to do when an error occurs.
--
-registry
-
-
replaces symFindByName
-
-
-Registry
-It is not possible to expect every environment to supply an environment
-that makes it easy to implement vxWorks symFindByName. Instead a
-facility to register and find pointers to functions and structures is provided.
-This leaves the problem of registering everything currently located via
-calls to symFindByName. The following solves the problem:
-
--
-Each "ioc" loads a single dbd file defining the complete set of record
-types, device support, and drivers for that "ioc". A utility is provided
-that reads this dbd file and generates a C routine registerRecordDeviceDriver,
-which registers the record, device, and driver support.
-
--
-dbLoadDatabase calls registerRecordDeviceDriver after loading the database.
-
--
-A version of registerRecordDeviceSupport is also provided which calls osiFindGlobalSymbol.
-If the underlying operating system properly implements osiFindGlobalSymbol,
-this version can be used without running the utility that generates the
-special version. For example this version works on vxWorks
-
--
-Nothing has been done to support things like subroutine records. It should
-not be hard.
-
-
-
-Build Environment (Everyone's favorite subject :-)
-The build environment is different. The principal features are:
-
--
-In source directories Makefile.Ioc replaces Makefile.Vx
-
--
-The new configuration files are located in base/configure
-
--
-At least for awhile base/config is still present so that old applications
-still build without major changes
-
-
-
-task_params.h
-This will go away.
-
--
-Thread names are determined by code that calls threadCreate
-
--
-Priorites and stack sizes are handled as described below in the description
-of thread
-
--
-Task creation options are determined by each platform specific version
-of thread.
-
-
-
-module_types.h
-This goes away.
-
-vxWorks shell
-If the target is not for vxWorks, the vxWorks target shell is not available.
-IocInit, dbLoadRecords, etc must be called directly by main
-or the equivalent. The "nice" vxWorks debugging environment is not available
-although a nicer one using xgdb may be available.
-How do we run dbpr, dbgf, etc?
-
-Interrupt Level
-The vxWorks intLock/intUnlock routines are an essential part of base. For
-example any code, including interrupt routines, can call callbackRequest.
-osiInterrupt is provided to solve this problem. For operating systems like
-vxWorks, in which everything runs in a shared memory, multithreaded kernel
-environment, an osi specific version must be provided. For other operating
-systems, e.g. winxx, Unix, Linux, a generic version is provided. The only
-restriction is that kernel code MUST not call any of the osi routines.
-
-
-
-
-
-
-Status Of Port
-
-
-
-
-
-The following has been done:
-
--
-All code except the Sequencer and vxWorks dependent device and driver support
-has been converted to use the new libraries
-
--
-The registry has been implemented
-
--
-The example generated by makeBaseApp has been successfully tested on vxWorks.
-
--
-A separate subdirectory base/src/vxWorks has been created and all vxWorks
-specific code moved to this subdirectory. This makes it possible for existing
-vxWorks ioc applications to use the new system with only minor changes
-to the applications. The sequencer has also been moved to src/vxWorks.
-The version supplied will only run on vxWorks
-
-
-
-
-
-
-
-Work Remaining
-
-
-
-
-
-
--
-Implement osiSem and osiThread for other platforms. If the implementation
-is done via posix (including posix real time and posix threads) then many
-platforms should be supported.
-
--
-Convert the sequencer to use osi calls. William Lupton has already implemented
-an alpha version. Also the sequencer will be unbundled from base.
-
--
-Finish the config and Makefile Changes.
-
--
-Modify makeBaseApp so that it uses the new method of building
-
--
-TEST TEST TEST
-
-
-
-
-Prototype Definitions
-
-
-
-This section contains prototype definitions of what needs to be implemented
-for each port of iocCore.
-A particular implementation may implement each function as desired BUT
-the final result must appear to user code like the definitions in this
-section. For example functions can be implemented via macros defined
-in a header file that replace the generic header file.
-
-
-osiClock
-
-unsigned long clockGetCurrentTick();
-int clockGetRate();
-int clockGetEventTime(int event_number,TS_STAMP *ts);
-int clockGetCurrentTime(TS_STAMP* ts);
-A vxWorks specific version is provided that provides exactly the same semantics
-as 3.13.
-A generic version is provided that should work on most platforms.
-
Perhaps it should be implemented via libCom/osiTime.
-
-osiFindGlobalSymbol
-
-void * osiFindGlobalSymbol(const char *name);
-A vxWorks version is provided that calls symFindByName. It is called by
-the registry if a name is not found in the registry itself.
-A generic version is provided that always returns failure. If the generic
-version is used then all external symbols must be registered, See the registry
-for details.
-
-osiInterrupt
-
-int interruptLock();
-void interruptUnlock(int key);
-int interruptIsInterruptContext();
-void interruptContextMessage(const char *message);
-To lock the following must be done:
-
-int key;
-...
-key = interruptLock();
-...
-interruptUnlock(key);
-
-A vxWorks specific version is provided. It maps directly to intLib calls.
-A generic version is provided that uses a global semaphore to lock.
-This version is intended for operating systems in which iocCore will run
-as a multithreaded process. The global semaphore is thus only global within
-the process.
-
-osiRing
-
-ringId ringCreate(int nbytes);
-void ringDelete(ringId id);
-int ringGet(ringId id, char *value,int nbytes);
-int ringPut(ringId id, char *value,int nbytes);
-void ringFlush(ringId id);
-int ringFreeBytes(ringId id);
-int ringUsedBytes(ringId id);
-int ringSize(ringId id);
-int ringIsEmpty(ringId id);
-int ringIsFull(ringId id);
-A vxWorks specific version is provided that maps directly to rngLib calls.
-A generic version is provided that works on all platforms. This
-version is currently 1.5 times slower than the vxWorks specific version.
-Perhaps some clever thought can make it as fast as rngLib.
-
osiRing has the following properties.
-
--
-For a single writer it is not necessary to lock puts
-
--
-For a single reader it is not necessary to lock gets
-
-
ringFlush should only be used if both gets and puts are locked.
-
-
-osiSem.h
-
-typedef void *semId;
-typedef enum {semTakeOK,semTakeTimeout,semTakeError} semTakeStatus;
-typedef enum {semEmpty,semFull} semInitialState;
-
-semId semBinaryCreate(int initialState);
-void semBinaryDestroy(semId id);
-void semBinaryGive(semId id);
-semTakeStatus semBinaryTake(semId id);
-void semBinaryTakeAssert(semId id);
-semTakeStatus semBinaryTakeTimeout(semId id, double timeOut);
-semTakeStatus semBinaryTakeNoWait(semId id);
-void semBinaryFlush(semId id);
-void semBinaryShow(semId id);
-
-semId semMutexCreate(void);
-void semMutexDestroy(semId id);
-void semMutexGive(semId id);
-semTakeStatus semMutexTake(semId id);
-void semMutexTakeAssert(semId id);
-semTakeStatus semMutexTakeTimeout(semId id, double timeOut);
-semTakeStatus semMutexTakeNoWait(semId id);
-void semMutexShow(semId id);
-
-
-
-
Mutual exclusion semaphores
-
--
-MUST implement recursive locking
-
--
-SHOULD implement priority inheritance and be deletion safe
-
-For POSIX
-
--
-Binary can be implemented easily as a condition variable
-
--
-Mutex can be implemented via various POSIX facilities. Takes careful thought.
-A pthread mutex is not sufficient.
-
-For vxWorks
-
--
-the entire implementation of Binary and Mutex is via macros in a
-vxWorks specific header file.
-
-
-
On a single threaded environment
-
--
-Mutex and context are implemented as though the caller always has access
-to the resource.
-
--
-Binary issues an error message and terminates if an attempt is made to
-create an instance.
-
-
-
-osiThread
-
-#define threadPriorityMax 99
-#define threadPriorityMin 0
-
-/*some generic values */
-#define threadPriorityLow 10
-#define threadPriorityMedium 50
-#define threadPriorityHigh 90
-
-
-/*some iocCore specific values */
-#define threadPriorityChannelAccessClient 10
-#define threadPriorityChannelAccessServer 20
-#define threadPriorityScanLow 60
-#define threadPriorityScanHigh 70
-
-/*
- *The following functions convert to/from osi (operating system independent)
- * and oss (operating system specific) priority values
- * NOTE THAT ALL OTHER CALLS USE osi priority values
-*/
-
-int threadGetOsiPriorityValue(int ossPriority);
-int threadGetOssPriorityValue(int osiPriority);
-
-/* stack sizes for each stackSizeClass are implementation and CPU dependent */
-typedef enum {
- threadStackSmall, threadStackMedium, threadStackBig
-} threadStackSizeClass;
-
-unsigned int threadGetStackSize(threadStackSizeClass size);
-
-typedef void *threadId;
-threadId threadCreate(const char *name,
- unsigned int priority, unsigned int stackSize,
- THREADFUNC funptr,void *parm);
-void threadDestroy(threadId id);
-void threadSuspend(threadId id);
-void threadResume(threadId id);
-int threadGetPriority(threadId id);
-void threadSetPriority(threadId id,int priority);
-void threadSetDestroySafe(threadId id);
-void threadSetDestroyUnsafe(threadId id);
-const char *threadGetName(threadId id);
-int threadIsEqual(threadId id1, threadId id2);
-int threadIsReady(threadId id);
-int threadIsSuspended(threadId id);
-void threadSleep(double seconds);
-threadId threadGetIdSelf(void);
-void threadLockContextSwitch(void);
-void threadUnlockContextSwitch(void);
-threadId threadNameToId(const char *name);
-Thread priorities are assigned a value from 0 to 99. A higher value means
-higher priority
-Thread stack values are handled as follows:
-
--
-threadGetStackSize cal be called to get one of three default sizes. Thus
-should be done whenever possible.
-
--
-Code can just set any size it desires. Such code is not portable.
-
-For vxWorks osiThread is implement via calls to taskLib
-For posix it should be possible (I hope) to implement thread via
-a combination of:
-
--
-osiSem
-
--
-pthread and POSIX.4
-
--
-Special code
-
-Thread is not implemented for a single threaded environment.
-
-osiWatchdog
-
-typedef void *watchdogId;
-typedef void (*WATCHDOGFUNC)(void *parm);
-
-watchdogId watchdogCreate ();
-void watchdogDestroy (watchdogId id);
-void watchdogStart(watchdogId id, int delaySeconds,WATCHDOGFUNC funptr,*parm);
-void watchdogCancel(watchdogId id);
-A vxWorks version is provided that maps directly into wdLib calls. A generic
-version is provided.
-
-c++ osi classes created by Jeff Hill
-
-
--
-osiTime
-
--
-osiPoolStatus
-
--
-osiSleep
-
--
-osiTimer
-
-
-
-
-
-
-New base supplied component
-
-
-
-
-
-cantProceed
-
-void cantProceed(const char *errorMessage);
-void *callocMustSucceed(size_t count, size_t size, const char *errorMessage);
-void *mallocMustSucceed(size_t size, const char *errorMessage);
-cantProceed prints error message and then
-
--
-For a threaded environment suspends. This is for debugging purposes.
-
--
-For a non-threaded environment exits.
-
-callocMustSucceed is like calloc except that it does not return if storage
-is not available. A lot of iocCore is initialized by iocInit. If memory
-allocation fails during iocInit it is not possible to recover. mallocMustSucceed
-is like malloc except that it does not return if storage is not available.
-
-
-
-
-
-REGISTRY
-
-
-
-
-iocCore currently uses symFindByName to dynamically bind the following:
-
--
-record/device/driver support.
-
-
The registration facility provides a safe and easy to use alternative
-to symFindByName
--
-subroutine record subroutines.
-
-
An easy to use solution must be developed.
--
-initHooks
-
-
A new implementation of initHooks is now provided. It provides a routine
-initHookRegister. This MUST be called by any routine that wants to be called
-during initialization.
--
-devLib
-
-
This has been moved to base/src/vxWorks. Thus for now it is only supported
-on vxWorks
--
-drvTS.c
-
-
This has been moved to base/src/vxWorks. Thus for now it is only supported
-on vxWorks
--
-errSymLib.c
-
-
This was rewritten to be independent of vxWorks.
--
-epicsDynLink - obsolete. Gone.
-
--
-dev/symbDev
-
-
Moved to base/src/vxWorks. Thus for now it is only supported on vxWorks.
--
-any hardware related component
-
-
Either moved to base/src/vxWorks. or unbundled from base.
-This only the first two items need a solution. The existing implementation
-solves the first problem. No solution has been provided for the subroutine
-records but it will not be hard implement.
-
-Overview
-The basic idea is to provide a registration facility. Any storage meant
-to be "globally" accessable must be registered before it can be accessed
-by other code.
-A perl script is provided that reads the xxxApp.dbd file and produces
-a c file containing a routine registerRecordDeviceDriver, which registers
-all record/device/driver support defined in the xxxApp.dbd file.
-
-registry
-
-
-int registryAdd(void *registryID,const char *name,void *data);
-void *registryFind(void *registryID,const char *name);
-
-int registrySetTableSize(int size);
-void registryFree();
-int registryDump(void);
-
-This is the code which does the work. Each different set of things to register
-must have it's own unique ID. Everything to be registered is stored in
-the same gpHash table.
-Routine registrySetTableSize is provided in case the default hash table
-size (1024 entries) is not sufficient.
-
-registryRecordType.h
-
-
-typedef int (*computeSizeOffset)(dbRecordType *pdbRecordType);
-
-typedef struct recordTypeLocation {
- struct rset *prset;
- computeSizeOffset sizeOffset;
-}recordTypeLocation;
-
-
-int registryRecordTypeAdd(const char *name,recordTypeLocation *prtl);
-recordTypeLocation *registryRecordTypeFind(const char *name);
-
-Some features:
-
--
-Access to both the record support entry table and to the routine which
-computes the size and offset of each field are provided
-
--
-Type safe access is provided.
-
-
-
-registryDeviceSupport
-
-
-int registryDeviceSupportAdd(const char *name,struct dset *pdset)
-struct dset *registryDeviceSupportFind(const char *name);
-
-This provides access to the device support entry table.
-
-registryDriverSupport
-
-
-int registryDriverSupportAdd(const char *name,struct drvet *pdrvet);
-struct drvet *registryDriverSupportFind(const char *name);
-
-/* The following function is generated by registerRecordDeviceDriver/pl */
-int registerRecordDeviceDriver(DBBASE *pdbbase);
-
-This provides access to the driver support entry table.
-
-
-registerRecordDeviceDriver.pl
-This is the perl script which creates a c source file that registers record/device/driver
-support. Make rules are provided that
-
--
-execute this script using the dbd file created by dbExpand
-
--
-compile the resulting C file
-
--
-Make the object file part of the xxxLib file
-
-
-
-registerRecordDeviceDriver.c
-A version of this is provided for vxWorks. This version makes it unnecessary
-to use registerRecordDeviceDriver.pl or register other external names.
-Thus for vxWorks everything can work almost exactly like it did in release
-3.13.x
-
-
diff --git a/iocPortStatus.html b/iocPortStatus.html
deleted file mode 100644
index 44e97d378..000000000
--- a/iocPortStatus.html
+++ /dev/null
@@ -1,250 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-Status of iocCore port
-
-Marty Kraimer
-
Nov 18, 1999
-
-This is a brief status of the iocCore port, what has changed since my
-last commit, and what needs to be done before we can run iocCore on something
-besides vxWorks.
-
In the following I state things as though the only remaining thing to
-do is implement osiSem and osiThread for other posixIOC. I realize that
-a bunck of problems will arise elsewhere when this is done but I really
-hope any such problems are minor.
-
Note also that I have only built for solaris and vxWorks.
-
-makeBaseApp
-The example does not work with the configure rules. We are working on this
-problem.
-
-Make
-Trying to build everything via a single Makefile in each source directory
-became too complicated. Janet has redone the build using Makefile.Host
-and Makefile.Ioc
-We still are not handling single vs multithreaded properly. For now
-Makefile.Host is single threaded, Makefile.Ioc is multithreaded.
-
-libCom
-The osiXXX routines are very similar to what was previously checked in.
-Janet has implemented a generic version of osiWatchDog using Jeff's osiTimer.
-Thus for ports to other operating systems only osiSem and osiThread need
-to be implemented.
-osiSem has changed since the last commit:
-
--
- semXXXShow functions were added. It is permissible to make these
-empty functions.
-
--
-semMutexFlush no longer exists.
-
-Some comments for Jeff
-
--
-What is osiPoolStatus.c ? Look at libCom/os/vxworks/osiPoolStatus.c Is
-this a problem?
-
--
-Are any of you msi routine going to cause a problem for other operating
-systems? My guess is that a posix or generic version of most will suffice.
-This includes osiTime, osiTimer, osiSleep, osiSock, osiFilename.
-
--
-I will let you decide how to move osiXXX things from libCom/misc/...
-to libCom/osi/...
-
--
-osiMutex - This caused a problem building the new way. For now we are not
-referencing it in the Makefiles. It looks like only cas uses it. Can we
-just move it to cas?
-
-ToDo before demonstration of iocCore of something besides vxWorks
-
--
-osiSem.c and osiThread.c must be created in libCom/osi/os/posixIOC
-
--
-Make up application that properly runs. The key is a main program that
-does what is currenlt done in the st.cmd file.
-
-
-
-Channel Access
-In ca/os/posixIOC I have created files caOsDependent.h and caOsDependent..c.
-I hope that these are close to what is needed for a pthreads version of
-iocCore. Until osiSem and osiThread are implemented for posix this code
-can not be tested. See details about channel access below.
-
-
-CASR
-I deleted caswatchdog.c. It was not being built.
-
-src/vxWorks
-All vxWorks specific code is moved to src/vxWorks. If you are building
-for something besides vxWorks just comment out the build for vxWorks in
-src/Makefile.
-
-Sequencer
-The old sequencer has been moved to src/vxWorks. This is for compatibilty
-for existing vxWorks applications.
-This version of the sequencer will not work on IOCS using the posixIOC
-version of Channel Access. The reason is the calls to ca_import and ca_import_cancel.
-The technique used for vxWorks will not work (at least easily) for pthreads
-since pthreads does not have task variables like vxWorks. Thus this needs
-to be rethought. Perhaps William already has solved this problem in his
-unbundled version of the sequencer.
-
-RTMS
-The following needs to be done.
-
--
-Definitions added to configure
-
--
-If pthreads are supported then that is all; otherwise
-
-
--
-osiSem and osiThread must be created in libCom/osi/os/rtms
-
--
-caOsDependent.h and caOsDependent.c must be created in ca/os/rtms. Start
-with the version in ca/os/posixIOC
-
-
-
-
-Details of CA conversion
-This is mainly of interest to Jeff. I will guess that Jeff will want to
-restructure the code so these are notes about what I did.
-As we knew up frount, the biggest problem was the use of the vxWorks
-task variable facility. This is used because ca clients do not have to
-call ca_initialize or pass a ca private pointer to each ca_xxx library
-routine. I looked at what pthreads supports and came up with the
-following solution.
-
iocinf.h previously had the statements
-
-GLBLTYPE struct CA_STATIC *ca_static;
-
-This is replaced by the statements
-
-#include "caOsDependent.h"
-CA_OSD_CA_STATIC
-
-caOsDependent.h is created in each ca/os/xxx directory. For posix (single
-threaded), vms, vxWorks, and win32 it just nhas the definitions:
-
-#define CA_OSD_CA_STATIC \
- GLBLTYPE CA_STATIC *ca_static;
-#define CA_OSD_GET_CA_STATIC
-
-For posixIOC it is defined as:
-
-#include <pthread.h>
-#define CA_OSD_CA_STATIC \
- extern pthread_key_t *pca_key;
-#define CA_OSD_GET_CA_STATIC \
- CA_STATIC *ca_static=(CA_STATIC *)pthread_getspecific(*pca_key);
-
-Then each ca public routine (those defined in cadef.h) that use ca_static
-have
-
-CA_OSD_GET_CA_STATIC
-
-as the last definition at the start of the routine. A look at ca_task_initialize
-in ca/os/posixIOC/caOsDependent.c shows how things are initialized for
-a pthread implementation.
-Most of the private ca routines (defined internally or in iocinf.h)
-that use ca_static were modified to have ca_static as their first argument.
-
Now for some details
-
V5_vxWorks_patch.c seemed to be obsolete code. It is gone.
-
The following are no longer part of the os specific code.
-
--
-cac_gettimeval is now implemented in access.c
-
--
-cac_block_for_io_completion is now implemented in iocinf.c
-
--
-cac_block_for_sg_completion is no longer needed.
-
--
-os_specific_sg_io_complete is no longer needed
-
--
-os_specific_sg_create is no longer needed
-
--
-os_specific_sg_delete is no longer needed
-
--
-cac_add_task_variable is no longer supported. See vxWorks below.
-
-The following should not be part of the os specific code. Can we put them
-somewhere else? Maybe ca/bsd_depen.c ?
-
--
-max_unix_fd
-
--
-caSetDefaultPrintfHandler
-
-
-
vxWorks_depen.c is now ca/os/vxWorks/caOsDependent.c. In addition
-
--
-It's functionality should be the same as previously. I moved the LOCAL
-routines to the end so that it was easier to understand what was needed.
-
--
-Some suggestions
-
-
--
-Can ca_channel_status be made a non os specific routine? The argument will
-not easily work for posixIOC. For now I am ignoring this problem.
-It is just not implemented for posixIOC
-
--
-Can the ca/os/posixIOC routines localUseName and caSetDefaultPrintfHandler
-be put in bsd_depen.c
-
-
--
-Problems
-
-
--
-ca_import and ca_import_cancel are a problem. These can NOT be easily implemented
-for posixIOC. Since it appears that they exist mainly for the sequencer,
-perhaps this is not a problem. I dont know what William is doing in the
-new sequencer. Note that definitions for ca_import and ca_import_cancel
-were removed from cadef.h
-
-
-posix_depen.c is now ca/os/posix/caOsDependent.c.
-
-ca/os/posixIOC/caOsDependent is the os specific code for a pthread implementation.
-Since osiSem and osiThread are not implemented for pthread I can not test
-this code. I hope it is close to ,what is needed.
-
vms_depen.c is now /home/phoebus/MRK/epics/base/src/ca/os/vms/caOsDependent.c.
-I removed the routines no longer needed. I can not test this.
-
windows_depen.c is now ca/os/win32/caOsDependent.c. I removed the routines
-no longer needed. I can not test this. Note that you will also want an
-IOC version.
-
-
-
-
-
-