User Tools

Site Tools


usersguide:devguide

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
usersguide:devguide [2022/07/14 23:11]
dpisuperadmin
usersguide:devguide [2022/07/14 23:40] (current)
dpisuperadmin
Line 1: Line 1:
-====== the Peripheral Control Project Developer's Guide ======+====== User's Guide for Software Defined Peripherals ====== 
  
 ===== Introduction ===== ===== Introduction =====
Line 103: Line 104:
 Next is the myperi Linux shared object driver.  Move to the pcdaemon/fpga-drivers directory and copy the gpio4 directory to myperi. Next is the myperi Linux shared object driver.  Move to the pcdaemon/fpga-drivers directory and copy the gpio4 directory to myperi.
     cd pcdaemon     cd pcdaemon
-    cp -r fpga-drivers/fpgpio4 fpga-drivers/myperi+    cp -r fpga-drivers/gpio4 fpga-drivers/myperi
 Change the name of the driver file and change the target name in the Makefile. Change the name of the driver file and change the target name in the Makefile.
     mv fpga-drivers/myperi/gpio4.c fpga-drivers/myperi/myperi.c     mv fpga-drivers/myperi/gpio4.c fpga-drivers/myperi/myperi.c
-    vi myperi/Makefile+    vi fpga-drivers/myperi/Makefile
 While not strictly required, this is a good time to edit myperi.c and change the name of the peripheral.  The line with the peripheral name should now look something like: While not strictly required, this is a good time to edit myperi.c and change the name of the peripheral.  The line with the peripheral name should now look something like:
     pslot->name = "myperi";     pslot->name = "myperi";
Line 311: Line 312:
 To understand how to load a driver into pcdaemon you should, perhaps, have some understanding of how pcdaemon works. To understand how to load a driver into pcdaemon you should, perhaps, have some understanding of how pcdaemon works.
  
-The core of dpdaemon is a list of slots.  Each slot has a SLOT structure (includes/daemon.h) which has the information needed to manage the peripheral in that slot.  SLOT has the number of the slot, the name of the shared object file, and an array of resources (RSC in includes/daemon.h) for the peripheral. //Resources//, you may recall, is the generic term given to the attributes and data endpoints of the peripheral. +The core of pcdaemon is a list of slots.  Each slot has a SLOT structure (include/daemon.h) which has the information needed to manage the peripheral in that slot.  SLOT has the number of the slot, the name of the shared object file, and an array of resources (RSC in includes/daemon.h) for the peripheral. //Resources//, you may recall, is the generic term given to the attributes and data endpoints of the peripheral. 
  
-Peripheral #0 in the FPGA binary is the //enumerator//. This  is just a copy of the perilist configuration file used to build the FPGA binary.  When dpdaemon starts it loads the enumerator driver and reads the list of peripherals in the FPGA. It then loops through the list trying to load the shared object driver for each peripheral.  When the driver is loaded dpdaemon looks up and calls the Initialize() routine in the driver. (Look for dlsym() in daemon/ui.c to see how this works.)  The goal of Initialize() is to give dpdaemon (i.e. the SLOT structure) everything it needs to manage the peripheral.+Peripheral #0 in the FPGA binary is the //enumerator//. This  is just a copy of the perilist configuration file used to build the FPGA binary.  When pcdaemon starts it loads the enumerator driver and reads the list of peripherals in the FPGA. It then loops through the list trying to load the shared object driver for each peripheral.  When the driver is loaded pcdaemon looks up and calls the Initialize() routine in the driver. (Look for dlsym() in daemon/ui.c to see how this works.)  The goal of Initialize() is to give pcdaemon (i.e. the SLOT structure) everything it needs to manage the peripheral.  The enumerator is usually overloaded with a board specific driver.  The board file lets you access buttons, LEDs, or other features unique to the board.
  
