SIS1100 Driver documentation > Application access > SIS1100 driver functions

SIS1100 driver functions

To fully understand why certain functions are neccesary it is recommended that you have read the part "Driver Structure".

Content:

Overview
Opening/Closing the connection to the user client
Getting information about the hardware
VME single read/write
VME block read/write
VME list read/write
VME direct bus access

 

Overview

So far you can access 16 different functions in the driver. They are grouped by VME access method. Because you can not access these functions directly you need to tell the user client which function you want to call. This is done by providing the right constant determining the desired function. In Mac OS X you have four different ways how to access a function in the driver:

  1. one or more scalar input parameters, one or more scalar output parameters (IOConnectMethodScalarIScalarO)
  2. one or more scalar input parameters, one structure output parameter (IOConnectMethodScalarIStructureO)
  3. one or more scalar input parameters, one structure input parameter (IOConnectMethodScalarIStructureI)
  4. one structure input parameter, one structure output parameter (IOConnectMethodStructurIStructureO)

The following table will give you an overview over the function constants and the access method.

Connections with user client
SISUserClientOpen IOConnectMethodScalarIScalarO
SISUserClientClose IOConnectMethodScalarIScalarO
Getting information
PLXReadLocalSpace0 IOConnectMethodScalarIScalarO
PLXWriteLocalSpace0 IOConnectMethodScalarIScalarO
PLXReadLocalSpace1 IOConnectMethodScalarIScalarO
PLXWriteLocalSpace1 IOConnectMethodScalarIScalarO
VME single read/write
VMERead IOConnectMethodStructureIStructureO
VMEWrite IOConnectMethodStructureIStructureO
VME block read/write
VMEBlockRead IOConnectMethodStructureIStructureO
VMEBlockWrite IOConnectMethodStructureIStructureO

VME list read/write

VMEReadList IOConnectMethodStructureIStructureO
VMEWriteList IOConnectMethodStructureIStructureO
VME direct bus access
VMEMapVMEMemory IOConnectMethodStructureIStructureO
VMEMappedRead IOConnectMethodStructureIStructureO
VMEMappedWrite IOConnectMethodStructureIStructureO
VMEFlushAllMaps IOConnectMethodScalarIScalarO

All these methods will return a kern_return_t. So a possible function call in your application should look like this:

Code sample 1: Possible function call to the driver

kern_return_t kernResult;
kernResult = IOConnectMethodScalarIScalarO(dataPort, SISUserClientOpen, 0, 0);

if (kernResult == KERN_SUCCESS)
{
  printf("User client open was successfull.\n");
}

To successfully call a function in the driver you need to include the "SISUserClientStuff.h" header file. It defines enumeration constants needed to select the right function in the user client. The header file "MacOSX_sis1100_var.h" is necessary to provide you with the right data structures you need. These files are part of the driver distribution. Additionally you need to link against the IOKit Framework provided by Apple.

Opening/Closing the connection to the user client

Opening a connection to the user client needs a bit of preparation. First you need to obtain a Mach port to communicate with the IOKit. The second step is to find the driver by matching its main class against the ones registered in the IORegistry. If we have got a match, then we can instantiate an instance of the user client (normally only one instance is allowed) and obtain a Mach port to communicate with the driver. Finally we can call the open method in the user client to do the basic initialisation.

The following code sample will show how you can complete this task.

Code sample 2: Opening the connection to the user client

