source: trunk/hal/tsar_mips32/drivers/soclib_tty.c @ 657

Last change on this file since 657 was 625, checked in by alain, 6 years ago

Fix a bug in the vmm_remove_vseg() function: the physical pages
associated to an user DATA vseg were released to the kernel when
the target process descriptor was in the reference cluster.
This physical pages release should be done only when the page
forks counter value is zero.
All other modifications are cosmetic.

File size: 20.4 KB
Line 
1/*
2 * soclib_tty.c - soclib tty driver implementation.
3 *
4 * Author  Alain Greiner (2016,2017,2018)
5 *
6 * Copyright (c)  UPMC Sorbonne Universites
7 *
8 * This file is part of ALMOS-MKH.
9 *
10 * ALMOS-MKH. is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; version 2.0 of the License.
13 *
14 * ALMOS-MKH is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24
25#include <hal_kernel_types.h>
26#include <dev_txt.h>
27#include <chdev.h>
28#include <soclib_tty.h>
29#include <thread.h>
30#include <printk.h>
31#include <hal_special.h>
32
33#if (DEBUG_SYS_READ & 1)
34extern uint32_t  enter_tty_cmd_read;
35extern uint32_t  exit_tty_cmd_read;
36
37extern uint32_t  enter_tty_isr_read;
38extern uint32_t  exit_tty_isr_read;
39#endif
40
41#if (DEBUG_SYS_WRITE & 1)
42extern uint32_t  enter_tty_cmd_write;
43extern uint32_t  exit_tty_cmd_write;
44
45extern uint32_t  enter_tty_isr_write;
46extern uint32_t  exit_tty_isr_write;
47#endif
48
49////////////////////////////////////////////////////////////////////////////////////
50// These global variables implement the TTY_RX  FIFOs (one per channel)
51////////////////////////////////////////////////////////////////////////////////////
52// Implementation note:
53// We allocate - in each cluster - two arrays of FIFOs containing as many entries
54// as the total number of TXT channels, but all entries are not used in all
55// clusters: for a given cluster K, a given entry corresponding to a given channel
56// and a given direction is only used if the associated chdev is in cluster K.
57// With this policy, the driver can ignore the actual placement of chdevs.
58////////////////////////////////////////////////////////////////////////////////////
59
60__attribute__((section(".kdata")))
61tty_fifo_t  tty_rx_fifo[CONFIG_MAX_TXT_CHANNELS];
62
63__attribute__((section(".kdata")))
64tty_fifo_t  tty_tx_fifo[CONFIG_MAX_TXT_CHANNELS];
65
66///////////////////////////////////////
67void soclib_tty_init( chdev_t * chdev )
68{
69    xptr_t reg_xp;
70
71    // initialise function pointers in chdev
72    chdev->cmd = &soclib_tty_cmd;
73    chdev->isr = &soclib_tty_isr;
74    chdev->aux = &soclib_tty_aux;
75
76    // get TTY channel and extended pointer on TTY peripheral base address
77    xptr_t   tty_xp  = chdev->base;
78    uint32_t channel = chdev->channel;
79    bool_t   is_rx   = chdev->is_rx;
80
81    // get SOCLIB_TTY device cluster and local pointer
82    cxy_t      tty_cxy = GET_CXY( tty_xp );
83    uint32_t * tty_ptr = GET_PTR( tty_xp );
84
85    // set TTY_RX_IRQ_ENABLE
86    reg_xp = XPTR( tty_cxy , tty_ptr + (channel * TTY_SPAN) + TTY_RX_IRQ_ENABLE );
87    hal_remote_s32( reg_xp , 1 );
88
89    // reset TTY_TX_IRQ_ENABLE
90    reg_xp = XPTR( tty_cxy , tty_ptr + (channel * TTY_SPAN) + TTY_TX_IRQ_ENABLE );
91    hal_remote_s32( reg_xp , 0 );
92
93    // reset relevant FIFO
94    if( is_rx )
95    {
96        tty_rx_fifo[channel].sts = 0;
97        tty_rx_fifo[channel].ptr = 0;
98        tty_rx_fifo[channel].ptw = 0;
99    }
100    else
101    {
102        tty_tx_fifo[channel].sts = 0;
103        tty_tx_fifo[channel].ptr = 0;
104        tty_tx_fifo[channel].ptw = 0;
105    }
106}  // end soclib_tty_init()
107
108//////////////////////////////////////////////////////////////
109void __attribute__ ((noinline)) soclib_tty_cmd( xptr_t th_xp )
110{
111    tty_fifo_t * fifo;     // TTY_RX or TTY_TX FIFO
112    char         byte;     // byte value
113    uint32_t     done;     // number of bytes moved
114
115    // get client thread cluster and local pointer
116    cxy_t      th_cxy = GET_CXY( th_xp );
117    thread_t * th_ptr = GET_PTR( th_xp );
118
119    // get command arguments
120    uint32_t type     = hal_remote_l32 ( XPTR( th_cxy , &th_ptr->txt_cmd.type   ) );
121    xptr_t   buf_xp   = hal_remote_l64( XPTR( th_cxy , &th_ptr->txt_cmd.buf_xp ) );
122    uint32_t count    = hal_remote_l32 ( XPTR( th_cxy , &th_ptr->txt_cmd.count  ) );
123    xptr_t   error_xp = XPTR( th_cxy , &th_ptr->txt_cmd.error );
124
125#if (DEBUG_SYS_READ & 1)
126if( type == TXT_READ) enter_tty_cmd_read = (uint32_t)hal_get_cycles();
127#endif
128
129#if (DEBUG_SYS_WRITE & 1)
130if( type == TXT_WRITE) enter_tty_cmd_write = (uint32_t)hal_get_cycles();
131#endif
132
133#if( DEBUG_HAL_TXT_TX || DEBUG_HAL_TXT_RX )
134thread_t * this = CURRENT_THREAD;
135#endif
136
137    // get TXT device cluster and pointers
138    xptr_t     dev_xp = (xptr_t)hal_remote_l64( XPTR( th_cxy , &th_ptr->txt_cmd.dev_xp ) );
139    cxy_t      dev_cxy = GET_CXY( dev_xp );
140    chdev_t  * dev_ptr = GET_PTR( dev_xp );
141
142    // get cluster and pointers for SOCLIB_TTY peripheral base segment
143    xptr_t     tty_xp = (xptr_t)hal_remote_l64( XPTR( dev_cxy , &dev_ptr->base ) );
144    cxy_t      tty_cxy = GET_CXY( tty_xp );
145    uint32_t * tty_ptr = GET_PTR( tty_xp );
146
147    // get TTY channel index and channel base address
148    uint32_t   channel = hal_remote_l32( XPTR( dev_cxy , &dev_ptr->channel ) );
149    uint32_t * base    = tty_ptr + TTY_SPAN * channel;
150
151    ///////////////////////
152    if( type == TXT_WRITE )         // write bytes to TTY_TX FIFO
153    {
154        fifo = &tty_tx_fifo[channel];
155
156        done = 0;
157
158        while( done < count )
159        {
160            if( fifo->sts < TTY_FIFO_DEPTH )   // put one byte to FIFO if TX_FIFO not full
161            {
162                // get one byte from command buffer
163                byte = hal_remote_lb( buf_xp + done );
164
165#if DEBUG_HAL_TXT_TX
166uint32_t   tx_cycle = (uint32_t)hal_get_cycles();
167if( DEBUG_HAL_TXT_TX < tx_cycle )
168printk("\n[%s] thread[%x,%x] put character <%c> to TXT%d_TX fifo / cycle %d\n",
169__FUNCTION__, this->process->pid, this->trdid, byte, channel, tx_cycle );
170#endif
171                // write byte to FIFO
172                fifo->data[fifo->ptw] = byte;
173
174                // prevent race
175                hal_fence();
176
177                // update FIFO state
178                fifo->ptw = (fifo->ptw + 1) % TTY_FIFO_DEPTH;
179                hal_atomic_add( &fifo->sts , 1 );
180
181                // udate number of bytes moved
182                done++;
183
184                // enable TX_IRQ
185                hal_remote_s32( XPTR( tty_cxy , base + TTY_TX_IRQ_ENABLE ) , 1 );
186            }
187            else                                // block & deschedule if TX_FIFO full
188            {
189                // block on ISR
190                thread_block( XPTR( local_cxy , CURRENT_THREAD ) , THREAD_BLOCKED_ISR );
191
192                // deschedule
193                sched_yield( "TTY_TX_FIFO full" ); 
194            }
195        }
196
197        // set error status in command and return
198        hal_remote_s32( error_xp , 0 );
199    }
200    ///////////////////////////
201    else if( type == TXT_READ )       // read several bytes from TTY_RX FIFO   
202    {
203        fifo = &tty_rx_fifo[channel];
204
205        done = 0;
206
207        while( done < count )
208        {
209            if( fifo->sts > 0 )               // get byte from FIFO if not empty
210            {
211                // get one byte from FIFO
212                char byte = fifo->data[fifo->ptr];
213
214#if DEBUG_HAL_TXT_RX
215uint32_t rx_cycle = (uint32_t)hal_get_cycles();
216if( DEBUG_HAL_TXT_RX < rx_cycle )
217printk("\n[%s] thread[%x,%x] get character <%c> from TXT%d_RX fifo / cycle %d\n",
218__FUNCTION__, this->process->pid, this->trdid, byte, channel, rx_cycle );
219#endif
220                // update FIFO state
221                fifo->ptr = (fifo->ptr + 1) % TTY_FIFO_DEPTH;
222                hal_atomic_add( &fifo->sts , -1 );
223
224                // set byte to command buffer
225                hal_remote_sb( buf_xp + done , byte );
226
227                // udate number of bytes
228                done++;
229            }
230            else                             //  deschedule if FIFO empty
231            {
232                // block on ISR
233                thread_block( XPTR( local_cxy , CURRENT_THREAD ) , THREAD_BLOCKED_ISR );
234   
235                // deschedule
236                sched_yield( "TTY_RX_FIFO empty" );
237            }
238        }  // end while
239
240        // set error status in command
241        hal_remote_s32( error_xp , 0 );
242    }
243    else
244    {
245        assert( false , "illegal TXT command\n" );
246    }
247
248#if (DEBUG_SYS_READ & 1)
249if( type == TXT_READ ) exit_tty_cmd_read = (uint32_t)hal_get_cycles();
250#endif
251
252#if (DEBUG_SYS_WRITE & 1)
253if( type == TXT_WRITE ) exit_tty_cmd_write = (uint32_t)hal_get_cycles();
254#endif
255
256}  // end soclib_tty_cmd()
257
258/////////////////////////////////////////////////////////////////
259void __attribute__ ((noinline)) soclib_tty_isr( chdev_t * chdev )
260{
261    thread_t   * server;            // pointer on TXT chdev server thread
262    lid_t        server_lid;        // local index of core running the server thread
263    uint32_t     channel;           // TXT chdev channel
264    bool_t       is_rx;             // TXT chdev direction
265    char         byte;              // byte value
266    xptr_t       owner_xp;          // extended pointer on TXT owner process
267    cxy_t        owner_cxy;         // TXT owner process cluster
268    process_t  * owner_ptr;         // local pointer on TXT owner process
269    pid_t        owner_pid;         // TXT owner process identifier
270    tty_fifo_t * fifo;              // pointer on TTY_TX or TTY_RX FIFO
271    cxy_t        tty_cxy;           // soclib_tty cluster
272    uint32_t   * tty_ptr;           // soclib_tty segment base address
273    uint32_t   * base;              // soclib_tty channel base address
274    xptr_t       status_xp;         // extended pointer on TTY_STATUS register
275    xptr_t       write_xp;          // extended pointer on TTY_WRITE register
276    xptr_t       read_xp;           // extended pointer on TTY_READ register
277    xptr_t       parent_xp;         // extended pointer on parent process
278    cxy_t        parent_cxy;        // parent process cluster
279    process_t  * parent_ptr;        // local pointer on parent process
280    thread_t   * parent_main_ptr;   // extended pointer on parent process main thread
281    xptr_t       parent_main_xp;    // local pointer on parent process main thread
282
283    // get TXT chdev channel, direction, server thread, and server core
284    channel    = chdev->channel;
285    is_rx      = chdev->is_rx;
286    server     = chdev->server;
287    server_lid = server->core->lid;
288
289#if (DEBUG_SYS_READ & 1)
290if( is_rx ) enter_tty_isr_read = (uint32_t)hal_get_cycles();
291#endif
292
293#if (DEBUG_SYS_WRITE & 1)
294if( is_rx == 0 ) enter_tty_isr_write = (uint32_t)hal_get_cycles();
295#endif
296
297#if DEBUG_HAL_TXT_RX
298uint32_t rx_cycle = (uint32_t)hal_get_cycles();
299#endif
300
301#if DEBUG_HAL_TXT_TX
302uint32_t tx_cycle = (uint32_t)hal_get_cycles();
303#endif
304
305    // get SOCLIB_TTY peripheral cluster and local pointer
306    tty_cxy = GET_CXY( chdev->base );
307    tty_ptr = GET_PTR( chdev->base );
308
309    // get channel base address
310    base    = tty_ptr + TTY_SPAN * channel;
311
312    // get extended pointer on TTY registers
313    status_xp = XPTR( tty_cxy , base + TTY_STATUS );
314    write_xp  = XPTR( tty_cxy , base + TTY_WRITE );
315    read_xp   = XPTR( tty_cxy , base + TTY_READ );
316
317    /////////////////////////// handle RX //////////////////////
318    if( is_rx )
319    {
320        fifo = &tty_rx_fifo[channel];
321
322        // try to move bytes until TTY_READ register empty
323        while( hal_remote_l32( status_xp ) & TTY_STATUS_RX_FULL )   
324        {
325            // get one byte from TTY_READ register & acknowledge RX_IRQ
326            byte = (char)hal_remote_lb( read_xp );
327
328            // filter special character ^Z  => block TXT owner process
329            if( byte == 0x1A ) 
330            {
331
332#if DEBUG_HAL_TXT_RX
333if( DEBUG_HAL_TXT_RX < rx_cycle )
334printk("\n[%s] read ^Z character from TXT%d\n", __FUNCTION__, channel );
335#endif
336                // get pointers on TXT owner process in owner cluster
337                owner_xp  = process_txt_get_owner( channel );
338               
339                // check process exist
340                assert( (owner_xp != XPTR_NULL) ,
341                "TXT owner process not found\n" );
342
343                // get relevant infos on TXT owner process
344                owner_cxy = GET_CXY( owner_xp );
345                owner_ptr = GET_PTR( owner_xp );
346                owner_pid = hal_remote_l32( XPTR( owner_cxy , &owner_ptr->pid ) );
347
348// TXT owner cannot be the INIT process
349assert( (owner_pid != 1) , "INIT process cannot be the TXT owner" );
350
351                // get parent process descriptor pointers
352                parent_xp  = hal_remote_l64( XPTR( owner_cxy , &owner_ptr->parent_xp ) );
353                parent_cxy = GET_CXY( parent_xp );
354                parent_ptr = GET_PTR( parent_xp );
355
356                // get pointers on the parent process main thread
357                parent_main_ptr = hal_remote_lpt(XPTR(parent_cxy,&parent_ptr->th_tbl[0])); 
358                parent_main_xp  = XPTR( parent_cxy , parent_main_ptr );
359
360                // transfer TXT ownership
361                process_txt_transfer_ownership( owner_xp );
362
363                // mark for block all threads in all clusters, but the main
364                process_sigaction( owner_pid , BLOCK_ALL_THREADS );
365
366                // block the main thread
367                xptr_t main_xp = XPTR( owner_cxy , &owner_ptr->th_tbl[0] );
368                thread_block( main_xp , THREAD_BLOCKED_GLOBAL );
369
370                // atomically update owner process termination state
371                hal_remote_atomic_or( XPTR( owner_cxy , &owner_ptr->term_state ) ,
372                                      PROCESS_TERM_STOP );
373
374                // unblock the parent process main thread
375                thread_unblock( parent_main_xp , THREAD_BLOCKED_WAIT );
376
377                return;
378            }
379
380            // filter special character ^C  => kill TXT owner process
381            if( byte == 0x03 )
382            {
383
384#if DEBUG_HAL_TXT_RX
385if( DEBUG_HAL_TXT_RX < rx_cycle )
386printk("\n[%s] read ^C character from TXT%d\n", __FUNCTION__, channel );
387#endif
388                // get pointer on TXT owner process in owner cluster
389                owner_xp  = process_txt_get_owner( channel );
390
391// check process exist
392assert( (owner_xp != XPTR_NULL) , "TXT owner process not found\n" );
393
394                // get relevant infos on TXT owner process
395                owner_cxy = GET_CXY( owner_xp );
396                owner_ptr = GET_PTR( owner_xp );
397                owner_pid = hal_remote_l32( XPTR( owner_cxy , &owner_ptr->pid ) );
398
399// TXT owner cannot be the INIT process
400assert( (owner_pid != 1) , "INIT process cannot be the TXT owner" );
401
402#if DEBUG_HAL_TXT_RX
403if( DEBUG_HAL_TXT_RX < rx_cycle )
404printk("\n[%s] TXT%d owner is process %x\n",
405__FUNCTION__, channel, owner_pid );
406#endif
407                // get parent process descriptor pointers
408                parent_xp  = hal_remote_l64( XPTR( owner_cxy , &owner_ptr->parent_xp ) );
409                parent_cxy = GET_CXY( parent_xp );
410                parent_ptr = GET_PTR( parent_xp );
411
412                // get pointers on the parent process main thread
413                parent_main_ptr = hal_remote_lpt(XPTR(parent_cxy,&parent_ptr->th_tbl[0])); 
414                parent_main_xp  = XPTR( parent_cxy , parent_main_ptr );
415
416                // transfer TXT ownership
417                process_txt_transfer_ownership( owner_xp );
418
419                // remove process from TXT list
420                // process_txt_detach( owner_xp );
421
422                // mark for delete all thread in all clusters, but the main
423                process_sigaction( owner_pid , DELETE_ALL_THREADS );
424               
425#if DEBUG_HAL_TXT_RX
426if( DEBUG_HAL_TXT_RX < rx_cycle )
427printk("\n[%s] marked for delete all threads of process but main\n",
428__FUNCTION__, owner_pid );
429#endif
430                // block main thread
431                xptr_t main_xp = XPTR( owner_cxy , &owner_ptr->th_tbl[0] );
432                thread_block( main_xp , THREAD_BLOCKED_GLOBAL );
433
434#if DEBUG_HAL_TXT_RX
435if( DEBUG_HAL_TXT_RX < rx_cycle )
436printk("\n[%s] blocked process %x main thread\n",
437__FUNCTION__, owner_pid );
438#endif
439
440                // atomically update owner process termination state
441                hal_remote_atomic_or( XPTR( owner_cxy , &owner_ptr->term_state ) ,
442                                      PROCESS_TERM_KILL );
443
444                // unblock the parent process main thread
445                thread_unblock( parent_main_xp , THREAD_BLOCKED_WAIT );
446
447#if DEBUG_HAL_TXT_RX
448if( DEBUG_HAL_TXT_RX < rx_cycle )
449printk("\n[%s] unblocked parent process %x main thread\n",
450__FUNCTION__, hal_remote_l32( XPTR( parent_cxy , &parent_ptr->pid) ) );
451#endif
452                return;
453            }
454
455            // write byte in TTY_RX FIFO if not full / discard byte if full
456            if ( fifo->sts < TTY_FIFO_DEPTH )
457            {
458
459#if DEBUG_HAL_TXT_RX
460if( DEBUG_HAL_TXT_RX < rx_cycle )
461printk("\n[%s] put character <%c> to TXT%d_RX fifo\n",
462__FUNCTION__, byte, channel );
463#endif
464                // store byte into FIFO
465                fifo->data[fifo->ptw] = (char)byte; 
466
467                // avoid race
468                hal_fence();
469
470                // update RX_FIFO state
471                fifo->ptw = (fifo->ptw + 1) % TTY_FIFO_DEPTH;
472                hal_atomic_add( &fifo->sts , 1 );
473
474                // unblock TXT_RX server thread
475                thread_unblock( XPTR( local_cxy , server ) , THREAD_BLOCKED_ISR );
476
477                // send IPI to core running server thread if required
478                if( server_lid != CURRENT_THREAD->core->lid )
479                {
480                    dev_pic_send_ipi( local_cxy , server_lid );
481                }
482            }
483            else
484            {
485                printk("\n[WARNING] %s : TTY_RX_FIFO[%d] full => discard character <%x>\n",
486                __FUNCTION__, channel, (uint32_t)byte );
487            }
488        }  // end while TTY_READ register full
489
490    }  // end RX
491
492    ///////////////////////  handle TX  /////////////////////////////
493    else
494    {
495        fifo = &tty_tx_fifo[channel];
496
497        // try to move bytes until TX_FIFO empty
498        while( fifo->sts > 0 )
499        {
500            // write one byte to TTY_WRITE register if empty / exit loop if full
501            if( (hal_remote_l32( status_xp ) & TTY_STATUS_TX_FULL) == 0 ) 
502            {
503                // get one byte from TX_FIFO
504                byte = fifo->data[fifo->ptr];
505
506#if DEBUG_HAL_TXT_TX
507if( DEBUG_HAL_TXT_TX < tx_cycle )
508printk("\n[%s] get character <%c> from TXT%d_TX fifo\n",
509__FUNCTION__, byte, channel );
510#endif
511                // update TX_FIFO state
512                fifo->ptr = (fifo->ptr + 1) % TTY_FIFO_DEPTH;
513                hal_atomic_add( &fifo->sts , -1 );
514
515                // write byte to TTY_WRITE register & acknowledge TX_IRQ
516                hal_remote_sb( write_xp , byte );
517            }
518        }
519
520        // disable TX_IRQ
521        hal_remote_s32( XPTR( tty_cxy , base + TTY_TX_IRQ_ENABLE ) , 0 );
522
523        // unblock TXT_TX server thread
524        thread_unblock( XPTR( local_cxy , server ) , THREAD_BLOCKED_ISR );
525
526        // send IPI to core running server thread if required
527        if( server_lid != CURRENT_THREAD->core->lid )
528        {
529            dev_pic_send_ipi( local_cxy , server_lid );
530        }
531
532    }  // end TX
533
534    hal_fence();
535
536#if (DEBUG_SYS_READ & 1)
537if( is_rx ) exit_tty_isr_read = (uint32_t)hal_get_cycles();
538#endif
539
540#if (DEBUG_SYS_WRITE & 1)
541if( is_rx == 0 ) exit_tty_isr_write = (uint32_t)hal_get_cycles();
542#endif
543
544}  // end soclib_tty_isr()
545
546/////////////////////////////////////////////////////////////
547void __attribute__ ((noinline)) soclib_tty_aux( void * args )
548{
549    uint32_t   status;
550    bool_t     empty;
551    uint32_t   i;
552
553    xptr_t       dev_xp = ((txt_sync_args_t *)args)->dev_xp;
554    const char * buffer = ((txt_sync_args_t *)args)->buffer;
555    uint32_t     count  = ((txt_sync_args_t *)args)->count;
556   
557    // get chdev cluster and local pointer
558    cxy_t     dev_cxy = GET_CXY( dev_xp );
559    chdev_t * dev_ptr = GET_PTR( dev_xp );
560
561    // get extended pointer on TTY channel base address
562    xptr_t tty_xp = (xptr_t)hal_remote_l64( XPTR( dev_cxy , &dev_ptr->base ) );
563
564    // get TTY channel segment cluster and local pointer
565    cxy_t      tty_cxy = GET_CXY( tty_xp );
566    uint32_t * tty_ptr = GET_PTR( tty_xp );
567
568    // get extended pointers on TTY_WRITE & TTY_STATUS registers
569    xptr_t write_xp  = XPTR( tty_cxy , tty_ptr + TTY_WRITE );
570    xptr_t status_xp = XPTR( tty_cxy , tty_ptr + TTY_STATUS );
571
572    // loop on characters
573    for( i = 0 ; i < count ; i++ )
574    {
575        // busy waiting policy on TTY_STATUS register
576        do
577        {
578            // get TTY_STATUS
579            status = hal_remote_l32( status_xp );
580            empty  = ( (status & TTY_STATUS_TX_FULL) == 0 );
581
582            // transfer one byte if TX buffer empty
583            if ( empty )  hal_remote_sb( write_xp , buffer[i] );
584        }
585        while ( empty == false );
586    }
587}  // end soclib_tty_aux()
588
589
590
Note: See TracBrowser for help on using the repository browser.