-Dpdaemon can have multiple instances of the same peripheral.  This implies that an instance's internal state must be kept separate from the internal state of all other instances.  To do this you should create a structure or object that holds your peripheral internal state.  For example, the gpio4 peripheral keeps the following state information:+Pcdaemon can have multiple instances of the same peripheral.  This implies that an instance's internal state must be kept separate from the internal state of all other instances.  To do this you should create a structure or object that holds your peripheral internal state.  For example, the gpio4 peripheral keeps the following state information:
         // All state info for an instance of an gpio4         // All state info for an instance of an gpio4
     typedef struct     typedef struct
Line 334: Line 335:
     if (pctx == (MYPERIDEV *) 0) {     if (pctx == (MYPERIDEV *) 0) {
         // Malloc failure this early?         // Malloc failure this early?
-        dplog("memory allocation failure in myperi initialization");+        pclog("memory allocation failure in myperi initialization");
         return (-1);         return (-1);
     }     }
Line 374: Line 375:
 The usercmd() routine is where you convert your API calls to read and write resources into packets of register reads and writes.   The usercmd() routine is where you convert your API calls to read and write resources into packets of register reads and writes.  
  
-The interface to dpdaemon is a TCP socket.  The daemon listens on the socket and accepts connections from application programs.  The application program sends lines of text in the form  +The interface to pcdaemon is a TCP socket.  The daemon listens on the socket and accepts connections from application programs.  The application program sends lines of text in the form  
-    [dpset|dpget|dpcat] [peri_name|slot_id] resource_name [resource_values] +    [pcset|pcget|pccat] [peri_name|slot_id] resource_name [resource_values] 
-The daemon parses line of input and rejects lines that do not match the above format.  The daemon checks for a valid peripheral name or slot ID, checks for a valid resource name, and verifies that the command (get/set/cat) is appropriate for the resource.  If everything is valid, the daemon calls your get/set callback.+The daemon parses lines of input and rejects lines that do not match the above format.  The daemon checks for a valid peripheral name or slot ID, checks for a valid resource name, and verifies that the command (get/set/cat) is appropriate for the resource.  If everything is valid, the daemon calls your get/set callback.
  