io_connect_t open()
{
  kern_return_t kernResult;
  mach_port_t masterPort;
  io_service_t serviceObject;
  io_connect_t dataPort;
  io_iterator_t iterator;
  CFDictionaryRef classToMatch;

  // Returns the mach port used to initiate communication with IOKit.
  kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);

  if (kernResult != KERN_SUCCESS)
  {
    printf("IOMasterPort returned %d\n", kernResult);
    return 0;
  }

  classToMatch = IOServiceMatching(kMyDriversIOKitClassName);
  if (classToMatch == NULL)
  {
    printf("IOServiceMatching returned a NULL dictionary.\n");
    return 0;
  }

  // This creates an io_iterator_t of all instances of our drivers class that exist in the IORegistry.
  kernResult = IOServiceGetMatchingServices(masterPort, classToMatch, &iterator);
  if (kernResult != KERN_SUCCESS)
  {
    printf("IOServiceGetMatchingServices returned %d\n\n", kernResult);
    return 0;
  }
  // Get the first item in the iterator.
  serviceObject = IOIteratorNext(iterator);

  // Release the io_iterator_t.
  IOObjectRelease(iterator);

  if (!serviceObject)
  {
    printf("Couldn't find any matches.\n");
    return 0;
  }

  // This call will cause the user client to be instantiated.
  kernResult = IOServiceOpen(serviceObject, mach_task_self(), 0, &dataPort);
  // Release the io_service_t.
  IOObjectRelease(serviceObject);

  if (kernResult != KERN_SUCCESS)
  {
    printf("IOServiceOpen returned %d\n", kernResult);
    return 0;
  }

  // This calls the 'open' method in our user client inside the Kernel.
  kernResult = IOConnectMethodScalarIScalarO(dataPort, SISUserClientOpen, 0, 0);

  if (kernResult == KERN_SUCCESS)
  {
    printf("User client open was successfull.\n");
  }
  return dataPort;
}

Once you have completed opening the connection to the user client you can use the obtained dataPort to communicate with the driver. How to do this is shown in the next section.

When you are done communicating with the driver you must close the dataPort so that other applications may access the driver. The next code sample will show you how to do this.

Code sample 3: Closing the connection to the user client

int close(io_connect_t dataPort)
{
  kern_return_t kernResult;

  // first close the user client
  kernResult = IOConnectMethodScalarIScalarO(dataPort, SISUserClientClose, 0, 0);
  if (kernResult == KERN_SUCCESS)
  {
    kernResult = IOServiceClose(dataPort);
    if (kernResult != KERN_SUCCESS)
    {
      return 1;
    }
    return 0;
  }
  return 1;
}

Getting information about the hardware

The following four methods allow you to get some information about the installed hardware or to implement some custom protocoll you need.

Getting information

The PLXReadLocalSpace0 method allows you to read information about the SIS1100 PCI card which is stored in the first memory area of the PCI card.

Code sample 4: Getting PCI card information

int firmware_version()
{
kern_return_t result;
int value = -1;
result = IOConnectMethodScalarIScalarO(dataPort, PLXReadLocalSpace0, 1, 1, 4, 0x0, &value);
return value;
}

This code sample shows you how to retrieve information about the firmware version of the PCI card. The parameters are described below:

dataPort this is the mach communication port obtained by open()
PLXReadLocalSpace0 this is the method name you want to call in the driver
2 number of input parameters
1 number of output parameters
4 size of the requested data word (1, 2 and 4 allowed, input)
0x0 the offset you want to read from (input)
&value the address where you want to store the requested value (output)

The PLXReadLocalSpace1 method works the same way.

Writing data manually

For writing data manually into the cards memory you can either use PLXWriteLocalSpace0 or PLXWriteLocalSpace1. They differ only in the memory space into which they are writing.

Code sampel 5: Writing data manually

void write()
{
kern_return_t result;
int value = 0xFF;
int offset = 0x80;
result = IOConnectMethodScalarIScalarO(dataPort, PLXWriteLocalSpace0, 2, 0, value, offset);
if(result != KERN_SUCCESS)
{
printf("Result is not kern_success: %i", result);
}
}

The parameters are mainly the same as in PLXReadLocalSpace0, except that you don't have an output parameter.

VME single read/write

If you want to transfer single bytes, words or double words, then you can use the VME single read/write functions implemented by the driver.

Because some date needs to be transfered in and out of the driver you need to setup a sis1100_vme_req data structure. This structure is defined in the header file "MacOSX_sis1100_var.h" and is shown in code sample 4.

Code sample 6: The sis1100_vme_req data structure

struct sis1100_vme_req
{
  int size;
  UInt32 am;
  UInt32 addr;
  UInt32 data;
  UInt32 error;
};

size stores the number of bytes you want to be transfered. Allowed values for single read/write are: 1 (byte), 2 (word) and 4 (double word).
am stores the address modifier if you want to use one
addr stores the address you want to read from or write to
data stores or returns the data you want to write or read
error returns the error code if one occurred

The following two code samples show you how to read and write data with the driver.

Code sample 7: VME single read

