-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdesign.txt
253 lines (224 loc) · 11.7 KB
/
design.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
```
This document describes the design and internal organization of
pcdaemon. This document is intended for pcdaemon developers and
maintainers.
CONTENTS
This document is broken into the following sections:
- System Overview
- Nomenclature
- User Interface to the Daemon
- Build Notes
- Program Design
-- Data Structures
-- Algorithms
SYSTEM OVERVIEW
Pcdaemon provides user-space drivers to connect a high-level
ASCII over TCP command into low level packets sent to/from FPGA
based peripherals. It works well for SPI, I2C, and USB peripherals
as well. Each driver is implemented as a shared-object plug-in.
The terms driver and plug-in are synonymous in this document.
Pcdaemon is an empty shell in the sense that the daemon only
provides a command line interface, leaving the real functionality
of the APIs to the loadable shared object plug-ins. Features of
the daemon include:
- Command line tools to view and set driver/plug-in parameters
- All commands and data are printable ASCII
- Modular driver/plug-ins for easy development and debug
- A ready-to-run, event-driven daemon
The daemon listens for TCP connections and then expects one of
five ASCII commands to sent on the connection. All commands are
terminated with a new line. Commands include:
pcloadso <driver.so>
pcget <name|ID#> <resource_name>
pcset <name|ID#> <resource_name> <value>
pccat <name|ID#> <resource_name>
pclist [name|ID#]
NOMENCLATURE
There are just a few terms but before you proceed with the rest
of the document you should have a clear understanding of them.
- pcdaemon - The core executable that has main(), select(), and
utility routines for timers and file IO callback handling.
- driver - A loadable shared object file that is attached to
pcdaemon. Drivers parse the command line parameters from the
high level user interface and convert the high level command
into one or more low level reads, writes, or packets.
- slot/name - When a plug-in is loaded it is assigned a slot number.
Slots are used for both FPGA-based and non-FPGA based peripherals.
When a plug-in is loaded it gives itself a name. Commands
can use either the name or the slot number to specify a plug-in.
- resource - A plug-in parameter that is made visible using the
pcget, pcset, and pccat commands. Resources can be read-only,
write-only, read-write, and broadcast. Read-only is often
associated with a status, read-write with a configuration, and
broadcast with stream of sensor data.
- monitor - The use of pccat to continuously read broadcast data
as it is made available.
- core - An FPGA based peripheral that is given a slot. Cores
are numbered from 0 up to the number of peripherals in the FPGA
image. Peripheral/core #0 in the fpga has a list of drivers IDs
for the peripherals in the FPGA image.
USER INTERFACE
Pcdaemon's ASCII over TCP user interface gives you the ability
to read, write, and monitor resources from any plug-in. You can
specify a plug-in by its slot number or by the name chosen by
the plug-in when it was loaded. It is possible to load a plug-in
multiple times or to load different plug-ins that have the same
name. When this happens you will need to use the slot number
to address the second plug-in since the look-up of a plug-in
name stops on the first match. (Again, driver and plug-in are
the same with 'plug-in' being technically correct and 'driver'
easier for a user to understand.)
Without a lot of explanation, here again is the syntax of
the five command available in the UI.
pcloadso <plug-in.so>
pcget <name|ID#> <resource_name>
pcset <name|ID#> <resource_name> <value>
pccat <name|ID#> <resource_name>
pclist [name|ID#]
The pccat command is permanent in that the only way to stop the
feed of input data from the broadcast resource is to close the
TCP connection to the daemon.
The above commands are the protocol used over the TCP link,
however the above commands as also implemented as Linux programs.
One nice thing about the programs is that their syntax is identical
to that of the above protocol. For example you can telnet to
localhost port 8870 and enter
the following:
pcset motors dir 50
Or you can run the pcset command from your PC command prompt:
pcset motors dir 50
The only real difference between the TCP protocol and the Linux
commands is that you can specify an optional port number on the
Linux commands.
BUILD NOTES
While you can use the commands pcset, pcget, pccat, pclist,
and pcloadso as is, you may find that your application needs
would be better served if you could rename then commands to
better match your application. You can easy override the 'pc'
part of the commands with a string of your choosing. For example,
let's say you want the command for your robot control program to
be roboset, roboget, robocat, and robolist, and you want the TCP
port for the program to be on port 7788. You can do this with
the following:
export CPREFIX="robo"
export DEF_UIPORT=7788
make ; make install
Alternatively you could use
CPREFIX="robo" DEF_UIPORT=7788 make
CPREFIX="robo" DEF_UIPORT=7788 make install
PROGRAM DESIGN
Below is a description of the design of the pcdaemon from the
perspective of important data structures, program start, and code
layout.
-- Data Structures
There are separate data structures for slots, resources, UI TCP
connections, and FPGA peripherals (cores). Pcdaemon has connections
and slots, and slots have resources. A peripheral in the FPGA is
called a core and each core uses a slot.
Each slot has an instance of a plug-in assigned to it. The
SLOT data structure has the following fields:
- int slot_id; // zero indexed slot number for this slot
- char *name; // Human readable name of plug-in in slot
- char *desc; // Very brief description of plug-in
- char *help; // Full text of help for plug-in
- char *soname; // shared object file name
- void *handle; // dlopen() handle for soname
- void *priv; // Pointer to plug-in's private data
- RSC rsc[NUM_RSC]; // Resources visible on this slot
There are a few fields in SLOT worth mentioning. The soname comes
from either the command line (-s option), a pcloadso command, or from
the driver ID requested by a core in the FPGA.
After pcdaemon loads the soname driver file it uses dlsym() to
look up the location of the "Initialize" function which it then calls.
Part of each plug-in's initialization sequence is to malloc a data
structure for that instance of the plug-in. The plug-in fills in its
data structure and places a pointer to it in the priv field of its
SLOT structure.
Each plug-in has a set of resources associated with it. Part of
the plug-in's initialization sequence is to fill in the RSC data
structure for each of the plug-in's resources.
Resources are described by the RSC data structure:
- char *name; // User visible name of the resource
- void (*pgscb) (); // Handler for get/set cmds from UI
- void *slot; // Pointer to resource's SLOT
- int bkey; // Broadcast key. Broadcast data if set
- int flags; // broadcast | read-only | read-write
- int uilock; // UI ID # of session awaiting read reply
It may take a few milliseconds for the plug-in to read a value
from the underlying driver or piece of equipment. We don't want
to wait for the reply so we put the UI session number in uilock.
This way when the response does come in we know where to send it.
By their nature some resources are read-only, read-write,
write-only, or sensor broadcast. An invalid access generates an
error message.
The bkey structure member is a token which, if non-zero,
indicates that at least one UI session wants to read any broadcast
data from this resource. To make the token unique per system
resource it is set to a combination of slot and resource ID.
The bkey is set by a UI session and is cleared when the plug-in
tries to broadcast the sensor data but finds that no UI is
monitoring it.
There is one instance of the UI structure per TCP connection
to a user or other control program. The UI structure has the
following fields:
- int cn; // connection index for this conn
- int fd; // FD of TCP conn (=-1 if not in use)
- int bkey; // broadcast slot/rsc info, else zero
- int o_port; // Other-end TCP port number
- int o_ip; // Other-end IP address
- int cmdindx; // Index of next location in cmd buffer
- char cmd[MXCMD]; // command from UI program
UI session are pretty straightforward. The cmd buffer holds
characters until a newline is found and the command is processed.
You may recall that the cat command is permanent in that you must
close the connection to turn off the stream of sensor or input
data. If the bkey field is non-zero then the connection is locked
in a sensor cat. The bkey field is encoded with the slot and ID
of the resource that is locked in an pccat session.
A core is an FPGA based peripheral. Core are numbered from 0
up to (NUM_CORE-1). An FPGA image might have fewer cores than the
maximum. Each core in the FPGA is given a slot for user access
and initialization. COREs are allocated slots zero to (NUM_CORE-1).
However unused cores do not consume a SLOT. Having the CORE and
SLOT numbers be the same makes the implementation of the -o
(overload driver) option much easier to implement.
When the Verilog is compiled into an FPGA image the peripherals
in the image are encoded as a table of identification numbers in
peripheral #0. These ID numbers indirectly specify what driver
plug-in to load to support that peripheral.
When pcdaemon starts it loads a driver whose only function is
to read the driver ID table from peripheral #0, convert the ID
numbers to .so file names, and then load the .so drivers for each
peripheral.
Each core driver has a packet arrival callback (pcb) in the
CORE structure that is called when a packet from that core arrives
at the host. Core_id and slot_id are often but not necessarily the
same. CORE has the following fields:
- int slot_id; // which pcdaemon slot we're in
- int core_id; // index into CORE table
- int driv_id; // ID number of plug-in to load
- void (*pcb) (); // Callback for packet arrival
-- Algorithms
While pcdaemon is fairly simple overall there is one algorithms
that you should understand before working on the code.
- Broadcast - Some sensors, such as a quadrature decoder can be
sent to more than one UI TCP connection. This allows, for example,
a quadrature decoder to be an input to both the speed control and
navigation programs even if they are completely separate tasks. The
pccat command has the slot and resource to monitor. These two values
(slot/rsc) are combined into an integer value called bkey. The value
is copied to the resource structure and to the UI structure. When
data comes into the resource the plug-in will look at the bkey to see
if any UIs are waiting for it. If so, the plug-in formats the data
and calls bcst_data(). The bcst_data() routine looks at all the UI
sessions to see which one wants the data. When a second UI session
joins, it overwrites the bkey in the resource (but the value is the
same slot/rsc) and now when the plug-in calls bcst_data() it will find
two UIs waiting for the data. When a UI connection is dropped its
bkey value is cleared. If, for this example, both UIs drop, then
the bcst_data() routine will be called but find that no UI wants
the data. When this happens, bcst_data() clears the bkey in the
resource struct. The bkey mechanism saves CPU cycles by letting
plug-ins avoid the effort of formatting the data and attempting to
broadcast it when no user session wants the data.
```