Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Lock and memory checking enhancements
[palacios.git] / linux_module / palacios-stubs.c
1 #include <linux/kernel.h>
2 #include <linux/kthread.h>
3 #include <linux/spinlock.h>
4 #include <linux/gfp.h>
5 #include <linux/interrupt.h>
6 #include <linux/linkage.h>
7 #include <linux/sched.h>
8 #include <linux/uaccess.h>
9 #include <asm/irq_vectors.h>
10 #include <asm/io.h>
11
12 #include <linux/init.h>
13 #include <linux/module.h>
14 #include <linux/kthread.h>
15 #include <asm/uaccess.h>
16 #include <linux/smp.h>
17 #include <linux/vmalloc.h>
18
19 #include <palacios/vmm.h>
20 #include <palacios/vmm_host_events.h>
21 #include "palacios.h"
22
23 #include "mm.h"
24
25 #include "memcheck.h"
26 #include "lockcheck.h"
27
28 // The following can be used to track heap bugs
29 // zero memory after allocation
30 #define ALLOC_ZERO_MEM 0
31 // pad allocations by this many bytes on both ends of block
32 #define ALLOC_PAD      0
33
34
35 u32 pg_allocs = 0;
36 u32 pg_frees = 0;
37 u32 mallocs = 0;
38 u32 frees = 0;
39 u32 vmallocs = 0;
40 u32 vfrees = 0;
41
42 static struct v3_vm_info * irq_to_guest_map[256];
43
44
45 extern unsigned int cpu_khz;
46
47 extern int cpu_list[NR_CPUS];
48 extern int cpu_list_len;
49
50
51 static char *print_buffer[NR_CPUS];
52
53 static void deinit_print_buffers(void)
54 {
55     int i;
56
57     for (i=0;i<NR_CPUS;i++) {
58         if (print_buffer[i]) { 
59             palacios_free(print_buffer[i]);
60             print_buffer[i]=0;
61         }
62     }
63 }
64
65 static int init_print_buffers(void)
66 {
67     int i;
68     
69     memset(print_buffer,0,sizeof(char*)*NR_CPUS);
70
71 #if !V3_PRINTK_OLD_STYLE_OUTPUT
72
73     for (i=0;i<NR_CPUS;i++) { 
74         print_buffer[i] = palacios_alloc(V3_PRINTK_BUF_SIZE);
75         if (!print_buffer[i]) { 
76             ERROR("Cannot allocate print buffer for cpu %d\n",i);
77             deinit_print_buffers();
78             return -1;
79         }
80         memset(print_buffer[i],0,V3_PRINTK_BUF_SIZE);
81     }
82
83 #endif
84     
85     return 0;
86
87 }
88  
89 /**
90  * Prints a message to the console.
91  */
92 void palacios_print_scoped(void * vm, int vcore, const char *fmt, ...) {
93
94 #if V3_PRINTK_OLD_STYLE_OUTPUT
95
96   va_list ap;
97
98   va_start(ap, fmt);
99   vprintk(fmt, ap);
100   va_end(ap);
101
102   return
103
104 #else 
105
106   va_list ap;
107   char *buf;
108   unsigned int cpu = palacios_get_cpu();
109   struct v3_guest *guest = (struct v3_guest *)vm;
110
111   buf = print_buffer[cpu];
112
113   if (!buf) { 
114       printk(KERN_INFO "palacios (pcore %u): output skipped - no allocated buffer\n",cpu);
115       return;
116   } 
117
118   va_start(ap, fmt);
119   vsnprintf(buf,V3_PRINTK_BUF_SIZE, fmt, ap);
120   va_end(ap);
121
122 #if V3_PRINTK_CHECK_7BIT
123   {
124       char c=0;
125       int i;
126       for (i=0;i<strlen(buf);i++) { 
127           if (buf[i] < 0) {
128               c=buf[i];
129               break;
130           }
131       }
132       if (c!=0) { 
133           printk(KERN_INFO "palacios (pcore %u): ALERT ALERT 8 BIT CHAR (c=%d) DETECTED\n", cpu,c);
134       }
135   }
136 #endif
137
138   if (guest) {
139     if (vcore>=0) { 
140       printk(KERN_INFO "palacios (pcore %u vm %s vcore %u): %s",
141              cpu,
142              guest->name,
143              vcore,
144              buf);
145     } else {
146        printk(KERN_INFO "palacios (pcore %u vm %s): %s",
147              cpu,
148              guest->name,
149              buf);
150     }
151   } else {
152     printk(KERN_INFO "palacios (pcore %u): %s",
153            cpu,
154            buf);
155   }
156     
157   return;
158
159 #endif
160
161 }
162
163
164 /*
165  * Allocates a contiguous region of pages of the requested size.
166  * Returns the physical address of the first page in the region.
167  */
168 void *palacios_allocate_pages(int num_pages, unsigned int alignment) {
169     void * pg_addr = NULL;
170
171     if (num_pages<=0) { 
172       ERROR("ALERT ALERT Attempt to allocate zero or fewer pages\n");
173       return NULL;
174     }
175
176     pg_addr = (void *)alloc_palacios_pgs(num_pages, alignment);
177
178     if (!pg_addr) { 
179         ERROR("ALERT ALERT  Page allocation has FAILED Warning\n");
180         return NULL;
181     }
182
183     pg_allocs += num_pages;
184
185     MEMCHECK_ALLOC_PAGES(pg_addr,num_pages*4096);
186
187     return pg_addr;
188 }
189
190
191 /**
192  * Frees a page previously allocated via palacios_allocate_page().
193  * Note that palacios_allocate_page() can allocate multiple pages with
194  * a single call while palacios_free_page() only frees a single page.
195  */
196
197 void palacios_free_pages(void * page_paddr, int num_pages) {
198     pg_frees += num_pages;
199     free_palacios_pgs((uintptr_t)page_paddr, num_pages);
200     MEMCHECK_FREE_PAGES(page_paddr,num_pages*4096);
201
202 }
203
204
205 void *
206 palacios_alloc_extended(unsigned int size, unsigned int flags) {
207     void * addr = NULL;
208
209     if (size==0) { 
210       // note that modern kernels will respond to a zero byte
211       // kmalloc and return the address 0x10...  In Palacios, 
212       // we will simply not allow 0 byte allocs at all, of any kind
213       ERROR("ALERT ALERT attempt to kmalloc zero bytes rejected\n");
214       return NULL;
215     }
216
217     addr = kmalloc(size+2*ALLOC_PAD, flags);
218
219     if (!addr) { 
220        ERROR("ALERT ALERT  kmalloc has FAILED FAILED FAILED\n");
221        return NULL;
222     }   
223
224     mallocs++;
225
226 #if ALLOC_ZERO_MEM
227     memset(addr,0,size+2*ALLOC_PAD);
228 #endif
229
230     MEMCHECK_KMALLOC(addr,size+2*ALLOC_PAD);
231
232     return addr+ALLOC_PAD;
233 }
234
235 void *
236 palacios_valloc(unsigned int size)
237 {
238     void * addr = NULL;
239
240     if (size==0) { 
241       ERROR("ALERT ALERT attempt to vmalloc zero bytes rejected\n");
242       return NULL;
243     }
244
245     addr = vmalloc(size);
246
247     if (!addr) { 
248        ERROR("ALERT ALERT  vmalloc has FAILED FAILED FAILED\n");
249        return NULL;
250     }   
251
252     vmallocs++;
253
254     MEMCHECK_VMALLOC(addr,size);
255
256     return addr;
257 }
258
259 void palacios_vfree(void *p)
260 {
261   vfree(p);
262   vfrees++;
263   MEMCHECK_VFREE(p);
264 }
265
266 /**
267  * Allocates 'size' bytes of kernel memory.
268  * Returns the kernel virtual address of the memory allocated.
269  */
270 void *
271 palacios_alloc(unsigned int size) {
272
273     // It is very important that this test remains since 
274     // this function is used extensively throughout palacios and the linux
275     // module, both in places where interrupts are off and where they are on
276     // a GFP_KERNEL call, when done with interrupts off can lead to DEADLOCK
277     if (irqs_disabled()) {
278         return palacios_alloc_extended(size,GFP_ATOMIC);
279     } else {
280         return palacios_alloc_extended(size,GFP_KERNEL);
281     }
282
283 }
284
285 /**
286  * Frees memory that was previously allocated by palacios_alloc().
287  */
288 void
289 palacios_free(
290         void *                  addr
291 )
292 {
293     frees++;
294     kfree(addr-ALLOC_PAD);
295     MEMCHECK_KFREE(addr-ALLOC_PAD);
296 }
297
298 /**
299  * Converts a kernel virtual address to the corresponding physical address.
300  */
301 void *
302 palacios_vaddr_to_paddr(
303         void *                  vaddr
304 )
305 {
306     return (void*) __pa(vaddr);
307
308 }
309
310 /**
311  * Converts a physical address to the corresponding kernel virtual address.
312  */
313 void *
314 palacios_paddr_to_vaddr(
315         void *                  paddr
316 )
317 {
318   return __va(paddr);
319 }
320
321 /**
322  * Runs a function on the specified CPU.
323  */
324 static void 
325 palacios_xcall(
326         int                     cpu_id, 
327         void                    (*fn)(void *arg),
328         void *                  arg
329 )
330 {
331
332
333     // We set wait to 1, but I'm not sure this is necessary
334     smp_call_function_single(cpu_id, fn, arg, 1);
335     
336     return;
337 }
338
339
340 #define MAX_THREAD_NAME 32
341
342 struct lnx_thread_arg {
343     int (*fn)(void * arg);
344     void * arg;
345     char name[MAX_THREAD_NAME];
346 };
347
348 static int lnx_thread_target(void * arg) {
349     struct lnx_thread_arg * thread_info = (struct lnx_thread_arg *)arg;
350     int ret = 0;
351     /*
352       INFO("Daemonizing new Palacios thread (name=%s)\n", thread_info->name);
353
354       daemonize(thread_info->name);
355       allow_signal(SIGKILL);
356     */
357
358
359     ret = thread_info->fn(thread_info->arg);
360
361
362     INFO("Palacios Thread (%s) EXITING\n", thread_info->name);
363
364     palacios_free(thread_info);
365     // handle cleanup 
366
367     do_exit(ret);
368     
369     return 0; // should not get here.
370 }
371
372 /**
373  * Creates a kernel thread.
374  */
375 void *
376 palacios_start_kernel_thread(
377         int (*fn)               (void * arg),
378         void *                  arg,
379         char *                  thread_name) {
380
381     struct lnx_thread_arg * thread_info = palacios_alloc(sizeof(struct lnx_thread_arg));
382
383     if (!thread_info) { 
384         ERROR("ALERT ALERT Unable to allocate thread\n");
385         return NULL;
386     }
387
388     thread_info->fn = fn;
389     thread_info->arg = arg;
390     strncpy(thread_info->name,thread_name,MAX_THREAD_NAME);
391     thread_info->name[MAX_THREAD_NAME-1] =0;
392
393     return kthread_run( lnx_thread_target, thread_info, thread_info->name );
394 }
395
396
397 /**
398  * Starts a kernel thread on the specified CPU.
399  */
400 void * 
401 palacios_start_thread_on_cpu(int cpu_id, 
402                              int (*fn)(void * arg), 
403                              void * arg, 
404                              char * thread_name ) {
405     struct task_struct * thread = NULL;
406     struct lnx_thread_arg * thread_info = palacios_alloc(sizeof(struct lnx_thread_arg));
407
408     if (!thread_info) { 
409         ERROR("ALERT ALERT Unable to allocate thread to start on cpu\n");
410         return NULL;
411     }
412
413     thread_info->fn = fn;
414     thread_info->arg = arg;
415     strncpy(thread_info->name,thread_name,MAX_THREAD_NAME);
416     thread_info->name[MAX_THREAD_NAME-1] =0;
417
418     thread = kthread_create( lnx_thread_target, thread_info, thread_info->name );
419
420     if (IS_ERR(thread)) {
421         WARNING("Palacios error creating thread: %s\n", thread_info->name);
422         palacios_free(thread_info);
423         return NULL;
424     }
425
426     if (set_cpus_allowed_ptr(thread, cpumask_of(cpu_id)) != 0) {
427         WARNING("Attempt to start thread on disallowed CPU\n");
428         kthread_stop(thread);
429         palacios_free(thread_info);
430         return NULL;
431     }
432
433     wake_up_process(thread);
434
435     return thread;
436 }
437
438
439 /**
440  * Rebind a kernel thread to the specified CPU
441  * The thread will be running on target CPU on return
442  * non-zero return means failure
443  */
444 int
445 palacios_move_thread_to_cpu(int new_cpu_id, 
446                             void * thread_ptr) {
447     struct task_struct * thread = (struct task_struct *)thread_ptr;
448
449     INFO("Moving thread (%p) to cpu %d\n", thread, new_cpu_id);
450
451     if (thread == NULL) {
452         thread = current;
453     }
454
455     /*
456      * Bind to the specified CPU.  When this call returns,
457      * the thread should be running on the target CPU.
458      */
459     return set_cpus_allowed_ptr(thread, cpumask_of(new_cpu_id));
460 }
461
462
463 /**
464  * Returns the CPU ID that the caller is running on.
465  */
466 unsigned int 
467 palacios_get_cpu(void) 
468 {
469
470     /* We want to call smp_processor_id()
471      * But this is not safe if kernel preemption is possible 
472      * We need to ensure that the palacios threads are bound to a give cpu
473      */
474
475     unsigned int cpu_id = get_cpu(); 
476     put_cpu();
477     return cpu_id;
478 }
479
480 /**
481  * Interrupts the physical CPU corresponding to the specified logical guest cpu.
482  *
483  * NOTE: 
484  * This is dependent on the implementation of xcall_reschedule().  Currently
485  * xcall_reschedule does not explicitly call schedule() on the destination CPU,
486  * but instead relies on the return to user space to handle it. Because
487  * palacios is a kernel thread schedule will not be called, which is correct.
488  * If it ever changes to induce side effects, we'll need to figure something
489  * else out...
490  */
491
492 #include <asm/apic.h>
493
494 static void
495 palacios_interrupt_cpu(
496         struct v3_vm_info *     vm, 
497         int                     cpu_id, 
498         int                     vector
499 )
500 {
501     if (vector == 0) {
502         smp_send_reschedule(cpu_id);
503     } else {
504         apic->send_IPI_mask(cpumask_of(cpu_id), vector);
505     }
506     return;
507 }
508
509 /**
510  * Dispatches an interrupt to Palacios for handling.
511  */
512 static void
513 palacios_dispatch_interrupt( int vector, void * dev, struct pt_regs * regs ) {
514     struct v3_interrupt intr = {
515         .irq            = vector,
516         .error          = regs->orig_ax,
517         .should_ack     = 1,
518     };
519     
520     if (irq_to_guest_map[vector]) {
521         v3_deliver_irq(irq_to_guest_map[vector], &intr);
522     }
523     
524 }
525
526 /**
527  * Instructs the kernel to forward the specified IRQ to Palacios.
528  */
529 static int
530 palacios_hook_interrupt(struct v3_vm_info *     vm,
531                         unsigned int            vector ) {
532     INFO("hooking vector %d\n", vector);        
533
534     if (irq_to_guest_map[vector]) {
535         WARNING(
536                "%s: Interrupt vector %u is already hooked.\n",
537                __func__, vector);
538         return -1;
539     }
540
541     DEBUG(
542            "%s: Hooking interrupt vector %u to vm %p.\n",
543            __func__, vector, vm);
544
545     irq_to_guest_map[vector] = vm;
546
547     /*
548      * NOTE: Normally PCI devices are supposed to be level sensitive,
549      *       but we need them to be edge sensitive so that they are
550      *       properly latched by Palacios.  Leaving them as level
551      *       sensitive would lead to an interrupt storm.
552      */
553     //ioapic_set_trigger_for_vector(vector, ioapic_edge_sensitive);
554     
555     //set_idtvec_handler(vector, palacios_dispatch_interrupt);
556     if (vector < 32) {
557         ERROR("unexpected vector for hooking\n");
558         return -1;
559     } else {
560         int device_id = 0;              
561         
562         int flag = 0;
563         int error;
564                 
565         DEBUG("hooking vector: %d\n", vector);          
566
567         if (vector == 32) {
568             flag = IRQF_TIMER;
569         } else {
570             flag = IRQF_SHARED;
571         }
572
573         error = request_irq((vector - 32),
574                             (void *)palacios_dispatch_interrupt,
575                             flag,
576                             "interrupt_for_palacios",
577                             &device_id);
578         
579         if (error) {
580             ERROR("error code for request_irq is %d\n", error);
581             ERROR("request vector %d failed", vector);
582             return -1;
583         }
584     }
585         
586     return 0;
587 }
588
589
590
591 /**
592  * Acknowledges an interrupt.
593  */
594 static int
595 palacios_ack_interrupt(
596         int                     vector
597
598 {
599   ack_APIC_irq(); 
600   DEBUG("Pretending to ack interrupt, vector=%d\n", vector);
601   return 0;
602 }
603   
604 /**
605  * Returns the CPU frequency in kilohertz.
606  */
607 unsigned int
608 palacios_get_cpu_khz(void) 
609 {
610     INFO("cpu_khz is %u\n", cpu_khz);
611
612     if (cpu_khz == 0) { 
613         INFO("faking cpu_khz to 1000000\n");
614         return 1000000;
615     } else {
616         return cpu_khz;
617     }
618   //return 1000000;
619 }
620
621 /**
622  * Yield the CPU so other host OS tasks can run.
623  * This will return immediately if there is no other thread that is runnable
624  * And there is no real bound on how long it will yield
625  */
626 void
627 palacios_yield_cpu(void)
628 {
629     schedule();
630     return;
631 }
632
633 /**
634  * Yield the CPU so other host OS tasks can run.
635  * Given now immediately if there is no other thread that is runnable
636  * And there is no real bound on how long it will yield
637  */
638 void palacios_sleep_cpu(unsigned int us)
639 {
640
641     set_current_state(TASK_INTERRUPTIBLE);
642     if (us) {
643         unsigned int uspj = 1000000U/HZ;
644         unsigned int jiffies = us/uspj + ((us%uspj) !=0);  // ceiling 
645         schedule_timeout(jiffies);
646     } else {
647         schedule();
648     }
649     return;
650 }
651
652 void palacios_wakeup_cpu(void *thread)
653 {
654     wake_up_process(thread);
655     return;
656 }
657
658 /**
659  * Allocates a mutex.
660  * Returns NULL on failure.
661  */
662 void *
663 palacios_mutex_alloc(void)
664 {
665     spinlock_t *lock = palacios_alloc(sizeof(spinlock_t));
666
667     if (lock) {
668         spin_lock_init(lock);
669         LOCKCHECK_ALLOC(lock);
670     } else {
671         ERROR("ALERT ALERT Unable to allocate lock\n");
672         return NULL;
673     }
674     
675     return lock;
676 }
677
678 void palacios_mutex_init(void *mutex)
679 {
680   spinlock_t *lock = (spinlock_t*)mutex;
681   
682   if (lock) {
683     spin_lock_init(lock);
684     LOCKCHECK_ALLOC(lock);
685   }
686 }
687
688 void palacios_mutex_deinit(void *mutex)
689 {
690   spinlock_t *lock = (spinlock_t*)mutex;
691   
692   if (lock) {
693     // no actual spin_lock_deinit on linux
694     // our purpose here is to drive the lock checker
695     LOCKCHECK_FREE(lock);
696   }
697 }
698
699
700 /**
701  * Frees a mutex.
702  */
703 void
704 palacios_mutex_free(void * mutex) {
705     palacios_free(mutex);
706     LOCKCHECK_FREE(mutex);
707 }
708
709 /**
710  * Locks a mutex.
711  */
712 void 
713 palacios_mutex_lock(void * mutex, int must_spin) {
714
715     LOCKCHECK_LOCK_PRE(mutex);
716     spin_lock((spinlock_t *)mutex);
717     LOCKCHECK_LOCK_POST(mutex);
718 }
719
720
721 /**
722  * Locks a mutex, disabling interrupts on this core
723  */
724 void *
725 palacios_mutex_lock_irqsave(void * mutex, int must_spin) {
726     
727     unsigned long flags; 
728     
729     LOCKCHECK_LOCK_IRQSAVE_PRE(mutex,flags);
730     spin_lock_irqsave((spinlock_t *)mutex,flags);
731     LOCKCHECK_LOCK_IRQSAVE_POST(mutex,flags);
732
733     return (void *)flags;
734 }
735
736
737 /**
738  * Unlocks a mutex.
739  */
740 void 
741 palacios_mutex_unlock(
742         void *                  mutex
743
744 {
745     LOCKCHECK_UNLOCK_PRE(mutex);
746     spin_unlock((spinlock_t *)mutex);
747     LOCKCHECK_UNLOCK_POST(mutex);
748 }
749
750
751 /**
752  * Unlocks a mutex and restores previous interrupt state on this core
753  */
754 void 
755 palacios_mutex_unlock_irqrestore(void *mutex, void *flags)
756 {
757     LOCKCHECK_UNLOCK_IRQRESTORE_PRE(mutex,(unsigned long)flags);
758     // This is correct, flags is opaque
759     spin_unlock_irqrestore((spinlock_t *)mutex,(unsigned long)flags);
760     LOCKCHECK_UNLOCK_IRQRESTORE_POST(mutex,(unsigned long)flags);
761 }
762
763 /**
764  * Structure used by the Palacios hypervisor to interface with the host kernel.
765  */
766 static struct v3_os_hooks palacios_os_hooks = {
767         .print                  = palacios_print_scoped,
768         .allocate_pages         = palacios_allocate_pages,
769         .free_pages             = palacios_free_pages,
770         .malloc                 = palacios_alloc,
771         .free                   = palacios_free,
772         .vaddr_to_paddr         = palacios_vaddr_to_paddr,
773         .paddr_to_vaddr         = palacios_paddr_to_vaddr,
774         .hook_interrupt         = palacios_hook_interrupt,
775         .ack_irq                = palacios_ack_interrupt,
776         .get_cpu_khz            = palacios_get_cpu_khz,
777         .start_kernel_thread    = palacios_start_kernel_thread,
778         .yield_cpu              = palacios_yield_cpu,
779         .sleep_cpu              = palacios_sleep_cpu,
780         .wakeup_cpu             = palacios_wakeup_cpu,
781         .mutex_alloc            = palacios_mutex_alloc,
782         .mutex_free             = palacios_mutex_free,
783         .mutex_lock             = palacios_mutex_lock, 
784         .mutex_unlock           = palacios_mutex_unlock,
785         .mutex_lock_irqsave     = palacios_mutex_lock_irqsave, 
786         .mutex_unlock_irqrestore= palacios_mutex_unlock_irqrestore,
787         .get_cpu                = palacios_get_cpu,
788         .interrupt_cpu          = palacios_interrupt_cpu,
789         .call_on_cpu            = palacios_xcall,
790         .start_thread_on_cpu    = palacios_start_thread_on_cpu,
791         .move_thread_to_cpu     = palacios_move_thread_to_cpu,
792 };
793
794
795
796
797 int palacios_vmm_init( char *options )
798 {
799     int num_cpus = num_online_cpus();
800     char * cpu_mask = NULL;
801
802     if (cpu_list_len > 0) {
803         int major = 0;
804         int minor = 0;
805         int i = 0;
806
807         cpu_mask = palacios_alloc((num_cpus / 8) + 1);
808
809         if (!cpu_mask) { 
810             ERROR("Cannot allocate cpu mask\n");
811             return -1;
812         }
813
814         memset(cpu_mask, 0, (num_cpus / 8) + 1);
815         
816         for (i = 0; i < cpu_list_len; i++) {
817             if (cpu_list[i] >= num_cpus) {
818                 WARNING("CPU (%d) exceeds number of available CPUs. Ignoring...\n", cpu_list[i]);
819                 continue;
820             }
821
822             major = cpu_list[i] / 8;
823             minor = cpu_list[i] % 8;
824     
825             *(cpu_mask + major) |= (0x1 << minor);
826         }
827     }
828
829     memset(irq_to_guest_map, 0, sizeof(struct v3_vm_info *) * 256);
830
831     if (init_print_buffers()) {
832         ERROR("Cannot initialize print buffers\n");
833         palacios_free(cpu_mask);
834         return -1;
835     }
836
837     INFO("palacios_init starting - calling init_v3\n");
838
839     Init_V3(&palacios_os_hooks, cpu_mask, num_cpus, options);
840
841     return 0;
842
843 }
844
845
846 int palacios_vmm_exit( void ) {
847
848     Shutdown_V3();
849
850     INFO("palacios shutdown complete\n");
851
852     deinit_print_buffers();
853
854     return 0;
855 }