struct sis1100_vme_req req, answer;
kern_return_t result;
IOByteCount structSize = sizeof(struct sis1100_vme_req);
IOByteCount outStructSize = sizeof(struct sis1100_vme_req);

// set the required data size, here 16bit (word)
req.size=2;
// use address modifier for 24bit address
req.am=0x39;
// set the address we want to read from
req.addr= 0x7777FC;

// call the user client with VMERead, the return values are stored in answer
result = IOConnectMethodStructureIStructureO(dataPort, VMERead, structSize, &outStructSize, &req, &answer);

dataPort this is the data port obtained from the open()-method
VMERead this is the function index of the function in the kernel
structSize the size of the input structure
&outStructSize address where the driver can store the size of the output structure
&req address of the input structure
&answer address of the ouput structure

Code sample 8: VME single write

struct sis1100_vme_req req, answer;
kern_return_t result;
IOByteCount structSize = sizeof(struct sis1100_vme_req);
IOByteCount outStructSize = sizeof(struct sis1100_vme_req);

// set the data size we want to write
req.size = 2;
// we want to use an address modifier for 24bit address
req.am = 0x39;
// the address where we want to write our data
req.addr = 0x777704;
// finally our data word
req.data = 0x1234;

result = IOConnectMethodStructureIStructureO(dataPort, VMEWrite, structSize, &outStructSize, &req, &answer);

dataPort this is the data port obtained from the open()-method
VMERead this is the function index of the function in the kernel
structSize the size of the input structure
&outStructSize address where the driver can store the size of the output structure
&req address of the input structure
&answer address of the ouput structure

VME block read/write

The VME block transfer is intended to use with larger amounts of data (e.g. if you want to upload a program into a VME module). The principles are mainly the same as with the single read/write, except that the data structure is a bit different. Note that with the block transfer you can only access sequentiell addresses!

Code sample 9: data structure for block transfer

struct sis1100_vme_block_req {
  int size;
  int fifo;
  unsigned int num;
  UInt32 am;
  UInt32 addr;
  UInt32 *data;
  UInt32 error;
};

In addition to the sis1100_vme_req there are two more data fields.

fifo this data field is not used at the moment
num this integer is used to tell the driver how much blocks you want to transfer

Note that the data field is now a pointer pointing to the address where the data blocks are stored. Please be aware that the application has to take care that this pointer is pointing to a valid memory area. Otherwise the application will crash or even worse your whole system. For allocating memory you should use calloc instead of malloc, so that the memory area is contiguous. The following code samples show you how to initiate a block transfer.

Code sample 10: block read

struct sis1100_vme_block_req req, answer;
kern_return_t result;
IOByteCount structSize = sizeof(struct sis1100_vme_block_req);
IOByteCount outStructSize = sizeof(struct sis1100_vme_block_req);
UInt32 *address

address = calloc(8, sizeof(UInt32));
// put the logic to fill the data field here
// ...


req.size = 4;
req.am = 0x39;
req.addr = 0xC08000;
req.num = 8;
req.data = address;

result = IOConnectMethodStructureIStructureO(dataPort, VMEReadBlock, structSize, &outStructSize, &req, &answer);

The driver will copy the data address is pointing to and then transfer it to the VME module.

Code sample 11: block write

struct sis1100_vme_block_req req, answer;
kern_return_t result;
IOByteCount structSize = sizeof(struct sis1100_vme_block_req);
IOByteCount outStructSize = sizeof(struct sis1100_vme_block_req);
UInt32 *address

address = calloc(8, sizeof(UInt32));

req.size = 4;
req.am = 0x39;
req.addr = 0xC08000;
req.num = 8;
req.data = address;

result = IOConnectMethodStructureIStructureO(dataPort, VMEWriteBlock, structSize, &outStructSize, &req, &answer);

When the driver has read all the data blocks starting at addr it will copy the result to the location where address is pointing to.

VME list read/write

VME list read/write comes in handy when you want to transfer large amounts of data and your data is spread over several VME modules or your addresses are not sequentiel. With a VME list access you create a list of single requests which are then performed in order by the driver.

For a VME list access you will need an additional data structure besides the sis1100_vme_req structure:

Code sample 12: list access data structure

struct sis1100_vme_list_req
{
  unsigned int num;
  struct sis1100_vme_req *data
};

num the total number of requests.
*data pointer to an array of num sis1100_vme_req