-The daemon passs a lot of information into your callback, including the command (DPGETDPSET, or DPCAT), the resource index you set in Initialize(), and the string of the new value,  There can be many instances of your peripheral, so the callback includes a SLOT pointer from which you can the the instance's private data structre.  Your response the the application that issued the command should be a newline terminated line of ASCII text.  The text goes into the 'buf' parameter and before returning you set *plen to the number of characters you put in buf.  You should be able to use the following exactly as it for your usercmd() callback.+The daemon passes a lot of information into your callback, including the command (PCGETPCSET, or PCCAT), the resource index you set in Initialize(), and the string of the new value,  There can be many instances of your peripheral, so the callback includes a SLOT pointer from which you can the the instance's private data structre.  Your response the the application that issued the command should be a newline terminated line of ASCII text.  The text goes into the 'buf' parameter and before returning you set *plen to the number of characters you put in buf.  You should be able to use the following exactly as it for your usercmd() callback.
     static void usercmd(     static void usercmd(
-        int      cmd,        //==DPGET if a read, ==DPSET on write+        int      cmd,        //==PCGET if a read, ==PCSET on write
         int      rscid,      // ID of resource being accessed         int      rscid,      // ID of resource being accessed
         char    *val,        // new value for the resource         char    *val,        // new value for the resource
Line 394: Line 395:
 Your code now needs to switch based on the resource and command.  A switch() statement works as does a string of if()/else if() statement.  Use your preferred coding style.  Long or complex calculations based on the user input might be moved to a separate routine to keep usercmd() simple and readable.  Your code might look something like the following: Your code now needs to switch based on the resource and command.  A switch() statement works as does a string of if()/else if() statement.  Use your preferred coding style.  Long or complex calculations based on the user input might be moved to a separate routine to keep usercmd() simple and readable.  Your code might look something like the following:
  
-    if ((cmd == DPGET) && (rcsid == RSC_MYRSC1)) {+    if ((cmd == PCGET) && (rcsid == RSC_MYRSC1)) {
         ret = snprintf(buf, *plen, "%1x\n", pctx->intr);         ret = snprintf(buf, *plen, "%1x\n", pctx->intr);
         *plen = ret;  // (errors are handled in calling routine)         *plen = ret;  // (errors are handled in calling routine)
         return;         return;
     }     }
-    else if ((cmd == DPSET) && (rcsid == RSC_MYRSC1)) {+    else if ((cmd == PCSET) && (rcsid == RSC_MYRSC1)) {
         ret = sscanf(val, "%x", &newrsc1);         ret = sscanf(val, "%x", &newrsc1);
         if ((ret != 1) || (newrsc1 < 0) || (newrsc1 > 0xf)) {         if ((ret != 1) || (newrsc1 < 0) || (newrsc1 > 0xf)) {
Line 413: Line 414:
         newrsc2 = getrsc2(val);         newrsc2 = getrsc2(val);
     }     }
-    else if ((cmd == DPCAT) && (rcsid == RSC_MYRSC3)) {+    else if ((cmd == PCCAT) && (rcsid == RSC_MYRSC3)) {
     .....     .....
     }     }
Line 420: Line 421:
  
 ==== Sending Packets to the FPGA ==== ==== Sending Packets to the FPGA ====
-The daemon and DPCore communicate using a packet based protocol which is defined in include/fpga.h.  You build a packet by setting the command, specifying the slot number, the register address, and the number of bytes in the data part of the packet.  Your code to build a packet might appear as follows:+The daemon and pccore communicate using a packet based protocol which is defined in include/fpga.h.  You build a packet by setting the command, specifying the slot number, the register address, and the number of bytes in the data part of the packet.  Your code to build a packet might appear as follows:
     static void sendconfigtofpga(     static void sendconfigtofpga(
         MYPERIDEV *pctx,   // This peripheral's context         MYPERIDEV *pctx,   // This peripheral's context
Line 437: Line 438:
         // Write the values for the pins, direction, and interrupt mask         // Write the values for the pins, direction, and interrupt mask
         // down to the card.         // down to the card.
-        pkt.cmd = DP_CMD_OP_WRITE DP_CMD_AUTOINC;+        pkt.cmd = PC_CMD_OP_WRITE PC_CMD_AUTOINC;
         pkt.core = pmycore->core_id;         pkt.core = pmycore->core_id;
         pkt.reg = MYPERI_REG_RSC1;   // the first reg of the three         pkt.reg = MYPERI_REG_RSC1;   // the first reg of the three
Line 444: Line 445:
         pkt.data[2] = pctx->rsc3;         pkt.data[2] = pctx->rsc3;
         pkt.count = 3;         pkt.count = 3;
-        txret = dpi_tx_pkt(pmycore, &pkt, 4 + pkt.count); // 4 header + data+        txret = pc_tx_pkt(pmycore, &pkt, 4 + pkt.count); // 4 header + data
  
  
 Some peripherals use a FIFO as a data endpoint.  In this case you would want to write all the bytes to one register.  Other peripherals have a string of registers that should be written secuentially.  This is referred to as "autoincrement" or "no autoincrement" Autoincrement can apply to both reading and writing registers so the four possibilities for the command are:  Some peripherals use a FIFO as a data endpoint.  In this case you would want to write all the bytes to one register.  Other peripherals have a string of registers that should be written secuentially.  This is referred to as "autoincrement" or "no autoincrement" Autoincrement can apply to both reading and writing registers so the four possibilities for the command are: 
-    pkt.cmd = DP_CMD_OP_WRITE DP_CMD_AUTOINC+    pkt.cmd = PC_CMD_OP_WRITE PC_CMD_AUTOINC
-    pkt.cmd = DP_CMD_OP_WRITE DP_CMD_NOAUTOINC+    pkt.cmd = PC_CMD_OP_WRITE PC_CMD_NOAUTOINC
-    pkt.cmd = DP_CMD_OP_READ  DP_CMD_AUTOINC+    pkt.cmd = PC_CMD_OP_READ  PC_CMD_AUTOINC
-    pkt.cmd = DP_CMD_OP_READ  DP_CMD_NOAUTOINC;+    pkt.cmd = PC_CMD_OP_READ  PC_CMD_NOAUTOINC;
  
-The routine to send a packet to the FPGA is dpi_tx_pkt().  You give it the peripheral address, the packet to send, and the total number of byte in the packet.  Dpi_tx_pkd() returns a success or failure indication.  You can use this to warn the user or to schedule another attempt.  Generally, something is seriously wrong if dpi_tx_pkt returns an error.+The routine to send a packet to the FPGA is pc_tx_pkt().  You give it the peripheral address, the packet to send, and the total number of byte in the packet.  Pc_tx_pkt() returns a success or failure indication.  You can use this to warn the user or to schedule another attempt.  Generally, something is seriously wrong if pci_tx_pkt() returns an error.
  
  
Line 459: Line 460:
 When you initialized your peripheral instance you specified a packet receive callback.  Your callback should be able to handle three types of packets from the FPGA.  The first is an acknowledgement for a packet you sent.  Use this packet to stop the timeout timers if you haveset one.  Otherwise the acknowledgement can be ignored. When you initialized your peripheral instance you specified a packet receive callback.  Your callback should be able to handle three types of packets from the FPGA.  The first is an acknowledgement for a packet you sent.  Use this packet to stop the timeout timers if you haveset one.  Otherwise the acknowledgement can be ignored.
  
-The second kind of packet is a read response.  Validate the packet and then read and format the packet data to send to the application.  Data to the application must be formatted as an ASCII string terminated by a newline.  When an application gives a DP_GET command the daemon marks the TCP connection as waiting for data from your peripheral.  You send data back to the application using a call to send_ui().+The second kind of packet is a read response.  Validate the packet and then read and format the packet data to send to the application.  Data to the application must be formatted as an ASCII string terminated by a newline.  When an application gives a PC_GET command the daemon marks the TCP connection as waiting for data from your peripheral.  You send data back to the application using a call to send_ui().
  
-The third kind of packet is an autosend packet.  Recall that the FPGA does not have a interrupt line to the CPU and instead can automatically send packets up to the host.  The autosend packet is similar in structure to a read response packet.  The difference is the high bit of the ''cmd'' byte.  In a read response the bit is set and in an autosend packet the bit is cleared.  Autosend data is most often used with resources that support the DP_CAT command.  The publish subscribe system in dpdaemon allows multiple TCP connections to subscribe to the same resource.   The routine to publish autosend data is the bcst_ui() routine.  Your code for read responses and autosend data might look like:+The third kind of packet is an autosend packet.  Recall that the FPGA does not have a interrupt line to the CPU and instead can automatically send packets up to the host.  The autosend packet is similar in structure to a read response packet.  The difference is the high bit of the ''cmd'' byte.  In a read response the bit is set and in an autosend packet the bit is cleared.  Autosend data is most often used with resources that support the PC_CAT command.  The publish subscribe system in dpdaemon allows multiple TCP connections to subscribe to the same resource.   The routine to publish autosend data is the bcst_ui() routine.  Your code for read responses and autosend data might look like:
  
         // If a read response from a user dpget command, send value to UI         // If a read response from a user dpget command, send value to UI
-        if ((pkt->cmd & DP_CMD_AUTO_MASK) != DP_CMD_AUTO_DATA) {+        if ((pkt->cmd & PC_CMD_AUTO_MASK) != PC_CMD_AUTO_DATA) {
             pinlen = sprintf(pinstr, "%1x\n", (pkt->data[0] & 0x0f));             pinlen = sprintf(pinstr, "%1x\n", (pkt->data[0] & 0x0f));
             send_ui(pinstr, pinlen, prsc->uilock);             send_ui(pinstr, pinlen, prsc->uilock);
Line 489: Line 490:
  
 ==== Non-FPGA Based Peripherals ==== ==== Non-FPGA Based Peripherals ====
-If you have ever built an application using dpdaemon then you have most likely come to appreciate the clean, simple, publish-subscribe API that it offers.  This section describes how you can use the dpdaemon and its API for non-FPGA based peripherals.  Let's start with an example of how it works.+If you have built an application using pcdaemon then you might appreciate the clean, simple, publish-subscribe API that it offers.  This section describes how you can use the pcdaemon and its API for non-FPGA based peripherals.  Let's start with an example of how it works.
  
-Dpdaemon comes with several examples of non-FPGA peripherals.  The first one to test is the 'hello_world' demo.  Start dpdaemon with any DPCore binary you have available.  Then at a command prompt enter: +Pcdaemon comes with several examples of non-FPGA peripherals.  The first one to test is the 'hello_world' demo.  Start pcdaemon with any pccore binary you have available.  Then at a command prompt enter: 
-    dploadso hellodemo.so +    pcloadso hellodemo.so 
-    dplist +    pclist 
-    dplist hellodemo +    pclist hellodemo 
-You should see the new peripheral listed in slot #11.  The help text displays the reqsources available to the peripheral.  Test it with the commands: +You should see the new peripheral listed in last slot.  The help text displays the resources available to the peripheral.  Test it with the commands: 
-    dpget hellodemo messagetext +    pcget hellodemo messagetext 
-    dpset hellodemo messagetext "Hello, again!" +    pcset hellodemo messagetext "Hello, again!" 
-    dpset hellodemo period 5 +    pcset hellodemo period 5 
-    dpcat hellodemo message+    pccat hellodemo message
  
 The structure of non-FPGA based drivers is almost identical to FPGA based ones.  You will still need the Initialize() and usercmd() routines.  One difference is that non-FPGA based peripherals do not need a packet handler.  However they may need the ability to respond to data arriving from a file descriptor.  Working code for this is in the gamepad driver.  If you have as device or socket that you want to use as a data source you can add a callback for your file descriptor with a call to add_fd().  An example taken from the gamepad driver Initialize routine is shown below: The structure of non-FPGA based drivers is almost identical to FPGA based ones.  You will still need the Initialize() and usercmd() routines.  One difference is that non-FPGA based peripherals do not need a packet handler.  However they may need the ability to respond to data arriving from a file descriptor.  Working code for this is in the gamepad driver.  If you have as device or socket that you want to use as a data source you can add a callback for your file descriptor with a call to add_fd().  An example taken from the gamepad driver Initialize routine is shown below:
Line 511: Line 512:
     pctx->gpfd = open(pctx->device, (O_RDONLY | O_NONBLOCK));     pctx->gpfd = open(pctx->device, (O_RDONLY | O_NONBLOCK));
     if (pctx->gpfd != -1) {     if (pctx->gpfd != -1) {
-        add_fd(pctx->gpfd, DP_READ, getevents, (void *) pctx);+        add_fd(pctx->gpfd,PC_READ, getevents, (void *) pctx);
     }     }
  
Line 520: Line 521:
     {     {
  
-You can think of dpdaemon as having two parts, the daemon part and the FPGA part.  The FPGA part is actually started as if it were a non-FPGA driver.  As mentioned above, dpdaemon load the driver for the "enumerator" peripheral and then the enumerator driver loads drivers for the peripherals found in the list from the FPGA.  You can easily make dpdaemon entirely ////non-FPGA//// based by a small change in main() of daemon/main.c.+You can think of pcdaemon as having two parts, the daemon part and the FPGA part.  The FPGA part is actually started as if it were a non-FPGA driver.  As mentioned above, pcdaemon loads the driver for the "enumerator" peripheral and then the enumerator driver loads drivers for the peripherals found in the list from the FPGA.  You can easily make dpdaemon entirely ////non-FPGA//// based by a small change in main() of daemon/main.c.
     // Add drivers here to always have them when the program starts     // Add drivers here to always have them when the program starts
     // The first loaded is in slot 0, the next in slot 1, ...     // The first loaded is in slot 0, the next in slot 1, ...
usersguide/devguide.1657840309.txt.gz · Last modified: 2022/07/14 23:11 by dpisuperadmin