22// Licensed under the Apache License, Version 2.0, see LICENSE for details.
33// SPDX-License-Identifier: Apache-2.0
44
5+ #include <stdbool.h>
6+
57#ifdef __linux__
68#include <linux/limits.h>
79#include <pty.h>
1214#include <assert.h>
1315#include <errno.h>
1416#include <fcntl.h>
15- #include <limits.h>
1617#include <stdio.h>
1718#include <stdlib.h>
1819#include <string.h>
2223#include <unistd.h>
2324
2425#include "spidpi.h"
25- #ifdef VERILATOR
2626#include "verilator_sim_ctrl.h"
27- #endif
2827
2928// This holds the necessary SPI state.
30- #define MAX_TRANSACTION 4
29+
3130struct spidpi_ctx {
3231 int loglevel ;
33- char ptyname [64 ];
3432 int host ;
3533 int device ;
34+ FILE * log_file ;
3635 FILE * mon_file ;
37- char mon_pathname [PATH_MAX ];
3836 void * mon ;
39- int tick ;
40- int cpol ;
41- int cpha ;
42- int msbfirst ; // shift direction
43- int nout ;
44- int bout ;
45- int nin ;
46- int bin ;
47- int din ;
48- int nmax ;
49- char driving ;
50- int state ;
51- char buf [MAX_TRANSACTION ];
37+ unsigned tick ;
38+ uint8_t rx ;
39+ uint8_t tx ;
40+ bool push_tx ;
5241};
5342
54- // SPI Host States
55- #define SP_IDLE 0
56- #define SP_CSFALL 1
57- #define SP_DMOVE 2
58- #define SP_LASTBIT 3
59- #define SP_CSRISE 4
60- #define SP_FINISH 99
61-
62- // Enable this define to stop tracing at cycle 4
63- // and resume at the first SPI packet
64- // #define CONTROL_TRACE
65-
6643void * spidpi_create (const char * name , int mode , int loglevel ) {
67- int i ;
6844 struct spidpi_ctx * ctx =
6945 (struct spidpi_ctx * )calloc (1 , sizeof (struct spidpi_ctx ));
7046 assert (ctx );
7147
7248 ctx -> loglevel = loglevel ;
73- ctx -> mon = monitor_spi_init (mode );
7449 ctx -> tick = 0 ;
75- ctx -> msbfirst = 1 ;
76- ctx -> nmax = MAX_TRANSACTION ;
77- ctx -> nin = 0 ;
78- ctx -> nout = 0 ;
79- ctx -> bout = 0 ;
80- ctx -> state = SP_IDLE ;
81- /* mode is CPOL << 1 | CPHA
82- * cpol = 0 --> external clock matches internal
83- * cpha = 0 --> drive on internal falling edge, capture on rising
84- */
85- ctx -> cpol = ((mode == 0 ) || (mode == 2 )) ? 0 : 1 ;
86- ctx -> cpha = ((mode == 1 ) || (mode == 3 )) ? 1 : 0 ;
87- /* CPOL = 1 for clock idle high */
88- ctx -> driving = P2D_CSB | ((ctx -> cpol ) ? P2D_SCK : 0 );
50+ ctx -> rx = P2D_CSB ;
51+ ctx -> tx = 0 ;
8952 char cwd [PATH_MAX ];
53+ char ptyname [64 ];
9054 char * cwd_rv ;
9155 cwd_rv = getcwd (cwd , sizeof (cwd ));
9256 assert (cwd_rv != NULL );
@@ -98,162 +62,136 @@ void *spidpi_create(const char *name, int mode, int loglevel) {
9862 rv = openpty (& ctx -> host , & ctx -> device , 0 , & tty , 0 );
9963 assert (rv != -1 );
10064
101- rv = ttyname_r (ctx -> device , ctx -> ptyname , 64 );
65+ rv = ttyname_r (ctx -> device , ptyname , 64 );
10266 assert (rv == 0 && "ttyname_r failed" );
10367
10468 int cur_flags = fcntl (ctx -> host , F_GETFL , 0 );
10569 assert (cur_flags != -1 && "Unable to read current flags." );
10670 int new_flags = fcntl (ctx -> host , F_SETFL , cur_flags | O_NONBLOCK );
10771 assert (new_flags != -1 && "Unable to set FD flags" );
10872
73+ // might be worth getting rid of this blahblahblah
10974 printf (
11075 "\n"
11176 "SPI: Created %s for %s. Connect to it with any terminal program, e.g.\n"
11277 "$ screen %s\n"
11378 "NOTE: a SPI transaction is run for every 4 characters entered.\n" ,
114- ctx -> ptyname , name , ctx -> ptyname );
79+ ptyname , name , ptyname );
80+
81+ char * spi_log = getenv ("VERILATOR_SPI_LOG" );
82+
83+ // check if some logging files are requested
84+ if (spi_log ) {
85+ // log SPI as ASCII-art signals (very verbose)
86+ char log_pathname [PATH_MAX ];
87+ if (strchr (spi_log , 'M' )) {
88+ // keep original name to avoid breaking some existing tool
89+ rv = snprintf (log_pathname , PATH_MAX , "%s/%s.log" , cwd , name );
90+ assert (rv < PATH_MAX && rv > 0 );
91+ ctx -> mon_file = fopen (log_pathname , "wt" );
92+ if (ctx -> mon_file == NULL ) {
93+ fprintf (stderr , "SPI: Unable to open file at %s: %s\n" , log_pathname ,
94+ strerror (errno ));
95+ free (ctx );
96+ return NULL ;
97+ }
98+ setlinebuf (ctx -> mon_file );
99+ ctx -> mon = monitor_spi_init (mode );
100+ printf (
101+ "SPI: Monitor output file created at %s. Works well with tail:\n"
102+ "$ tail -f %s\n" ,
103+ log_pathname , log_pathname );
104+ }
115105
116- rv = snprintf (ctx -> mon_pathname , PATH_MAX , "%s/%s.log" , cwd , name );
117- assert (rv <= PATH_MAX && rv > 0 );
118- ctx -> mon_file = fopen (ctx -> mon_pathname , "w" );
119- if (ctx -> mon_file == NULL ) {
120- fprintf (stderr , "SPI: Unable to open file at %s: %s\n" , ctx -> mon_pathname ,
121- strerror (errno ));
122- return NULL ;
106+ // log SPI PTY protocol (communication with peer)
107+ if (strchr (spi_log , 'P' )) {
108+ rv = snprintf (log_pathname , PATH_MAX , "%s/%s.pty.log" , cwd , name );
109+ assert (rv < PATH_MAX && rv > 0 );
110+ ctx -> log_file = fopen (log_pathname , "wt" );
111+ if (!ctx -> log_file ) {
112+ fprintf (stderr , "SPI: Unable to open file at %s: %s\n" , log_pathname ,
113+ strerror (errno ));
114+ setlinebuf (ctx -> log_file );
115+ } else {
116+ printf ("SPI: PTY output file created at %s.\n" , log_pathname );
117+ }
118+ }
123119 }
124- // more useful for tail -f
125- setlinebuf (ctx -> mon_file );
126- printf (
127- "SPI: Monitor output file created at %s. Works well with tail:\n"
128- "$ tail -f %s\n" ,
129- ctx -> mon_pathname , ctx -> mon_pathname );
120+
121+ ctx -> push_tx = false;
130122
131123 return (void * )ctx ;
132124}
133125
134- char spidpi_tick (void * ctx_void , const svLogicVecVal * d2p_data ) {
126+ __attribute__((noinline )) uint8_t spidpi_tick2 (void * ctx_void ,
127+ const svLogicVecVal * d2p_data ) {
135128 struct spidpi_ctx * ctx = (struct spidpi_ctx * )ctx_void ;
136129 assert (ctx );
137- int d2p = d2p_data -> aval ;
138130
139- // Will tick at the host clock
140- ctx -> tick ++ ;
131+ ctx -> tx = d2p_data -> aval & (D2P_SDO | D2P_SDO_EN );
141132
142- #ifdef VERILATOR
143- #ifdef CONTROL_TRACE
144- if (ctx -> tick == 4 ) {
145- VerilatorSimCtrl ::GetInstance ().TraceOff ();
133+ if (ctx -> push_tx ) {
134+ /* add '0' in ASCII so that it easier to debug */
135+ uint8_t out_byte = 0x30 | ctx -> tx ;
136+ if (ctx -> log_file ) {
137+ fprintf (ctx -> log_file , "@%u tx:%01x\n" , ctx -> tick , ctx -> tx );
138+ fflush (ctx -> log_file );
139+ }
140+ ctx -> push_tx = false;
141+ (void )write (ctx -> host , & out_byte , sizeof (out_byte ));
146142 }
147- #endif
148- #endif
149143
150- monitor_spi ( ctx -> mon , ctx -> mon_file , ctx -> loglevel , ctx -> tick , ctx -> driving ,
151- d2p ) ;
144+ // Will tick at the host clock
145+ ctx -> tick ++ ;
152146
153- if (ctx -> state == SP_IDLE ) {
154- int n = read (ctx -> host , & (ctx -> buf [ctx -> nin ]), ctx -> nmax - ctx -> nin );
155- if (n == -1 ) {
156- if (errno != EAGAIN ) {
157- fprintf (stderr , "Read on SPI FIFO gave %s\n" , strerror (errno ));
158- }
159- } else {
160- ctx -> nin += n ;
161- if (ctx -> nin == ctx -> nmax ) {
162- ctx -> nout = 0 ;
163- ctx -> nin = 0 ;
164- ctx -> bout = ctx -> msbfirst ? 0x80 : 0x01 ;
165- ctx -> bin = ctx -> msbfirst ? 0x80 : 0x01 ;
166- ctx -> din = 0 ;
167- ctx -> state = SP_CSFALL ;
168- #ifdef VERILATOR
169- #ifdef CONTROL_TRACE
170- VerilatorSimCtrl ::GetInstance ().TraceOn ();
171- #endif
172- #endif
173- }
174- }
147+ if (ctx -> mon && ctx -> mon_file ) {
148+ monitor_spi (ctx -> mon , ctx -> mon_file , ctx -> loglevel , ctx -> tick , ctx -> rx ,
149+ ctx -> tx );
175150 }
151+
176152 // SPI clock toggles every 4th tick (i.e. freq=primary_frequency/8)
177- if (( ctx -> tick & 3 ) || ( ctx -> state == SP_IDLE ) ) {
178- return ctx -> driving ;
153+ if (ctx -> tick & 3 ) {
154+ return ctx -> rx ;
179155 }
180156
181- // Only get here on sck edges when active
182- int internal_sck = (ctx -> tick & 4 ) ? 1 : 0 ;
183- int set_sck = (internal_sck ? P2D_SCK : 0 );
184- if (ctx -> cpol ) {
185- set_sck ^= P2D_SCK ;
186- }
157+ uint8_t in_byte ;
187158
188- if (internal_sck == ctx -> cpha ) {
189- // host driving edge (falling for mode 0)
190- switch (ctx -> state ) {
191- case SP_DMOVE :
192- // SCLK low, CSB low
193- ctx -> driving =
194- set_sck | (ctx -> buf [ctx -> nout ] & ctx -> bout ) ? P2D_SDI : 0 ;
195- ctx -> bout = (ctx -> msbfirst ) ? ctx -> bout >> 1 : ctx -> bout << 1 ;
196- if ((ctx -> bout & 0xff ) == 0 ) {
197- ctx -> bout = ctx -> msbfirst ? 0x80 : 0x01 ;
198- ctx -> nout ++ ;
199- if (ctx -> nout == ctx -> nmax ) {
200- ctx -> state = SP_LASTBIT ;
201- }
202- }
203- break ;
204- case SP_LASTBIT :
205- ctx -> state = SP_CSRISE ;
206- // fallthrough
207- default :
208- ctx -> driving = set_sck | (ctx -> driving & ~P2D_SCK );
209- break ;
210- }
211- } else {
212- // host other edge (rising for mode 0)
213- switch (ctx -> state ) {
214- // Read input data (opposite edge to sending)
215- // both DMOVE and LASTBIT states are data moving ones!
216- case SP_DMOVE :
217- case SP_LASTBIT :
218- ctx -> din = ctx -> din | ((d2p & D2P_SDO ) ? ctx -> bin : 0 );
219- ctx -> bin = (ctx -> msbfirst ) ? ctx -> bin >> 1 : ctx -> bin << 1 ;
220- if (ctx -> bin == 0 ) {
221- int rv = write (ctx -> host , & (ctx -> din ), 1 );
222- assert (rv == 1 && "write() failed." );
223- ctx -> bin = (ctx -> msbfirst ) ? 0x80 : 0x01 ;
224- ctx -> din = 0 ;
225- }
226- ctx -> driving = set_sck | (ctx -> driving & ~P2D_SCK );
227- break ;
228- case SP_CSFALL :
229- // CSB low, drive SDI to first bit
230- ctx -> driving =
231- (set_sck | (ctx -> buf [ctx -> nout ] & ctx -> bout ) ? P2D_SDI : 0 );
232- ctx -> state = SP_DMOVE ;
233- break ;
234- case SP_CSRISE :
235- // CSB high, clock stopped
236- ctx -> driving = P2D_CSB ;
237- ctx -> state = SP_IDLE ;
238- break ;
239- case SP_FINISH :
240- #ifdef VERILATOR
241- VerilatorSimCtrl ::GetInstance ().RequestStop (true);
242- #endif
243- break ;
244- default :
245- ctx -> driving = set_sck | (ctx -> driving & ~P2D_SCK );
246- break ;
159+ int n = read (ctx -> host , & in_byte , sizeof (in_byte ));
160+ if (n == -1 ) {
161+ if (errno != EAGAIN ) {
162+ fprintf (stderr , "Read on SPI FIFO gave %s\n" , strerror (errno ));
247163 }
164+ return ctx -> rx ;
248165 }
249- return ctx -> driving ;
166+
167+ /* filter MSB so that ASCII-encoded digit can be accepted (for debug) */
168+ in_byte &= P2D_SCK | P2D_CSB | P2D_SDI ;
169+ ctx -> rx = in_byte ;
170+ ctx -> push_tx = true;
171+
172+ if (ctx -> log_file ) {
173+ fprintf (ctx -> log_file , "@%u rx:%01x\n" , ctx -> tick , ctx -> rx );
174+ fflush (ctx -> log_file );
175+ }
176+
177+ return ctx -> rx ;
178+ }
179+
180+ uint8_t spidpi_tick (void * ctx_void , const svLogicVecVal * d2p_data ) {
181+ return spidpi_tick2 (ctx_void , d2p_data );
250182}
251183
252184void spidpi_close (void * ctx_void ) {
253185 struct spidpi_ctx * ctx = (struct spidpi_ctx * )ctx_void ;
254186 if (!ctx ) {
255187 return ;
256188 }
257- fclose (ctx -> mon_file );
189+ if (ctx -> mon_file ) {
190+ fclose (ctx -> mon_file );
191+ }
192+ if (ctx -> log_file ) {
193+ fprintf (ctx -> log_file , "%u ticks\n" );
194+ fclose (ctx -> log_file );
195+ }
258196 free (ctx );
259197}
0 commit comments