The following code sample demonstrates a VME list write access. For read access it is analogous except that you do not need to fill the data fields of the requests.

Code sample 13: VME write with list access

struct sis1100_vme_list_req lreq;
struct sis1100_vme_req *req;
IOByteCount structSize = sizeof(struct sis1100_vme_list_req);
IOByteCount outStructSize = sizeof(struct sis1100_vme_list_req);
int count;

// number of requests
count = 10000;
// allocate enough memory to hold all requests
req = malloc(count*sizeof(struct sis1100_vme_req));
if(!req)
{
  printf("malloc failed\n");
  return -1;
}
// prepare the list request to hold the correct information
lreq.data = req;
lreq.num = count;

// fill all requests
for(i = 0; i < count; i++)
{
  req[i].size = 0x2;
  req[i].am = 0x39;
  req[i].addr = 0xC00000 + 0x38;
  req[i].data = i;

}

kernReturn = IOConnectMethodStructureIStructureO(dataPort, VMEWriteList, structSize, &outStructSize, &lreq, &lreq);

VME direct bus access

The VME direct bus access is intended to be used for frequent VME access to a small chunk of VME memory. You have to request a memory mapping of VME memory and then the driver can directly write to this memory. The restrictions about this are that the memory area can not be larger than 64kB and you can create only 64 mappings at a time. Explicit flushing of a mapping is currently not implemented - you can only flush all mappings.

To use direct bus access you have to request a mapping of VME memory. To do this use the following data structure:

Code sample 14: data structure for map requests

struct sis1100_map_req
{
  UInt32 am;
  UInt32 baseAddr;
  UInt32 mapNum;
};

am address modifier for accesses to this mapping
baseAddr the base address of the VME memory you want to be mapped
mapNum this field is filled by the driver and returns the number of the mapping. This number is later used to address the correct mapping in a request

Code sample 15: request a mapping

struct sis1100_map_req mapReq;
IOByteCount mapStructSize = sizeof(struct sis1100_map_req);
IOByteCount mapOutStructSize = sizeof(struct sis1100_map_req);

// set base address of the mapping
mapReq.baseAddr = 0xC00000;
// use address modifier for access to this mapping
mapReq.am = 0x39;

kernReturn = IOConnectMethodStructureIStructureO(dataPort, VMEMapVMEMemory, mapStructSize, &mapOutStructSize, &mapReq, &mapReq);

printf("map number %d\n", mapReq.mapNum);

To actually write to or read from a mapping you need fill two more data structures. See code sample 18 how to perform such a access.

Code sample 16: mapped request data structure

struct sis1100_mapped_req
{
  UInt32 mapNum;
  UInt32 numberOfReq;
  struct
sis1100_simple_req *data;
};

mapNum the number of the memory map where the requests pointed to by *data should be written or read.
numberOfReq the total number of requests pointed to by *data.
*data pointer to user space memory where all the sis1100_simple_req are stored.

Code sample 17: simple request data structure

struct sis1100_simple_req
{
  UInt32 offset;
  UInt32 value;
};

offset this is an offset to the base address of the mapped VME memory region.
value here you store the value if you want to write something. Otherwise leave empty and the driver will fill this field on a read request.

To perform the request you put together a sis1100_mapped_req specifying the map number, the number of requests and a pointer pointing to an array of sis1100_simple_req.

Code sample 18: mapped request

struct sis1100_simple_req *mappedReq;
struct sis1100_mapped_req mReq;
IOByteCount size1 = sizeof(struct sis1100_mapped_req);
IOByteCount size2 = sizeof(struct sis1100_mapped_req);
int count;

// allocate enough memory for the requests
count = 10000;
mappedReq = malloc(count*sizeof(struct sis1100_simple_req));
if(!mappedReq)
{
  printf("malloc failed\n");
  return -1;
}

// put the number of requests and a pointer to them into the mapped request
mReq.numberOfReq = count;
mReq.data = mappedReq;
// set the correct map number to access the right mapping
mReq.mapNum = mapNumber;

// fill offsets and values
for(i = 0; i < count; i++)
{
  mappedReq[i].offset = 0x38;
  mappedReq[i].value = i;
}

kernReturn = IOConnectMethodStructureIStructureO(dataPort, VMEMappedWrite, size1, &size2, &mReq, &mReq);