aboutsummaryrefslogblamecommitdiffstats
path: root/erts/emulator/beam/io.c
blob: 2a55a7c09f8099cb65076ae034c167cd12001f78 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795

                   
  
                                                        
  




                                                                      
  



                                                                         
  
















                                                                       
                    









                        
                      

                               
                           






                                                                                                
                                                                   




                                                                                                 
                                                                                   


                                                                             

                                                           

                                                     



                             




                                                               


                                                                    








                                                                    
 


                                                       


                               

                                                              


                                                      
                         













































                                                   

                       













                                                  
                    











                                                              
      
 



                                                   
                                                                          
                                 
                                                                          









                                 
                                  





                                                
                             
 









































                                                                      



                                                      
                                
      

                                                      
       
                             


                                                                      

 

                                                                


                                                 
                                          

                                   
 



                                                   
                                 



                                                                
 




                                                                  
     


                                        
 
                                 
 




                                              
     
 

                     

               






























                                                                       
                                       






                                                             

                              
 



                                                                             
 








                                                                 



                                                     



                                   


                                         
      







                                                                       
 



                                                    








                                                                      




                                               


                            



                                                           
 
      

    
                         
 
                                                        
                                                              
      

                                                       
                                              
 
               


                                                                   
      
 

                                           
               


                                                  
 












                                                           
                                    

 













































                                                                    


     

                                                              
 









                                                                                 










                                                                     
      



                                                                

                                                                            
 










                                                 
                            

                         


                                   
                      


                               
                                                 





                                                                   
                                                           
                                                   










































                                                                              
                                                       
                                               

     
               
                               





                                                       
                                                   
 



                                                                         
 



                                                                               

                             
                                                         
                                                           
                                                           






                                                         





                                                             

                                  




                                                           
                    



                                                                       
      
                                            
















                                                                   

                                           


                                                            






                                         
                     



                                  
                                                            
     
                                                      

                    





                                              
                                                         
                                                           
                                                           
         
                        
                                
                                                             
     



                                      























                                                                                         
                      



                          
                                   


                               



                                                   
                                                            
                      




                                         



                                                             
                                                 
                                               
                                                       
                                         

     








                                                              
                                                   
 




                                                                        

                             
                                                         
                                                           
                                                           


                                                         
     



                                                     
                                                      
                             
                                                         
                                                           
                                                           



                                         

     


                                                                

               
                       



                                                   
     

      
                                      
 
                             











                                 
                                
                                  






                                                                             


















                                                                   




                                                                         









                                                                   
                                                                  



                                  

                               






































                                                  
                             









































































                                                                          
                                                                         
                                                                         
                                                                         


                                                                         
                                                                         


                                                                         
                                                                         

                                                                         
                                                                         










                                                                         
                                                                         













                                                                         








                                                                        

           


                                                   


                      






                        
                                                   












                                          


                                          






































                                                               



                              
                                      
 
                      



                       
             

              
                  
                      
             

 














                                                                                 
                         
























































                                                                                

                               




                                                         
 
                                            
 
                                    
 



                           
 











                                                 
      
 




















































                                                                      
                    
                            
      




















                                                         
         
 








                                                           
 










                                               


     








                                                                 
 









                                                           
 





















                                                                   
 

                                        
 


                                                              
     
 
 
                           
 






                                                                
 

                                                     
 






                                    
 





                                                       
 

                                            
 
















                                                        

 



































                                                                                
 

  




                                                                     
   






                              
 





































                                                              

 
 
  



















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































                                                                                     
   

                                                                 




































































































































                                                                               
                                                 


                               
                                                             
 


                                                                           






                            




                                                                           
                                                        
                    


                                


                                               


                                       











                                                  
                                                                          

                                                                     
 
                     






                               
                                                 




                                                        

                                            

                  
                                                            




                              



                                                                          





                                                                 
                                                  













                                                     

                                                     




                                             
                                                   







                                                       
                                           





                                                              
                                              

            
                                                             
                    


                                



                                           





                                                       

                                                                 

                                                                 





                                                               
                                                                           









                                                                        
                                                                  




                                                            




                                             





                                                              
                                   
                                                          










                                                                        
                                                                        


                                                                        
                                                                        

                                                                    
                     






                               
                                                 
                        







                                                    



                                                                          


               
                                                




                                                               
                                           









                                                                 
                                                  

























                                                                             

                                                         




                                             
                                                           









                                                                          
                                                        






                                                        
                                              

            
                                                             
                    


                            
                                       

                                   





                                                                         
                                                                         
                                                                     

                                                                            











                                                                 
                                                           












                                                                            
                    
                                           
                                                                            

                                                                  
      














                                                         
                                                                       
                                  










                                                                  
                        



                                                    

                                  
 
                                                   

                                                                     
                                             
                                             
                                                    





                                  
                                                       
     
                                               




                                                
                    
                                          
                                                                

                                                                   
      








                                                
                                                     
                                                     
                                                       














                                                             

                                    





                                                                  

                                                                        
                                                                   

                                                 

                                                                     


















                                                               
                                                               




















































                                                                               
                                                                            


































                                                                              


                                                                          


                 
                       





                                                      
                    









                                                                      
      
 






































































































































































































































































































































































































































































                                                                                         
 
























                                                                 
 
                                                           
 
                       
 
                
 














                                                                         
 






                                                          
 


















                                                           

 










                                        
 

                                   
 



                                                          
 




                                                                              
 








                                                         
 


                                                     
 
                                    
 















































                                                       
                          






























                                                                      

                              



                                                       



             
                                                              
 







                                                                            

 
 














                                    
 


                                                                

     
                                                                
 
                                      
 
























































































































                                                                           
                                 



                                             
         





                                                             
     













                                                                          
 



                                       
 
                                                      
 







                                                                        
     
 
 










































                                                                     
 










































                                                                                
     
 


































































                                                                  
 














                                                     

     







































































                                                                  
         





























                                                     
         
















                                                                    
         








                                                   
     










                                                             



















                                                                  
                                           
 
                                                            
 
                                     

               
                                                    
                                                                         
                                           
                                                                         


                                  
                          



                                       
                                                                  

                                  
                             



                                          
                                                                            


                                  

                                                                                












                                                                                     
                                      
 
              

                        
                    
                                 
      
 

                               
                                      

               

             

                                                                   

                                      



                                                      
                    

                                                     
                                                

                                         
      
            

                          

                                                                     

                                          
 
                    

                                                     
                                                

                                             
      
                              



                                                            
                                         









                                                                               






                                                 
                    













                                                                        
                                                   




                                                                             
      
 

                                                   
                                       









                                                           


                                                

 








                                        


                                                    




                                          
 
                 

 








                                                        
                                                                    




                                                                  
                    





                                                                
      











                                                                           
                                                                
                                                      
                                                
                                              
                    
                                                     
                                                                                

                                                                            
      







                                                                      
                                                                       








                                                                    
                                                           



                                                                   
                                                                                   













                                                                             
                                                                  






































                                                                       
                                         

                                               

               




                                                                       














                                                                            



                                                                     
















                                                                       























                                                                           


                                                    






                                       
                                                  
 
                                            






                              
                                                



                                                   
                                      
                                



                                                                          






                                                               
                                             
 
                                                            
                    


                           

                                      

                                  

 






















































































                                                                               
                                                            














                                                           
                                                         
































                                                                           


                                                         


                                                                   
      




                                                       


                                                         


                                                                   
      




































































































































                                                                            
                                                         
       
 






                                                                          

























                                                                            


                                                              





                                                      
      



                                                       


                                                              





                                                     
      





































                                                                                                      

                                                             




                                                             
                                                           























                                                                        
                                             





                                                                         

                                                              


                                                    
                                                            


























































































                                                                           
                                                                
                    


                                















                                                       

                                       







                             











































                                                                              
    
                                                                     
 
                        




                                                                         

                                       

                                                    

                                                      
                                            


                                                                        

 










                                                     
 



                                                                   
   



                                      
 




















                                                               
                                              







                                                           

                                                                              
 

                                              





                                                    
                                       


                                  
                                                                        
                                              





                                                                     
                                                               










                                                                      

                                                               
 

                                              







                                                    
                                       


                                  
                                                                        
                                              










                                                        
                                               

                                                                         
        

                                                                      




                                                     
                                                            




                                                 

                                                               

          
                    
                     


                        
                        


                               

                              
                                                       

                                                                      



                                             
                                        




                                                    
                                       






















                                                          
                                                                         

                                                                      






                                              
                                                                        


                             
                               

                
                                   























                                                                             
          


                                          
                                                     

 
          


                                          
                                                        

 
          


                                          
                                                        







                                             
                                     


                




                                                            
                                  




                                     
                                                                        



                   
               

                                                                 
                                                                   
                                     
                                                

                                                                    















                                                           
                                           




















                                                                       
                                    



                                                       
                                                 




















                                                              
                                        

 
                             

                            
                                                        
                              
                             




                           
                                    


                                                 
                             

                               
                                                            
                             
                             





                           
                                    



                                                 
                             

                               
                                                            
                              
                             





                                                  
                                 


                                              

























                                                                        







                                      
                                           





                                                            

                           

                             
                                                                                             
















                                                                       
                    








                                                                         
          




                                  
          

                                  
                                            
                
                                                               
                            



                
          

                                  
                                            
                

                                                               






























































































                                                                               
                                                               

          
               
                     







                                    

                                                     
                 
                            
















































                                                                         
                                                                 

          
               
                     







                                    
                          
                 

                            





















































                                                                         
                                                       

                                    
                    
 
                                        
                  
                    





















                                              
                   


 
                                                        



                                    
                                



























                                                                            
                                       



                                    
                           






                      

                                                     















                                          
                                                            














                                                 

                                                       















                                          
                                                              



















                                                 
                                                       
     
                                               

                                                        
                                                 

 
                                                    
 
                                            




                               
 



                                      

                                                       


                                                                  
                                           









                                                         
                                            










                                                    
                                            






                                                    


                                                                 
     
                                                 






                                       
                         




                               



                                                












                                                                




                                                            
 

                
 








                                                                
 
                                       

                                                                                 
 




                                                  


                                                                             
                                              




                                                  

                                                                              
                                                         
      

                                             


                              
                                                
 
                                                        




























                                                                                                      
 

                




                                                     
                                                         










                                                       
                                                          




                                  
                                                              







                                                      
                                                



                                                          

                                                                              
                                                         
      

                                             


                              
                                                
 
                                                        


























                                                                                             

                                                                         
              




                                                     
                                                         








                                    
 
                                                               



                                                                         

                                                                              
                                                         
      

                                             


                              
                                                
                                                        



























                                                                                             














                                                                  
                              
                                                                 
                                    




                                            
                                
                    
                                              
                                                                            

                                                                       
      


                                                            
                              
                                         
                                                           
                                








                                                       

                                              






                                                    

                                           
                            
                                                          
                                                                             
            
                                                                                   




                                                   
                                                                               











                                                              
                                            

                                
                    





                               

                                                                
             
                                                                  

     
                                                          























































                                                                 
                                            
                                                    
                                           



                                              
                                            



                                                    
                                        



                                           
                                            








                                                    
                                            



                               
                                                  
 
                      
                                                        

                  


                                                           
                                                        


                                                  
                                                    








                                                             
                                                      






                                              
                                                        




































































































































                                                                                                







                                                                 
















                                                        
                                                                   








                                                                                     
                                                






                                                               
                                           







                                                                 
                                          






















                                                                              




                                                           





                                                      


                                                   
                                                                      
                                                                     
     
                           


















                                                                                       

                                         








                                                   

                                                                               






































                                                                                     
                                                      



























                                                                  
                                                        











                                                             
                                                      






                                   
                                                                












                                      
                                                            



                           
                                                        






















                                                                     
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1996-2012. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */

/* 
 * I/O routines for manipulating ports.
 */

#define ERL_IO_C__

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"

/* must be included BEFORE global.h (since it includes erl_driver.h) */
#include "erl_sys_driver.h"
#include "erl_nif.h"

#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "dist.h"
#include "big.h"
#include "erl_binary.h"
#include "erl_bits.h"
#include "erl_version.h"
#include "error.h"
#include "erl_async.h"
#define ERTS_WANT_EXTERNAL_TAGS
#include "external.h"
#include "dtrace-wrapper.h"

extern ErlDrvEntry fd_driver_entry;
extern ErlDrvEntry vanilla_driver_entry;
extern ErlDrvEntry spawn_driver_entry;
extern ErlDrvEntry *driver_tab[]; /* table of static drivers, only used during initialization */

erts_driver_t *driver_list; /* List of all drivers, static and dynamic. */
erts_smp_rwmtx_t erts_driver_list_lock; /* Mutex for driver list */
static erts_smp_tsd_key_t driver_list_lock_status_key; /*stop recursive locks when calling 
							 driver init */
static erts_smp_tsd_key_t driver_list_last_error_key;  /* Save last DDLL error on a 
							  per thread basis (for BC interfaces) */

ErtsPTab erts_port erts_align_attribute(ERTS_CACHE_LINE_SIZE); /* The port table */
erts_smp_atomic_t erts_bytes_out;	/* No bytes sent out of the system */
erts_smp_atomic_t erts_bytes_in;	/* No bytes gotten into the system */

const ErlDrvTermData driver_term_nil = (ErlDrvTermData)NIL;

const Port erts_invalid_port = {{ERTS_INVALID_PORT}};

erts_driver_t vanilla_driver;
erts_driver_t spawn_driver;
erts_driver_t fd_driver;

int erts_port_synchronous_ops = 0;
int erts_port_schedule_all_ops = 0;
int erts_port_parallelism = 0;

static void deliver_result(Eterm sender, Eterm pid, Eterm res);
static int init_driver(erts_driver_t *, ErlDrvEntry *, DE_Handle *);
static void terminate_port(Port *p);
static void pdl_init(void);
#ifdef ERTS_SMP
static void driver_monitor_lock_pdl(Port *p);
static void driver_monitor_unlock_pdl(Port *p);
#define DRV_MONITOR_LOCK_PDL(Port) driver_monitor_lock_pdl(Port)
#define DRV_MONITOR_UNLOCK_PDL(Port) driver_monitor_unlock_pdl(Port)
#else
#define DRV_MONITOR_LOCK_PDL(Port) /* nothing */
#define DRV_MONITOR_UNLOCK_PDL(Port) /* nothing */
#endif

#define ERL_SMALL_IO_BIN_LIMIT (4*ERL_ONHEAP_BIN_LIMIT)
#define SMALL_WRITE_VEC  16

static ERTS_INLINE ErlIOQueue*
drvport2ioq(ErlDrvPort drvport)
{
    Port *prt = erts_thr_drvport2port_raw(drvport);
    erts_aint32_t state = erts_atomic32_read_nob(&prt->state);
    if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
	return NULL;
    else
	return &prt->ioq;
}

static ERTS_INLINE int
is_port_ioq_empty(Port *pp)
{
    int res;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));
    if (!pp->port_data_lock)
	res = (pp->ioq.size == 0);
    else {
	ErlDrvPDL pdl = pp->port_data_lock;
	erts_mtx_lock(&pdl->mtx);
	res = (pp->ioq.size == 0);
	erts_mtx_unlock(&pdl->mtx);
    }
    return res;
}

int
erts_is_port_ioq_empty(Port *pp)
{
    return is_port_ioq_empty(pp);
}

Uint
erts_port_ioq_size(Port *pp)
{
    int res;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));
    if (!pp->port_data_lock)
	res = pp->ioq.size;
    else {
	ErlDrvPDL pdl = pp->port_data_lock;
	erts_mtx_lock(&pdl->mtx);
	res = pp->ioq.size;
	erts_mtx_unlock(&pdl->mtx);
    }
    return (Uint) res;
}

/*
 * Line buffered I/O.
 */
typedef struct line_buf_context {
    LineBuf **b;
    char *buf;
    ErlDrvSizeT left;
    ErlDrvSizeT retlen;
} LineBufContext;

#define LINEBUF_EMPTY 0
#define LINEBUF_EOL 1
#define LINEBUF_NOEOL 2
#define LINEBUF_ERROR -1

#define LINEBUF_STATE(LBC) ((*(LBC).b)->data[0])

#define LINEBUF_DATA(LBC) (((*(LBC).b)->data) + 1)
#define LINEBUF_DATALEN(LBC) ((LBC).retlen)

#define LINEBUF_INITIAL 100

#ifdef USE_VM_PROBES
#define DTRACE_FORMAT_COMMON_PID_AND_PORT(PID, PORT)         \
    DTRACE_CHARBUF(process_str, DTRACE_TERM_BUF_SIZE);       \
    DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);          \
                                                             \
    dtrace_pid_str((PID), process_str);                      \
    dtrace_port_str((PORT), port_str);
#define DTRACE_FORMAT_COMMON_PROC_AND_PORT(PID, PORT)        \
    DTRACE_CHARBUF(process_str, DTRACE_TERM_BUF_SIZE);       \
    DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);          \
                                                             \
    dtrace_proc_str((PID), process_str);                     \
    dtrace_port_str((PORT), port_str);
#endif

static ERTS_INLINE void
kill_port(Port *pp)
{
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));
    erts_ptab_delete_element(&erts_port, &pp->common); /* Time of death */
    erts_port_task_free_port(pp);
    /* In non-smp case the port structure may have been deallocated now */
}

#ifdef ERTS_SMP

#ifdef ERTS_ENABLE_LOCK_CHECK
int
erts_lc_is_port_locked(Port *prt)
{
    if (!prt)
	return 0;
    ERTS_SMP_LC_ASSERT(prt->lock);
    return erts_smp_lc_mtx_is_locked(prt->lock);
}
#endif

#endif /* #ifdef ERTS_SMP */

static void initq(Port* prt);

#if defined(ERTS_ENABLE_LOCK_CHECK) || defined(ERTS_ENABLE_LOCK_COUNT)
#define ERTS_PORT_INIT_INSTR_NEED_ID 1
#else
#define ERTS_PORT_INIT_INSTR_NEED_ID 0
#endif

static ERTS_INLINE void port_init_instr(Port *prt
#if ERTS_PORT_INIT_INSTR_NEED_ID
				   , Eterm id
#endif
    )
{
#if !ERTS_PORT_INIT_INSTR_NEED_ID
    Eterm id = NIL; /* Not used */
#endif

    /*
     * Stuff that need to be initialized with the port id
     * in the instrumented case, but not in the normal case.
     */
#ifdef ERTS_SMP
    ASSERT(prt->drv_ptr && prt->lock);
    if (!prt->drv_ptr->lock)
	erts_mtx_init_locked_x(prt->lock, "port_lock", id);
#endif
    erts_port_task_init_sched(&prt->sched, id);
}

#if !ERTS_PORT_INIT_INSTR_NEED_ID
static ERTS_INLINE void port_init_instr_abort(Port *prt)
{
#ifdef ERTS_SMP
    ASSERT(prt->drv_ptr && prt->lock);
    if (!prt->drv_ptr->lock) {
	erts_mtx_unlock(prt->lock);
	erts_mtx_destroy(prt->lock);
    }
#endif
    erts_port_task_fini_sched(&prt->sched);
}
#endif

static void insert_port_struct(void *vprt, Eterm data)
{
    Port *prt = (Port *) vprt;
    Eterm id = make_internal_port(data);
#if ERTS_PORT_INIT_INSTR_NEED_ID
    /*
     * This cannot be done earlier in the instrumented
     * case since we don't now 'id' until now.
     */
    port_init_instr(prt, id);
#endif
    prt->common.id = id;
    erts_atomic32_init_relb(&prt->state, ERTS_PORT_SFLG_INITIALIZING);
}

#define ERTS_CREATE_PORT_FLAG_PARALLELISM		(1 << 0)

static Port *create_port(char *name,
			 erts_driver_t *driver,
			 erts_mtx_t *driver_lock,
			 int create_flags,
			 Eterm pid,
			 int *enop)
{
    Port *prt;
    char *p;
    size_t port_size, size;
    erts_aint32_t state = ERTS_PORT_SFLG_CONNECTED;
    erts_aint32_t x_pts_flgs = 0;
#ifdef DEBUG
    /* Make sure the debug flags survives until port is freed */
    state |= ERTS_PORT_SFLG_PORT_DEBUG;
#endif

#ifdef ERTS_SMP
    if (!driver_lock) {
	/* Align size for mutex following port struct */
	port_size = size = ERTS_ALC_DATA_ALIGN_SIZE(sizeof(Port));
	size += sizeof(erts_mtx_t);
    }
    else
#endif
	port_size = size = sizeof(Port);

    size += sys_strlen(name) + 1;

    p = erts_alloc_fnf(ERTS_ALC_T_PORT, size);
    if (!p) {
	if (enop)
	    *enop = ENOMEM;
	return NULL;
    }

    prt = (Port *) p;
    p += port_size;

#ifdef ERTS_SMP
    if (driver_lock) {
	prt->lock = driver_lock;
	erts_mtx_lock(driver_lock);
    }
    else {
	prt->lock = (erts_mtx_t *) p;
	p += sizeof(erts_mtx_t);
	state |= ERTS_PORT_SFLG_PORT_SPECIFIC_LOCK;
    }
    erts_smp_atomic_set_nob(&prt->run_queue,
			    (erts_aint_t) erts_get_runq_current(NULL));
    prt->xports = NULL;
#else
    erts_atomic32_init_nob(&prt->refc, 1);
    prt->cleanup = 0;
#endif
    
    prt->name = p;
    sys_strcpy(p, name);
    prt->drv_ptr = driver;
    ERTS_P_LINKS(prt) = NULL;
    ERTS_P_MONITORS(prt) = NULL;
    prt->linebuf = NULL;
    prt->bp = NULL;
    prt->suspended = NULL;
    prt->data = am_undefined;
    prt->port_data_lock = NULL;
    prt->control_flags = 0;
    prt->bytes_in = 0;
    prt->bytes_out = 0;
    prt->dist_entry = NULL;
    ERTS_PORT_INIT_CONNECTED(prt, pid);
    prt->common.u.alive.reg = NULL;
#ifdef ERTS_SMP
    prt->common.u.alive.ptimer = NULL;
#else
    sys_memset(&prt->common.u.alive.tm, 0, sizeof(ErlTimer));
#endif
    erts_port_task_handle_init(&prt->timeout_task);
    prt->psd = NULL;
    prt->drv_data = (SWord) 0;

    /* Set default tracing */
    erts_get_default_tracing(&ERTS_TRACE_FLAGS(prt), &ERTS_TRACER_PROC(prt));

    ASSERT(((char *) prt) == ((char *) &prt->common));

#if !ERTS_PORT_INIT_INSTR_NEED_ID
    /*
     * When 'id' isn't needed (the normal case), it is better to
     * do the initialization here avoiding unnecessary contention
     * on table...
     */
    port_init_instr(prt);
#endif

    if (!erts_ptab_new_element(&erts_port,
			       &prt->common,
			       (void *) prt,
			       insert_port_struct)) {

#if !ERTS_PORT_INIT_INSTR_NEED_ID
	port_init_instr_abort(prt);
#endif
#ifdef ERTS_SMP
	if (driver_lock)
	    erts_mtx_unlock(driver_lock);
#endif
	if (enop)
	    *enop = 0;
	return NULL;
    }

    ASSERT(prt == (Port *) (erts_ptab_pix2intptr_nob(
				&erts_port,
				internal_port_index(prt->common.id))));

    initq(prt);

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

    if (erts_port_schedule_all_ops)
	x_pts_flgs |= ERTS_PTS_FLG_FORCE_SCHED;

    if (create_flags & ERTS_CREATE_PORT_FLAG_PARALLELISM)
	x_pts_flgs |= ERTS_PTS_FLG_PARALLELISM;

    if (x_pts_flgs)
	erts_smp_atomic32_read_bor_nob(&prt->sched.flags, x_pts_flgs);

    erts_atomic32_set_relb(&prt->state, state);
    return prt;
}

#ifndef ERTS_SMP
void
erts_port_cleanup(Port *prt)
{
    if (prt->drv_ptr && prt->drv_ptr->handle)
	erts_ddll_dereference_driver(prt->drv_ptr->handle);
    prt->drv_ptr = NULL;
    erts_port_dec_refc(prt);
}
#endif

void
erts_port_free(Port *prt)
{
#if defined(ERTS_SMP) || defined(ERTS_ENABLE_LOCK_CHECK)
    erts_aint32_t state = erts_atomic32_read_nob(&prt->state);
#endif
    ERTS_LC_ASSERT(state & (ERTS_PORT_SFLG_INITIALIZING
			    | ERTS_PORT_SFLG_FREE));
    ASSERT(state & ERTS_PORT_SFLG_PORT_DEBUG);

#ifdef ERTS_SMP
    ERTS_LC_ASSERT(erts_atomic32_read_nob(&prt->common.refc) == 0);
#else
    ERTS_LC_ASSERT(erts_atomic32_read_nob(&prt->refc) == 0);
#endif

    erts_port_task_fini_sched(&prt->sched);

#ifdef ERTS_SMP
    ASSERT(prt->lock);
    if (state & ERTS_PORT_SFLG_PORT_SPECIFIC_LOCK)
	erts_mtx_destroy(prt->lock);

    /*
     * We cannot dereference a driver using driver
     * locking until here in smp case. Otherwise,
     * the driver lock may still be in use by others.
     *
     * In the non-smp case we cannot do it here since
     * this function may be called by non-scheduler
     * threads. This is done in erts_port_cleanup()
     * in the non-smp case.
     */
    if (prt->drv_ptr->handle)
	erts_ddll_dereference_driver(prt->drv_ptr->handle);
#endif
    erts_free(ERTS_ALC_T_PORT, prt);
}

/*
** Initialize v_start to point to the small fixed vector.
** Once (reallocated) we never reset the pointer to the small vector
** This is a possible optimisation.
*/
static void initq(Port* prt)
{
    ErlIOQueue* q = &prt->ioq;

    ERTS_LC_ASSERT(!prt->port_data_lock);

    q->size = 0;
    q->v_head = q->v_tail = q->v_start = q->v_small;
    q->v_end = q->v_small + SMALL_IO_QUEUE;
    q->b_head = q->b_tail = q->b_start = q->b_small;
    q->b_end = q->b_small + SMALL_IO_QUEUE;
}

static void stopq(Port* prt)
{
    ErlIOQueue* q;
    ErlDrvBinary** binp;

    if (prt->port_data_lock)
	driver_pdl_lock(prt->port_data_lock);

    q = &prt->ioq;
    binp = q->b_head;

    if (q->v_start != q->v_small)
	erts_free(ERTS_ALC_T_IOQ, (void *) q->v_start);

    while(binp < q->b_tail) {
	if (*binp != NULL)
	    driver_free_binary(*binp);
	binp++;
    }
    if (q->b_start != q->b_small)
	erts_free(ERTS_ALC_T_IOQ, (void *) q->b_start);
    q->v_start = q->v_end = q->v_head = q->v_tail = NULL;
    q->b_start = q->b_end = q->b_head = q->b_tail = NULL;
    q->size = 0;

    if (prt->port_data_lock) {
	driver_pdl_unlock(prt->port_data_lock);
	driver_pdl_dec_refc(prt->port_data_lock);
    }
}

int
erts_save_suspend_process_on_port(Port *prt, Process *process)
{
    int saved;
    erts_aint32_t flags;
    erts_port_task_sched_lock(&prt->sched);
    flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
    saved = ((flags & (ERTS_PTS_FLG_BUSY
		       | ERTS_PTS_FLG_EXIT)) == ERTS_PTS_FLG_BUSY);
    if (saved)
	erts_proclist_store_last(&prt->suspended, erts_proclist_create(process));
    erts_port_task_sched_unlock(&prt->sched);
    return saved;
}

/*
   Opens a driver.
   Returns the non-negative port number, if successful.
   If there is an error, -1 or -2 or -3 is returned. -2 means that
   there is valid error information in *error_number_ptr.
   Returning -3 means that an error in the given options was detected
   (*error_number_ptr must contain either BADARG or SYSTEM_LIMIT).
   The driver start function must obey the same conventions.
*/
Port *
erts_open_driver(erts_driver_t* driver,	/* Pointer to driver. */
		 Eterm pid,		/* Current process. */
		 char* name,		/* Driver name. */
		 SysDriverOpts* opts,	/* Options. */
		 int *error_type_ptr,	/* error type */
		 int *error_number_ptr)	/* errno in case of error type -2 */
{

#undef ERTS_OPEN_DRIVER_RET
#define ERTS_OPEN_DRIVER_RET(Prt, EType, ENo)	\
    do {					\
	if (error_type_ptr)			\
	    *error_type_ptr = (EType);		\
	if (error_number_ptr)			\
	    *error_number_ptr = (ENo);		\
	return (Prt);				\
    } while (0)

    ErlDrvData drv_data = 0;
    Port *port;
    int fpe_was_unmasked;
    int error_type, error_number;
    int port_errno = 0;
    erts_mtx_t *driver_lock = NULL;
    int cprt_flgs = 0;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
    if (!driver) {
	for (driver = driver_list; driver; driver = driver->next) {
	    if (sys_strcmp(driver->name, name) == 0)
		break;
	}
	if (!driver) { 
	    erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	    ERTS_OPEN_DRIVER_RET(NULL, -3, BADARG);
	}
    }
    if (driver == &spawn_driver) {
	char *p;
	erts_driver_t *d;

	/*
	 * Dig out the name of the driver or port program.
	 */

	if (!(opts->spawn_type & ERTS_SPAWN_EXECUTABLE)) {
	    /* No spawn driver default */
	    driver = NULL;
	}


	if (opts->spawn_type != ERTS_SPAWN_EXECUTABLE) {
	    p = name;
	    while(*p != '\0' && *p != ' ')
		p++;
	    if (*p == '\0')
		p = NULL;
	    else
		*p = '\0';

	    /*
	     * Search for a driver having this name.  Defaults to spawn_driver
	     * if not found.
	     */
	    
	    for (d = driver_list; d; d = d->next) {
		if (strcmp(d->name, name) == 0 && 
		    erts_ddll_driver_ok(d->handle)) {
		    driver = d;
		    break;
		}
	    }
	    if (p != NULL)
		*p = ' ';
	}
    }

    if (driver == NULL || (driver != &spawn_driver && opts->exit_status)) {
	erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	ERTS_OPEN_DRIVER_RET(NULL, -3, BADARG);
    }

#ifdef ERTS_SMP
    driver_lock = driver->lock;
#endif

    if (driver->handle != NULL) {
	erts_ddll_increment_port_count(driver->handle);
	erts_ddll_reference_driver(driver->handle);
    }
    erts_smp_rwmtx_runlock(&erts_driver_list_lock);

    /*
     * We'll set up the port before calling the start function,
     * to allow message sending and setting timers in the start function.
     */

    if (opts->parallelism)
	cprt_flgs |= ERTS_CREATE_PORT_FLAG_PARALLELISM;

    port = create_port(name, driver, driver_lock, cprt_flgs, pid, &port_errno);
    if (!port) {
	if (driver->handle) {
	    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
	    erts_ddll_decrement_port_count(driver->handle);
	    erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	    erts_ddll_dereference_driver(driver->handle);
	}
	if (port_errno)
	    ERTS_OPEN_DRIVER_RET(NULL, -2, port_errno);
	else
	    ERTS_OPEN_DRIVER_RET(NULL, -3, SYSTEM_LIMIT);
    }
    
    if (IS_TRACED_FL(port, F_TRACE_PORTS)) {
	trace_port_open(port,
		pid,		
		am_atom_put(port->name, strlen(port->name)));
    }

    error_number = error_type = 0;
    if (driver->start) {
	if (IS_TRACED_FL(port, F_TRACE_SCHED_PORTS)) {
	    trace_sched_ports_where(port, am_in, am_start);
	}
	port->caller = pid;
#ifdef USE_VM_PROBES
        if (DTRACE_ENABLED(driver_start)) {
            DTRACE_FORMAT_COMMON_PID_AND_PORT(pid, port)
            DTRACE3(driver_start, process_str, driver->name, port_str);
        }
#endif
	fpe_was_unmasked = erts_block_fpe();
	drv_data = (*driver->start)((ErlDrvPort) port, name, opts);
	if (((SWord) drv_data) == -1)
	    error_type = -1;
	else if (((SWord) drv_data) == -2) {
	    /*
	     * We need to save errno quickly after the
	     * call to the 'start' callback before
	     * something else modify it.
	     */
	    error_type = -2;
	    error_number = errno;
	}
	else if (((SWord) drv_data) == -3) {
	    error_type = -3;
	    error_number = BADARG;
	}

	erts_unblock_fpe(fpe_was_unmasked);
	port->caller = NIL;
	if (IS_TRACED_FL(port, F_TRACE_SCHED_PORTS)) {
	    trace_sched_ports_where(port, am_out, am_start);
	}
#ifdef ERTS_SMP
	if (port->xports)
	    erts_smp_xports_unlock(port);
	ASSERT(!port->xports);
#endif
    }

    if (error_type) {
	/*
	 * Must clean up the port.
	 */
#ifdef ERTS_SMP
	erts_cancel_smp_ptimer(port->common.u.alive.ptimer);
#else
	erts_cancel_timer(&(port->common.u.alive.tm));
#endif
	stopq(port);
	if (port->linebuf != NULL) {
	    erts_free(ERTS_ALC_T_LINEBUF,
		      (void *) port->linebuf);
	    port->linebuf = NULL;
	}
	if (driver->handle != NULL) {
	    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
	    erts_ddll_decrement_port_count(driver->handle);
	    erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	}
	kill_port(port);
	erts_port_release(port);
	ERTS_OPEN_DRIVER_RET(NULL, error_type, error_number);
    }
    port->drv_data = (UWord) drv_data;
    ERTS_OPEN_DRIVER_RET(port, 0, 0);

#undef ERTS_OPEN_DRIVER_RET
}

#ifdef ERTS_SMP

struct ErtsXPortsList_ {
    ErtsXPortsList *next;
    Port *port;
};

ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(xports_list, ErtsXPortsList, 50, ERTS_ALC_T_XPORTS_LIST)

#endif

/*
 * Driver function to create new instances of a driver
 * Historical reason: to be used with inet_drv for creating
 * accept sockets inorder to avoid a global table.
 */
ErlDrvPort
driver_create_port(ErlDrvPort creator_port_ix, /* Creating port */
		   ErlDrvTermData pid,    /* Owner/Caller */
		   char* name,            /* Driver name */
		   ErlDrvData drv_data)   /* Driver data */
{
    int cprt_flgs = 0;
    Port *creator_port;
    Port* port;
    erts_driver_t *driver;
    Process *rp;
    erts_mtx_t *driver_lock = NULL;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    /* Need to be called from a scheduler thread */
    if (!erts_get_scheduler_id())
	return ERTS_INVALID_ERL_DRV_PORT;

    creator_port = erts_drvport2port(creator_port_ix, NULL);
    if (!creator_port)
	return ERTS_INVALID_ERL_DRV_PORT;

    rp = erts_proc_lookup(pid);
    if (!rp)
	return ERTS_INVALID_ERL_DRV_PORT;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(creator_port));

    driver = creator_port->drv_ptr;
    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
    if (!erts_ddll_driver_ok(driver->handle)) {
	erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	return ERTS_INVALID_ERL_DRV_PORT;
    }

    if (driver->handle != NULL) {
	erts_ddll_increment_port_count(driver->handle);
	erts_ddll_reference_referenced_driver(driver->handle);
    }

#ifdef ERTS_SMP
    driver_lock = driver->lock;
#endif

    erts_smp_rwmtx_runlock(&erts_driver_list_lock);

    /* Inherit parallelism flag from parent */
    if (ERTS_PTS_FLG_PARALLELISM &
	erts_smp_atomic32_read_nob(&creator_port->sched.flags))
	cprt_flgs |= ERTS_CREATE_PORT_FLAG_PARALLELISM;
    port = create_port(name, driver, driver_lock, cprt_flgs, pid, NULL);
    if (!port) {
	if (driver->handle) {
	    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
	    erts_ddll_decrement_port_count(driver->handle);
	    erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	    erts_ddll_dereference_driver(driver->handle);
	}
	return ERTS_INVALID_ERL_DRV_PORT;
    }
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(port));

    erts_smp_proc_lock(rp, ERTS_PROC_LOCK_LINK);
    if (ERTS_PROC_IS_EXITING(rp)) {
	erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
	if (driver->handle) {
	    erts_smp_rwmtx_rlock(&erts_driver_list_lock);
	    erts_ddll_decrement_port_count(driver->handle);
	    erts_smp_rwmtx_runlock(&erts_driver_list_lock);
	}
	kill_port(port);
	erts_port_release(port);
	return ERTS_INVALID_ERL_DRV_PORT;
    }

    erts_add_link(&ERTS_P_LINKS(port), LINK_PID, pid);
    erts_add_link(&ERTS_P_LINKS(rp), LINK_PID, port->common.id);
    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);

#ifdef ERTS_SMP
    if (!driver_lock) {
	ErtsXPortsList *xplp = xports_list_alloc();
	xplp->port = port;
	xplp->next = creator_port->xports;
	creator_port->xports = xplp;
    }
#endif

    port->drv_data = (UWord) drv_data;

    return (ErlDrvPort) port;
}

#ifdef ERTS_SMP
void
erts_smp_xports_unlock(Port *prt)
{
    ErtsXPortsList *xplp;

    ASSERT(prt);
    xplp = prt->xports;
    ASSERT(xplp);
    while (xplp) {
	Port *rprt = xplp->port;
	ErtsXPortsList *free_xplp;
	erts_aint32_t state;
	if (rprt->xports)
	    erts_smp_xports_unlock(rprt);
	state = erts_atomic32_read_nob(&rprt->state);
	if ((state & ERTS_PORT_SFLG_CLOSING) && erts_is_port_ioq_empty(rprt))
	    terminate_port(rprt);
	erts_port_release(rprt);
	free_xplp = xplp;
	xplp = xplp->next;
	xports_list_free(free_xplp);
    }
    prt->xports = NULL;
}
#endif

/* Fills a possibly deep list of chars and binaries into vec
** Small characters are first stored in the buffer buf of length ln
** binaries found are copied and linked into msoh
** Return  vector length on succsess,
**        -1 on overflow
**        -2 on type error
*/

#define SET_VEC(iov, bv, bin, ptr, len, vlen) do {	\
   (iov)->iov_base = (ptr);				\
   (iov)->iov_len = (len);				\
   if (sizeof((iov)->iov_len) < sizeof(len)				\
       /* Check if (len) overflowed (iov)->iov_len */			\
       && ((len) >> (sizeof((iov)->iov_len)*CHAR_BIT)) != 0) {		\
       goto L_overflow;							\
   }									\
   *(bv)++ = (bin);					\
   (iov)++;						\
   (vlen)++;						\
} while(0)

static int
io_list_to_vec(Eterm obj,	/* io-list */
	       SysIOVec* iov,	/* io vector */
	       ErlDrvBinary** binv, /* binary reference vector */
	       ErlDrvBinary* cbin, /* binary to store characters */
	       ErlDrvSizeT bin_limit)	/* small binaries limit */
{
    DECLARE_ESTACK(s);
    Eterm* objp;
    char *buf  = cbin->orig_bytes;
    Uint len = cbin->orig_size;
    Uint csize  = 0;
    int vlen   = 0;
    char* cptr = buf;

    goto L_jump_start;  /* avoid push */

    while (!ESTACK_ISEMPTY(s)) {
	obj = ESTACK_POP(s);
    L_jump_start:
	if (is_list(obj)) {
	L_iter_list:
	    objp = list_val(obj);
	    obj = CAR(objp);
	    if (is_byte(obj)) {
		if (len == 0)
		    goto L_overflow;
		*buf++ = unsigned_val(obj);
		csize++;
		len--;
	    } else if (is_binary(obj)) {
		ESTACK_PUSH(s, CDR(objp));
		goto handle_binary;
	    } else if (is_list(obj)) {
		ESTACK_PUSH(s, CDR(objp));
		goto L_iter_list;    /* on head */
	    } else if (!is_nil(obj)) {
		goto L_type_error;
	    }	    
	    obj = CDR(objp);
	    if (is_list(obj))
		goto L_iter_list; /* on tail */
	    else if (is_binary(obj)) {
		goto handle_binary;
	    } else if (!is_nil(obj)) {
		goto L_type_error;
	    }
	} else if (is_binary(obj)) {
	    Eterm real_bin;
	    Uint offset;
	    Eterm* bptr;
	    ErlDrvSizeT size;
	    int bitoffs;
	    int bitsize;

	handle_binary:
	    size = binary_size(obj);
	    ERTS_GET_REAL_BIN(obj, real_bin, offset, bitoffs, bitsize);
	    ASSERT(bitsize == 0);
	    bptr = binary_val(real_bin);
	    if (*bptr == HEADER_PROC_BIN) {
		ProcBin* pb = (ProcBin *) bptr;
		if (bitoffs != 0) {
		    if (len < size) {
			goto L_overflow;
		    }
		    erts_copy_bits(pb->bytes+offset, bitoffs, 1,
				   (byte *) buf, 0, 1, size*8);
		    csize += size;
		    buf += size;
		    len -= size;
		} else if (bin_limit && size < bin_limit) {
		    if (len < size) {
			goto L_overflow;
		    }
		    sys_memcpy(buf, pb->bytes+offset, size);
		    csize += size;
		    buf += size;
		    len -= size;
		} else {
		    if (csize != 0) {
			SET_VEC(iov, binv, cbin, cptr, csize, vlen);
			cptr = buf;
			csize = 0;
		    }
		    if (pb->flags) {
			erts_emasculate_writable_binary(pb);
		    }
		    SET_VEC(iov, binv, Binary2ErlDrvBinary(pb->val),
			    pb->bytes+offset, size, vlen);
		}
	    } else {
		ErlHeapBin* hb = (ErlHeapBin *) bptr;
		if (len < size) {
		    goto L_overflow;
		}
		copy_binary_to_buffer(buf, 0,
				      ((byte *) hb->data)+offset, bitoffs,
				      8*size);
		csize += size;
		buf += size;
		len -= size;
	    }
	} else if (!is_nil(obj)) {
	    goto L_type_error;
	}
    }

    if (csize != 0) {
	SET_VEC(iov, binv, cbin, cptr, csize, vlen);
    }

    DESTROY_ESTACK(s);
    return vlen;

 L_type_error:
    DESTROY_ESTACK(s);
    return -2;

 L_overflow:
    DESTROY_ESTACK(s);
    return -1;
}

#define IO_LIST_VEC_COUNT(obj)						\
do {									\
    Uint _size = binary_size(obj);					\
    Eterm _real;							\
    ERTS_DECLARE_DUMMY(Uint _offset);					\
    int _bitoffs;							\
    int _bitsize;							\
    ERTS_GET_REAL_BIN(obj, _real, _offset, _bitoffs, _bitsize);		\
    if (_bitsize != 0) goto L_type_error;				\
    if (thing_subtag(*binary_val(_real)) == REFC_BINARY_SUBTAG &&	\
	_bitoffs == 0) {						\
	b_size += _size;						\
        if (b_size < _size) goto L_overflow_error;			\
	in_clist = 0;							\
	v_size++;							\
        if (_size >= ERL_SMALL_IO_BIN_LIMIT) {				\
            p_in_clist = 0;						\
            p_v_size++;							\
        } else {							\
            p_c_size += _size;						\
            if (!p_in_clist) {						\
                p_in_clist = 1;						\
                p_v_size++;						\
            }								\
        }								\
    } else {								\
	c_size += _size;						\
        if (c_size < _size) goto L_overflow_error;			\
	if (!in_clist) {						\
	    in_clist = 1;						\
	    v_size++;							\
	}								\
	p_c_size += _size;						\
	if (!p_in_clist) {						\
	    p_in_clist = 1;						\
	    p_v_size++;							\
	}								\
    }									\
} while (0)


/* 
 * Returns 0 if successful and a non-zero value otherwise.
 *
 * Return values through pointers:
 *    *vsize      - SysIOVec size needed for a writev
 *    *csize      - Number of bytes not in binary (in the common binary)
 *    *pvsize     - SysIOVec size needed if packing small binaries
 *    *pcsize     - Number of bytes in the common binary if packing
 *    *total_size - Total size of iolist in bytes
 */

static int 
io_list_vec_len(Eterm obj, int* vsize, Uint* csize,
		Uint* pvsize, Uint* pcsize,
		ErlDrvSizeT* total_size)
{
    DECLARE_ESTACK(s);
    Eterm* objp;
    Uint v_size = 0;
    Uint c_size = 0;
    Uint b_size = 0;
    Uint in_clist = 0;
    Uint p_v_size = 0;
    Uint p_c_size = 0;
    Uint p_in_clist = 0;
    Uint total; /* Uint due to halfword emulator */

    goto L_jump_start;  /* avoid a push */

    while (!ESTACK_ISEMPTY(s)) {
	obj = ESTACK_POP(s);
    L_jump_start:
	if (is_list(obj)) {
	L_iter_list:
	    objp = list_val(obj);
	    obj = CAR(objp);

	    if (is_byte(obj)) {
		c_size++;
		if (c_size == 0) {
		    goto L_overflow_error;
		}
		if (!in_clist) {
		    in_clist = 1;
		    v_size++;
		}
		p_c_size++;
		if (!p_in_clist) {
		    p_in_clist = 1;
		    p_v_size++;
		}
	    }
	    else if (is_binary(obj)) {
		IO_LIST_VEC_COUNT(obj);
	    }
	    else if (is_list(obj)) {
		ESTACK_PUSH(s, CDR(objp));
		goto L_iter_list;   /* on head */
	    }
	    else if (!is_nil(obj)) {
		goto L_type_error;
	    }

	    obj = CDR(objp);
	    if (is_list(obj))
		goto L_iter_list;   /* on tail */
	    else if (is_binary(obj)) {  /* binary tail is OK */
		IO_LIST_VEC_COUNT(obj);
	    }
	    else if (!is_nil(obj)) {
		goto L_type_error;
	    }
	}
	else if (is_binary(obj)) {
	    IO_LIST_VEC_COUNT(obj);
	}
	else if (!is_nil(obj)) {
	    goto L_type_error;
	}
    }

    total = c_size + b_size;
    if (total < c_size) {
	goto L_overflow_error;
    }
    *total_size = (ErlDrvSizeT) total;

    DESTROY_ESTACK(s);
    *vsize = v_size;
    *csize = c_size;
    *pvsize = p_v_size;
    *pcsize = p_c_size;
    return 0;

 L_type_error:
 L_overflow_error:
    DESTROY_ESTACK(s);
    return 1;
}

typedef enum {
    ERTS_TRY_IMM_DRV_CALL_OK,
    ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK,
    ERTS_TRY_IMM_DRV_CALL_INVALID_PORT,
    ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS
} ErtsTryImmDrvCallResult;

typedef struct {
    Process *c_p; /* Currently executing process (unlocked) */
    Port *port; /* Port to operate on */
    Eterm port_op; /* port operation as an atom */
    erts_aint32_t state; /* in: invalid state; out: read state (if read) */
    erts_aint32_t sched_flags; /* in: invalid flags; out: read flags (if read) */
    int async; /* Asynchronous operation */
    int pre_chk_sched_flags; /* Check sched flags before lock? */
    int fpe_was_unmasked;
} ErtsTryImmDrvCallState;

#define ERTS_INIT_TRY_IMM_DRV_CALL_STATE(C_P, PRT, SFLGS, PTS_FLGS, A, PRT_OP) \
    {(C_P), (PRT), (PRT_OP), (SFLGS), (PTS_FLGS), (A), 1, 0}

/*
 * Try doing an immediate driver callback call from a process. If
 * this fail, the operation should be scheduled in the normal case...
 *
 */
static ERTS_INLINE ErtsTryImmDrvCallResult
try_imm_drv_call(ErtsTryImmDrvCallState *sp)
{
    ErtsTryImmDrvCallResult res;
    erts_aint32_t invalid_state, invalid_sched_flags;
    Port *prt = sp->port;
    Process *c_p = sp->c_p;

    ASSERT(is_atom(sp->port_op));

    invalid_sched_flags = ERTS_PTS_FLGS_FORCE_SCHEDULE_OP;
    invalid_sched_flags |= sp->sched_flags;
    if (sp->async)
	invalid_sched_flags |= ERTS_PTS_FLG_PARALLELISM;

    if (sp->pre_chk_sched_flags) {
	sp->sched_flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
	if (sp->sched_flags & invalid_sched_flags)
	    return ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS;
    }

    if (erts_smp_port_trylock(prt) == EBUSY)
	return ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK;

    invalid_state = sp->state;
    sp->state = erts_atomic32_read_nob(&prt->state);
    if (sp->state & invalid_state) {
	res = ERTS_TRY_IMM_DRV_CALL_INVALID_PORT;
	goto locked_fail;
    }

    sp->sched_flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
    if (sp->sched_flags & invalid_sched_flags) {
	res = ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS;
	goto locked_fail;
    }

    if (c_p) {
	if (IS_TRACED_FL(c_p, F_TRACE_SCHED_PROCS))
	    trace_virtual_sched(c_p, am_out);
	if (erts_system_profile_flags.runnable_procs
	    && erts_system_profile_flags.exclusive)
	    profile_runnable_proc(c_p, am_inactive);

	erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
    }

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (IS_TRACED_FL(prt, F_TRACE_SCHED_PORTS))
	trace_sched_ports_where(prt, am_in, sp->port_op);
    if (erts_system_profile_flags.runnable_ports
	&& !erts_port_is_scheduled(prt))
    	profile_runnable_port(prt, am_active);

    sp->fpe_was_unmasked = erts_block_fpe();

    return ERTS_TRY_IMM_DRV_CALL_OK;

locked_fail:
    erts_port_release(prt);
    return res;
}

static ERTS_INLINE void
finalize_imm_drv_call(ErtsTryImmDrvCallState *sp)
{
    Port *prt = sp->port;
    Process *c_p = sp->c_p;

    erts_unblock_fpe(sp->fpe_was_unmasked);

#ifdef ERTS_SMP
    if (prt->xports)
	erts_smp_xports_unlock(prt);
    ASSERT(!prt->xports);
#endif

    if (IS_TRACED_FL(prt, F_TRACE_SCHED_PORTS))
	trace_sched_ports_where(prt, am_out, sp->port_op);
    if (erts_system_profile_flags.runnable_ports
	&& !erts_port_is_scheduled(prt))
    	profile_runnable_port(prt, am_inactive);

    erts_port_release(prt);

    if (c_p) {
	erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);

	if (IS_TRACED_FL(c_p, F_TRACE_SCHED_PROCS))
	    trace_virtual_sched(c_p, am_in);
	if (erts_system_profile_flags.runnable_procs
	    && erts_system_profile_flags.exclusive)
	    profile_runnable_proc(c_p, am_active);
    }
}

#define ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE (REF_THING_SIZE + 3)

static ERTS_INLINE void
queue_port_sched_op_reply(Process *rp,
			  ErtsProcLocks *rp_locksp,
			  Eterm *hp_start,
			  Eterm *hp,
			  Uint h_size,
			  ErlHeapFragment* bp,
			  Uint32 *ref_num,
			  Eterm msg)
{
    Eterm ref = make_internal_ref(hp);
    write_ref_thing(hp, ref_num[0], ref_num[1], ref_num[2]);
    hp += REF_THING_SIZE;

    msg = TUPLE2(hp, ref, msg);
    hp += 3;

    if (!bp) {
	HRelease(rp, hp_start + h_size, hp);
    }
    else {
	Uint used_h_size = hp - hp_start;
	ASSERT(h_size >= used_h_size);
	if (h_size > used_h_size)
	    bp = erts_resize_message_buffer(bp, used_h_size, &msg, 1);
    }

    erts_queue_message(rp,
		       rp_locksp,
		       bp,
		       msg,
		       NIL
#ifdef USE_VM_PROBES
		       , NIL
#endif
	);
}

static void
port_sched_op_reply(Eterm to, Uint32 *ref_num, Eterm msg)
{
    Process *rp = erts_proc_lookup_raw(to);
    if (rp) {
	ErlOffHeap *ohp;
	ErlHeapFragment* bp;
	Eterm msg_copy;
	Uint hsz, msg_sz;
	Eterm *hp, *hp_start;
	ErtsProcLocks rp_locks = 0;

	hsz = ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE;
	if (is_immed(msg))
	    msg_sz = 0;
	else {
	    msg_sz = size_object(msg);
	    hsz += msg_sz;
	}

	hp_start = hp = erts_alloc_message_heap(hsz,
						&bp,
						&ohp,
						rp,
						&rp_locks);
	if (is_immed(msg))
	    msg_copy = msg;
	else
	    msg_copy = copy_struct(msg, msg_sz, &hp, ohp);

	queue_port_sched_op_reply(rp,
				  &rp_locks,
				  hp_start,
				  hp,
				  hsz,
				  bp,
				  ref_num,
				  msg_copy);

	if (rp_locks)
	    erts_smp_proc_unlock(rp, rp_locks);
    }
}


ErtsPortOpResult
erts_schedule_proc2port_signal(Process *c_p,
			       Port *prt,
			       Eterm caller,
			       Eterm *refp,
			       ErtsProc2PortSigData *sigdp,
			       int task_flags,
			       ErtsProc2PortSigCallback callback)
{
    int sched_res;
    if (!refp) {
	if (c_p)
	    erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN);
    }
    else {
	ASSERT(c_p);
	sigdp->flags |= ERTS_P2P_SIG_DATA_FLG_REPLY;
	erts_make_ref_in_array(sigdp->ref);
	*refp = erts_proc_store_ref(c_p, sigdp->ref);

	/*
	 * Caller needs to wait for a message containing
	 * the ref that we just created. No such message
	 * can exist in callers message queue at this time.
	 * We therefore move the save pointer of the
	 * callers message queue to the end of the queue.
	 *
	 * NOTE: It is of vital importance that the caller
	 *       immediately do a receive unconditionaly
	 *       waiting for the message with the reference;
	 *       otherwise, next receive will *not* work
	 *       as expected!
	 */
	erts_smp_proc_lock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);

	if (ERTS_PROC_PENDING_EXIT(c_p)) {
	    /* need to exit caller instead */
	    erts_smp_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
	    KILL_CATCHES(c_p);
	    c_p->freason = EXC_EXIT;
	    return ERTS_PORT_OP_CALLER_EXIT;
	}

	ERTS_SMP_MSGQ_MV_INQ2PRIVQ(c_p);
	c_p->msg.save = c_p->msg.last;

	erts_smp_proc_unlock(c_p,
			     (ERTS_PROC_LOCK_MAIN
			      | ERTS_PROC_LOCKS_MSG_RECEIVE));
    }


    sigdp->caller = caller;

    /* Schedule port close call for later execution... */
    sched_res = erts_port_task_schedule(prt->common.id,
					NULL,
					ERTS_PORT_TASK_PROC_SIG,
					sigdp,
					callback,
					task_flags);

    if (c_p)
	erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_MAIN);

    if (sched_res != 0) {
	if (refp)
	    *refp = NIL;
	return ERTS_PORT_OP_DROPPED;
    }
    return ERTS_PORT_OP_SCHEDULED;
}

static ERTS_INLINE void
send_badsig(Port *prt)
{
    ErtsProcLocks rp_locks = ERTS_PROC_LOCKS_XSIG_SEND;
    Process* rp;
    Eterm connected = ERTS_PORT_GET_CONNECTED(prt);

    ERTS_SMP_CHK_NO_PROC_LOCKS;
    ERTS_LC_ASSERT(erts_get_scheduler_id());

    ASSERT(is_internal_pid(connected));

    rp = erts_proc_lookup_raw(connected);
    if (rp) {
	erts_smp_proc_lock(rp, rp_locks);
	if (!ERTS_PROC_IS_EXITING(rp))
	    (void) erts_send_exit_signal(NULL,
					 prt->common.id,
					 rp,
					 &rp_locks, 
					 am_badsig,
					 NIL,
					 NULL,
					 0);
	if (rp_locks)
	    erts_smp_proc_unlock(rp, rp_locks);
    }
}

static void
badsig_received(int bang_op,
		Port *prt,
		erts_aint32_t state,
		int bad_output_value)
{
    /*
     * if (bang_op)
     *   we are part of a "Prt ! Something" operation
     * else
     *   we are part of a call to a port BIF
     * behave accordingly...
     */
    if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP)) {
	if (bad_output_value) {
	    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	    erts_dsprintf(dsbufp, "Bad value on output port '%s'\n", prt->name);
	    erts_send_error_to_logger_nogl(dsbufp);
	}
	if (bang_op)
	    send_badsig(prt);
    }
}

static int
port_badsig(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    if (op == ERTS_PROC2PORT_SIG_EXEC)
	badsig_received(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
			prt,
			state,
			sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BAD_OUTPUT);
    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg);
    return ERTS_PORT_REDS_BADSIG;
}


/*
 * bad_port_signal() will
 * - preserve signal order of signals.
 * - send a 'badsig' exit signal to connected process if 'from' is an
 *   internal pid and the port is alive when the bad signal reaches
 *   it.
 */
static ErtsPortOpResult
bad_port_signal(Process *c_p,
		int flags,
		Port *prt,
		Eterm from,
		Eterm *refp,
		Eterm port_op)
{
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallResult try_call_res;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
	    c_p,
	    prt,
	    ERTS_PORT_SFLGS_INVALID_LOOKUP,
	    0,
	    !refp,
	    port_op);

    try_call_res = try_imm_drv_call(&try_call_state);
    switch (try_call_res) {
    case ERTS_TRY_IMM_DRV_CALL_OK:
	badsig_received(flags & ERTS_PORT_SIG_FLG_BANG_OP,
			prt,
			try_call_state.state,
			flags & ERTS_PORT_SIG_FLG_BAD_OUTPUT);
	finalize_imm_drv_call(&try_call_state);
	return ERTS_PORT_OP_BADARG;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
	/* Schedule badsig() call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = flags;

    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p->common.id,
					  refp,
					  sigdp,
					  0,
					  port_badsig);
}


/*
 * Driver outputv() callback
 */

static ERTS_INLINE void
call_driver_outputv(int bang_op,
		    Eterm caller,
		    Eterm from,
		    Port *prt,
		    erts_driver_t *drv,
		    ErlIOVec *evp)
{
    /*
     * if (bang_op)
     *   we are part of a "Prt ! {From, {command, Data}}" operation
     * else
     *   we are part of a call to port_command/[2,3]
     * behave accordingly...
     */
    if (bang_op && from != ERTS_PORT_GET_CONNECTED(prt))
	send_badsig(prt);
    else {
	ErlDrvSizeT size = evp->size;

	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

#ifdef USE_VM_PROBES
	if (DTRACE_ENABLED(driver_outputv)) {
	    DTRACE_FORMAT_COMMON_PID_AND_PORT(caller, prt);
	    DTRACE4(driver_outputv, process_str, port_str, prt->name, size);
	}
#endif

	prt->caller = caller;
	(*drv->outputv)((ErlDrvData) prt->drv_data, evp);
	prt->caller = NIL;

	prt->bytes_out += size;
	erts_smp_atomic_add_nob(&erts_bytes_out, size);
    }
}

static ERTS_INLINE void
cleanup_scheduled_outputv(ErlIOVec *ev, ErlDrvBinary *cbinp)
{
    int i;
    /* Need to free all binaries */
    for (i = 1; i < ev->vsize; i++)
	if (ev->binv[i])
	    driver_free_binary(ev->binv[i]);
    if (cbinp)
	driver_free_binary(cbinp);
    erts_free(ERTS_ALC_T_DRV_CMD_DATA, ev);
}

static int
port_sig_outputv(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    Eterm reply;

    switch (op) {
    case ERTS_PROC2PORT_SIG_EXEC:
	/* Execution of a scheduled outputv() call */

	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

	if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
	    reply = am_badarg;
	else {
	    call_driver_outputv(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
				sigdp->caller,
				sigdp->u.outputv.from,
				prt,
				prt->drv_ptr,
				sigdp->u.outputv.evp);
	    reply = am_true;
	}
	break;
    case ERTS_PROC2PORT_SIG_ABORT_NOSUSPEND:
	reply = am_false;
	break;
    default:
	reply = am_badarg;
	break;
    }

    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, reply);

    cleanup_scheduled_outputv(sigdp->u.outputv.evp,
			      sigdp->u.outputv.cbinp);

    return ERTS_PORT_REDS_CMD_OUTPUTV;
}

/*
 * Driver output() callback
 */

static ERTS_INLINE void
call_driver_output(int bang_op,
		   Eterm caller,
		   Eterm from,
		   Port *prt,
		   erts_driver_t *drv,
		   char *bufp,
		   ErlDrvSizeT size)
{
    /*
     * if (bang_op)
     *   we are part of a "Prt ! {From, {command, Data}}" operation
     * else
     *   we are part of a call to port_command/[2,3]
     * behave accordingly...
     */
    if (bang_op && from != ERTS_PORT_GET_CONNECTED(prt))
	send_badsig(prt);
    else {

#ifdef USE_VM_PROBES
	if (DTRACE_ENABLED(driver_output)) {
	    DTRACE_FORMAT_COMMON_PID_AND_PORT(caller, prt);
	    DTRACE4(driver_output, process_str, port_str, prt->name, size);
	}
#endif

	prt->caller = caller;
	(*drv->output)((ErlDrvData) prt->drv_data, bufp, size);
	prt->caller = NIL;

	prt->bytes_out += size;
	erts_smp_atomic_add_nob(&erts_bytes_out, size);
    }
}

static ERTS_INLINE void
cleanup_scheduled_output(char *bufp)
{
    erts_free(ERTS_ALC_T_DRV_CMD_DATA, bufp);
}

static int
port_sig_output(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    Eterm reply;

    switch (op) {
    case ERTS_PROC2PORT_SIG_EXEC:
	/* Execution of a scheduled output() call */

	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

	if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
	    reply = am_badarg;
	else {
	    call_driver_output(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
			       sigdp->caller,
			       sigdp->u.output.from,
			       prt,
			       prt->drv_ptr,
			       sigdp->u.output.bufp,
			       sigdp->u.output.size);
	    reply = am_true;
	}
	break;
    case ERTS_PROC2PORT_SIG_ABORT_NOSUSPEND:
	reply = am_false;
	break;
    default:
	reply = am_badarg;
	break;
    }

    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, reply);

    cleanup_scheduled_output(sigdp->u.output.bufp);

    return ERTS_PORT_REDS_CMD_OUTPUT;
}

ErtsPortOpResult
erts_port_output(Process *c_p,
		 int flags,
		 Port *prt,
		 Eterm from,
		 Eterm list,
		 Eterm *refp)
{
    ErtsPortOpResult res;
    ErtsProc2PortSigData *sigdp;
    erts_driver_t *drv = prt->drv_ptr;
    size_t size;
    int try_call;
    erts_aint32_t sched_flags, busy_flag, invalid_flags;
    int task_flags;
    ErtsProc2PortSigCallback port_sig_callback;
    ErlDrvBinary *cbin = NULL;
    ErlIOVec *evp = NULL;
    char *buf = NULL;

    ASSERT((flags & ~(ERTS_PORT_SIG_FLG_BANG_OP
		      | ERTS_PORT_SIG_FLG_NOSUSPEND
		      | ERTS_PORT_SIG_FLG_FORCE)) == 0);

    busy_flag = ((flags & ERTS_PORT_SIG_FLG_FORCE)
		 ? ((erts_aint32_t) 0)
		 : ERTS_PTS_FLG_BUSY);
    invalid_flags = busy_flag;
    if (!refp)
	invalid_flags |= ERTS_PTS_FLG_PARALLELISM;

    /*
     * Assumes caller have checked that port is valid...
     */

    sched_flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
    if (sched_flags & (busy_flag|ERTS_PTS_FLG_EXIT))
	return ((sched_flags & busy_flag)
		? ERTS_PORT_OP_BUSY
		: ERTS_PORT_OP_DROPPED);

    try_call = !(sched_flags & (invalid_flags|ERTS_PTS_FLGS_FORCE_SCHEDULE_OP));

#ifdef USE_VM_PROBES
    if(DTRACE_ENABLED(port_command)) {
	DTRACE_FORMAT_COMMON_PID_AND_PORT(c_p->common.id, prt);
	DTRACE4(port_command, process_str, port_str, prt->name, "command");
    }
#endif

    if (drv->outputv) {
	ErlIOVec ev;
	SysIOVec iv[SMALL_WRITE_VEC];
	ErlDrvBinary* bv[SMALL_WRITE_VEC];
	SysIOVec* ivp;
	ErlDrvBinary**  bvp;
	int vsize;
	Uint csize;
	Uint pvsize;
	Uint pcsize;
	Uint blimit;
	size_t iov_offset, binv_offset, alloc_size;

	if (io_list_vec_len(list, &vsize, &csize, &pvsize, &pcsize, &size))
	    goto bad_value;

	iov_offset = ERTS_ALC_DATA_ALIGN_SIZE(sizeof(ErlIOVec));
	binv_offset = iov_offset;
	binv_offset += ERTS_ALC_DATA_ALIGN_SIZE((vsize+1)*sizeof(SysIOVec));
	alloc_size = binv_offset;
	alloc_size += (vsize+1)*sizeof(ErlDrvBinary *);

	if (try_call && vsize < SMALL_WRITE_VEC) {
	    ivp = ev.iov = iv;
	    bvp = ev.binv = bv;
	    evp = &ev;
	}
	else {
	    char *ptr = erts_alloc((try_call
				    ? ERTS_ALC_T_TMP
				    : ERTS_ALC_T_DRV_CMD_DATA), alloc_size);

	    evp = (ErlIOVec *) ptr;
	    ivp = evp->iov = (SysIOVec *) (ptr + iov_offset);
	    bvp = evp->binv = (ErlDrvBinary **) (ptr + binv_offset);
	}

	/* To pack or not to pack (small binaries) ...? */
	if (vsize < SMALL_WRITE_VEC) {
	    /* Do NOT pack */
	    blimit = 0;
	}
	else {
	    /* Do pack */
	    vsize = pvsize + 1;
	    csize = pcsize;
	    blimit = ERL_SMALL_IO_BIN_LIMIT;
	}
	/* Use vsize and csize from now on */

	cbin = driver_alloc_binary(csize);
	if (!cbin)
	    erts_alloc_enomem(ERTS_ALC_T_DRV_BINARY, ERTS_SIZEOF_Binary(csize));

	/* Element 0 is for driver usage to add header block */
	ivp[0].iov_base = NULL;
	ivp[0].iov_len = 0;
	bvp[0] = NULL;
	evp->vsize = io_list_to_vec(list, ivp+1, bvp+1, cbin, blimit);
	if (evp->vsize < 0) {
	    if (evp != &ev)
		erts_free(try_call ? ERTS_ALC_T_TMP : ERTS_ALC_T_DRV_CMD_DATA,
			  evp);
	    driver_free_binary(cbin);
	    goto bad_value;
	}
#if 0
	/* This assertion may say something useful, but it can
	   be falsified during the emulator test suites. */
	ASSERT(evp->vsize == vsize);
#endif
	evp->vsize++;
	evp->size = size;  /* total size */

	if (!try_call) {
	    int i;
	    /* Need to increase refc on all binaries */
	    for (i = 1; i < evp->vsize; i++)
		if (bvp[i])
		    driver_binary_inc_refc(bvp[i]);
	}
	else {
	    int i;
	    ErlIOVec *new_evp;
	    ErtsTryImmDrvCallResult try_call_res;
	    ErtsTryImmDrvCallState try_call_state
		= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
		    c_p,
		    prt,
		    ERTS_PORT_SFLGS_INVALID_LOOKUP,
		    invalid_flags,
		    !refp,
		    am_command);
	    try_call_state.pre_chk_sched_flags = 0; /* already checked */
	    try_call_res = try_imm_drv_call(&try_call_state);
	    switch (try_call_res) {
	    case ERTS_TRY_IMM_DRV_CALL_OK:
		call_driver_outputv(flags & ERTS_PORT_SIG_FLG_BANG_OP,
				    c_p->common.id,
				    from,
				    prt,
				    drv,
				    evp);
		finalize_imm_drv_call(&try_call_state);
		/* Fall through... */
	    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
		driver_free_binary(cbin);
		if (evp != &ev)
		    erts_free(ERTS_ALC_T_TMP, evp);
		if (try_call_res == ERTS_TRY_IMM_DRV_CALL_OK)
		    return ERTS_PORT_OP_DONE;
		else
		    return ERTS_PORT_OP_DROPPED;
	    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
		sched_flags = try_call_state.sched_flags;
	    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
		/* Schedule outputv() call instead... */
		break;
	    }

	    /* Need to increase refc on all binaries */
	    for (i = 1; i < evp->vsize; i++)
		if (bvp[i])
		    driver_binary_inc_refc(bvp[i]);

	    new_evp = erts_alloc(ERTS_ALC_T_DRV_CMD_DATA, alloc_size);

	    if (evp != &ev) {
		sys_memcpy((void *) new_evp, (void *) evp, alloc_size);
		new_evp->iov = (SysIOVec *) (((char *) new_evp)
					     + iov_offset);
		bvp = new_evp->binv = (ErlDrvBinary **) (((char *) new_evp)
							 + binv_offset);

#ifdef DEBUG
		ASSERT(new_evp->vsize == evp->vsize);
		ASSERT(new_evp->size == evp->size);
		for (i = 0; i < evp->vsize; i++) {
		    ASSERT(new_evp->iov[i].iov_len == evp->iov[i].iov_len);
		    ASSERT(new_evp->iov[i].iov_base == evp->iov[i].iov_base);
		    ASSERT(new_evp->binv[i] == evp->binv[i]);
		}
#endif

		erts_free(ERTS_ALC_T_TMP, evp);
	    }
	    else { /* from stack allocated structure; offsets may differ */

		sys_memcpy((void *) new_evp, (void *) evp, sizeof(ErlIOVec));
		new_evp->iov = (SysIOVec *) (((char *) new_evp)
					     + iov_offset);
		sys_memcpy((void *) new_evp->iov,
			   (void *) evp->iov,
			   evp->vsize * sizeof(SysIOVec));
		new_evp->binv = (ErlDrvBinary **) (((char *) new_evp)
						   + binv_offset);
		sys_memcpy((void *) new_evp->binv,
			   (void *) evp->binv,
			   evp->vsize * sizeof(ErlDrvBinary *));

#ifdef DEBUG
		ASSERT(new_evp->vsize == evp->vsize);
		ASSERT(new_evp->size == evp->size);
		for (i = 0; i < evp->vsize; i++) {
		    ASSERT(new_evp->iov[i].iov_len == evp->iov[i].iov_len);
		    ASSERT(new_evp->iov[i].iov_base == evp->iov[i].iov_base);
		    ASSERT(new_evp->binv[i] == evp->binv[i]);
		}
#endif

	    }

	    evp = new_evp;
	}

	sigdp = erts_port_task_alloc_p2p_sig_data();
	sigdp->u.outputv.from = from;
	sigdp->u.outputv.evp = evp;
	sigdp->u.outputv.cbinp = cbin;
	port_sig_callback = port_sig_outputv;
    }
    else {
	ErlDrvSizeT r;

	/*
	 * Apperently there exist code that write 1 byte to
	 * much in buffer. Where it resides I don't know, but
	 * we can live with one byte extra allocated...
	 */

	if (!try_call) {
	    if (erts_iolist_size(list, &size))
		goto bad_value;

	    buf = erts_alloc(ERTS_ALC_T_DRV_CMD_DATA, size + 1);

	    r = erts_iolist_to_buf(list, buf, size);
	    ASSERT(ERTS_IOLIST_TO_BUF_SUCCEEDED(r));
	}
	else {
	    char *new_buf;
	    ErtsTryImmDrvCallResult try_call_res;
	    ErtsTryImmDrvCallState try_call_state
		= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
		    c_p,
		    prt,
		    ERTS_PORT_SFLGS_INVALID_LOOKUP,
		    invalid_flags,
		    !refp,
		    am_command);

	    /* Try with an 8KB buffer first (will often be enough I guess). */
	    size = 8*1024;

	    buf = erts_alloc(ERTS_ALC_T_TMP, size + 1);
	    r = erts_iolist_to_buf(list, buf, size);

	    if (ERTS_IOLIST_TO_BUF_SUCCEEDED(r)) {
		ASSERT(r <= size);
		size -= r;
	    }
	    else {
		erts_free(ERTS_ALC_T_TMP, buf);
		if (r == ERTS_IOLIST_TO_BUF_TYPE_ERROR)
		    goto bad_value;
		ASSERT(r == ERTS_IOLIST_TO_BUF_OVERFLOW);
		if (erts_iolist_size(list, &size))
		    goto bad_value;
		buf = erts_alloc(ERTS_ALC_T_TMP, size + 1); 
		r = erts_iolist_to_buf(list, buf, size);
		ASSERT(ERTS_IOLIST_TO_BUF_SUCCEEDED(r));
	    }
	    try_call_state.pre_chk_sched_flags = 0; /* already checked */
	    try_call_res = try_imm_drv_call(&try_call_state);
	    switch (try_call_res) {
	    case ERTS_TRY_IMM_DRV_CALL_OK:
		call_driver_output(flags & ERTS_PORT_SIG_FLG_BANG_OP,
				   c_p->common.id,
				   from,
				   prt,
				   drv,
				   buf,
				   size);
		finalize_imm_drv_call(&try_call_state);
		/* Fall through... */
	    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
		erts_free(ERTS_ALC_T_TMP, buf);
		if (try_call_res == ERTS_TRY_IMM_DRV_CALL_OK)
		    return ERTS_PORT_OP_DONE;
		else
		    return ERTS_PORT_OP_DROPPED;
	    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
		sched_flags = try_call_state.sched_flags;
	    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
		/* Schedule outputv() call instead... */
		break;
	    }

	    new_buf = erts_alloc(ERTS_ALC_T_DRV_CMD_DATA, size + 1);
	    sys_memcpy(new_buf, buf, size);
	    erts_free(ERTS_ALC_T_TMP, buf);
	    buf = new_buf;
	}

	sigdp = erts_port_task_alloc_p2p_sig_data();
	sigdp->u.output.from = from;
	sigdp->u.output.bufp = buf;
	sigdp->u.output.size = size;
	port_sig_callback = port_sig_output;
    }

    task_flags = ERTS_PT_FLG_WAIT_BUSY;
    sigdp->flags = flags;
    if (flags & (ERTS_P2P_SIG_DATA_FLG_FORCE|ERTS_P2P_SIG_DATA_FLG_NOSUSPEND)) {
	task_flags = 0;
	if (flags & ERTS_P2P_SIG_DATA_FLG_FORCE)
	    sigdp->flags &= ~ERTS_P2P_SIG_DATA_FLG_NOSUSPEND;
	else if (flags & ERTS_P2P_SIG_DATA_FLG_NOSUSPEND)
	    task_flags = ERTS_PT_FLG_NOSUSPEND;
    }

    res = erts_schedule_proc2port_signal(c_p,
					 prt,
					 c_p->common.id,
					 refp,
					 sigdp,
					 task_flags,
					 port_sig_callback);

    if (res != ERTS_PORT_OP_SCHEDULED) {
	if (drv->outputv)
	    cleanup_scheduled_outputv(evp, cbin);
	else
	    cleanup_scheduled_output(buf);
	return res;
    }

    if ((sched_flags & (busy_flag|ERTS_PTS_FLG_EXIT)) == ERTS_PTS_FLG_BUSY)
	return ERTS_PORT_OP_BUSY_SCHEDULED;

    return res;

bad_value:

    flags |= ERTS_PORT_SIG_FLG_BAD_OUTPUT;
    return bad_port_signal(c_p, flags, prt, from, refp, am_command);
}

static ERTS_INLINE ErtsPortOpResult
call_deliver_port_exit(int bang_op,
		       Eterm from,
		       Port *prt,
		       erts_aint32_t state,
		       Eterm reason,
		       int broken_link)
{
    /*
     * if (bang_op)
     *   we are part of a "Prt ! {From, close}" operation
     * else
     *   we are part of a call to port_close(Port)
     * behave accordingly...
     */

    if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
	return ERTS_PORT_OP_DROPPED;

    if (bang_op && from != ERTS_PORT_GET_CONNECTED(prt)) {
	send_badsig(prt);
	return ERTS_PORT_OP_DROPPED;
    }

    if (broken_link) {
	ErtsLink *lnk = erts_remove_link(&ERTS_P_LINKS(prt), from);
	if (lnk)
	    erts_destroy_link(lnk);
	else
	    return ERTS_PORT_OP_DROPPED;
    }

    if (!erts_deliver_port_exit(prt, from, reason, bang_op))
	return ERTS_PORT_OP_DROPPED;

#ifdef USE_VM_PROBES
    if(DTRACE_ENABLED(port_command) && bang_op) {
	DTRACE_FORMAT_COMMON_PID_AND_PORT(from, prt);
	DTRACE4(port_command, process_str, port_str, prt->name, "close");
    }
#endif

    return ERTS_PORT_OP_DONE;
}

static int
port_sig_exit(Port *prt,
	      erts_aint32_t state,
	      int op,
	      ErtsProc2PortSigData *sigdp)
{
    Eterm msg = am_badarg;
    if (op == ERTS_PROC2PORT_SIG_EXEC) {
	ErtsPortOpResult res;
	int bang_op = sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP;
	int broken_link = sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BROKEN_LINK;
	res = call_deliver_port_exit(bang_op,
				     sigdp->u.exit.from,
				     prt,
				     state,
				     sigdp->u.exit.reason,
				     broken_link);

	if (res == ERTS_PORT_OP_DONE)
	    msg = am_true;
    }
    if (sigdp->u.exit.bp)
	free_message_buffer(sigdp->u.exit.bp);
    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, msg);

    return ERTS_PORT_REDS_EXIT;
}

ErtsPortOpResult
erts_port_exit(Process *c_p,
	       int flags,
	       Port *prt,
	       Eterm from,
	       Eterm reason,
	       Eterm *refp)
{
    ErtsPortOpResult res;
    ErtsProc2PortSigData *sigdp;
    ErlHeapFragment *bp = NULL;

    ASSERT((flags & ~(ERTS_PORT_SIG_FLG_BANG_OP
		      | ERTS_PORT_SIG_FLG_BROKEN_LINK
		      | ERTS_PORT_SIG_FLG_FORCE_SCHED)) == 0);

    if (!(flags & ERTS_PORT_SIG_FLG_FORCE_SCHED)) {
	ErtsTryImmDrvCallState try_call_state
	    = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(c_p,
					       prt,
					       ERTS_PORT_SFLGS_INVALID_LOOKUP,
					       0,
					       !refp,
					       am_exit);


	switch (try_imm_drv_call(&try_call_state)) {
	case ERTS_TRY_IMM_DRV_CALL_OK: {
	    res = call_deliver_port_exit(flags & ERTS_PORT_SIG_FLG_BANG_OP,
					 from,
					 prt,
					 try_call_state.state,
					 reason,
					 flags & ERTS_PORT_SIG_FLG_BROKEN_LINK);
	    finalize_imm_drv_call(&try_call_state);
	    return res;
	}
	case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	    return ERTS_PORT_OP_DROPPED;
	default:
	    /* Schedule call instead... */
	    break;
	}
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = flags;
    sigdp->u.exit.from = from;

    if (is_immed(reason)) {
	sigdp->u.exit.reason = reason;
	sigdp->u.exit.bp = NULL;
    }
    else {
	Eterm *hp;
	Uint hsz = size_object(reason);
	bp = new_message_buffer(hsz);
	sigdp->u.exit.bp = bp;
	hp = bp->mem;
	sigdp->u.exit.reason = copy_struct(reason,
					   hsz,
					   &hp,
					   &bp->off_heap);
    }

    res = erts_schedule_proc2port_signal(c_p,
					 prt,
					 c_p ? c_p->common.id : from,
					 refp,
					 sigdp,
					 0,
					 port_sig_exit);

    if (res == ERTS_PORT_OP_DROPPED) {
	if (bp)
	    free_message_buffer(bp);
    }

    return res;
}

static ErtsPortOpResult
set_port_connected(int bang_op,
		   Eterm from,
		   Port *prt,
		   erts_aint32_t state,
		   Eterm connect)
{
    /*
     * if (bang_op)
     *   we are part of a "Prt ! {From, {connect, Connect}}" operation
     * else
     *   we are part of a call to port_connect(Port, Connect)
     * behave accordingly...
     */

    if (state & ERTS_PORT_SFLGS_INVALID_LOOKUP)
	return ERTS_PORT_OP_DROPPED;

    if (bang_op) { /* Bang operation */
	if (is_not_internal_pid(connect) || ERTS_PORT_GET_CONNECTED(prt) != from) {
	    send_badsig(prt);
	    return ERTS_PORT_OP_DROPPED;
	}

	ERTS_PORT_SET_CONNECTED(prt, connect);
	deliver_result(prt->common.id, from, am_connected);

#ifdef USE_VM_PROBES
	if(DTRACE_ENABLED(port_command)) {
	    DTRACE_FORMAT_COMMON_PID_AND_PORT(from, prt);
	    DTRACE4(port_command, process_str, port_str, prt->name, "connect");
	}
#endif
    }
    else { /* Port BIF operation */
	Process *rp = erts_proc_lookup_raw(connect);
	if (!rp)
	    return ERTS_PORT_OP_DROPPED;
	erts_smp_proc_lock(rp, ERTS_PROC_LOCK_LINK);
	if (ERTS_PROC_IS_EXITING(rp)) {
	    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
	    return ERTS_PORT_OP_DROPPED;
	}

	erts_add_link(&ERTS_P_LINKS(rp), LINK_PID, prt->common.id);
	erts_add_link(&ERTS_P_LINKS(prt), LINK_PID, connect);

	ERTS_PORT_SET_CONNECTED(prt, connect);

	erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);

#ifdef USE_VM_PROBES
	if (DTRACE_ENABLED(port_connect)) {
	    DTRACE_CHARBUF(process_str, DTRACE_TERM_BUF_SIZE);
	    DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);
	    DTRACE_CHARBUF(newprocess_str, DTRACE_TERM_BUF_SIZE);

	    dtrace_pid_str(connect, process_str);
	    erts_snprintf(port_str, sizeof(port_str), "%T", prt->common.id);
	    dtrace_proc_str(rp, newprocess_str);
	    DTRACE4(port_connect, process_str, port_str, prt->name, newprocess_str);
	}
#endif
    }

    return ERTS_PORT_OP_DONE;
}

static int
port_sig_connect(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    Eterm msg = am_badarg;
    if (op == ERTS_PROC2PORT_SIG_EXEC) {
	ErtsPortOpResult res;
	res = set_port_connected(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_BANG_OP,
				 sigdp->u.connect.from,
				 prt,
				 state,
				 sigdp->u.connect.connected);
	if (res == ERTS_PORT_OP_DONE)
	    msg = am_true;
    }
    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, msg);
    return ERTS_PORT_REDS_CONNECT;
}

ErtsPortOpResult
erts_port_connect(Process *c_p,
		  int flags,
		  Port *prt,
		  Eterm from,
		  Eterm connect,
		  Eterm *refp)
{
    ErtsProc2PortSigData *sigdp;
    Eterm connect_id;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(c_p,
					   prt,
					   ERTS_PORT_SFLGS_INVALID_LOOKUP,
					   0,
					   !refp,
					   am_connect);

    ASSERT((flags & ~ERTS_PORT_SIG_FLG_BANG_OP) == 0);

    if (is_not_internal_pid(connect))
	connect_id = NIL; /* Fail in op (for signal order) */
    else
	connect_id = connect;

    switch (try_imm_drv_call(&try_call_state)) {
    case ERTS_TRY_IMM_DRV_CALL_OK: {
	ErtsPortOpResult res;
	res = set_port_connected(flags & ERTS_PORT_SIG_FLG_BANG_OP,
				 from,
				 prt,
				 try_call_state.state,
				 connect_id);
	finalize_imm_drv_call(&try_call_state);
	return res;
    }
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    default:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = flags;

    sigdp->u.connect.from = from;
    sigdp->u.connect.connected = connect_id;
    
    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p->common.id,
					  refp,
					  sigdp,
					  0,
					  port_sig_connect);
}

static void
port_unlink(Port *prt, Eterm from)
{
    ErtsLink *lnk = erts_remove_link(&ERTS_P_LINKS(prt), from);
    if (lnk)
	erts_destroy_link(lnk);
}

static int
port_sig_unlink(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    if (op == ERTS_PROC2PORT_SIG_EXEC)
	port_unlink(prt, sigdp->u.unlink.from);
    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_true);
    return ERTS_PORT_REDS_UNLINK;
}

ErtsPortOpResult
erts_port_unlink(Process *c_p, Port *prt, Eterm from, Eterm *refp)
{
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(c_p,
					   prt,
					   ERTS_PORT_SFLGS_DEAD,
					   0,
					   !refp,
					   am_unlink);

    switch (try_imm_drv_call(&try_call_state)) {
    case ERTS_TRY_IMM_DRV_CALL_OK:
	port_unlink(prt, from);
	finalize_imm_drv_call(&try_call_state);
	return ERTS_PORT_OP_DONE;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    default:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.unlink.from = from;
    
    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p ? c_p->common.id : from,
					  refp,
					  sigdp,
					  0,
					  port_sig_unlink);
}

static void
port_link_failure(Eterm port_id, Eterm linker)
{
    Process *rp;
    ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCKS_XSIG_SEND;
    ASSERT(is_internal_pid(linker));
    rp = erts_pid2proc(NULL, 0, linker, rp_locks);
    if (rp) {
	ErtsLink *rlnk = erts_remove_link(&ERTS_P_LINKS(rp), port_id);
	if (rlnk) {
	    int xres = erts_send_exit_signal(NULL,
					     port_id,
					     rp,
					     &rp_locks, 
					     am_noproc,
					     NIL,
					     NULL,
					     0);
	    if (xres >= 0 && IS_TRACED_FL(rp, F_TRACE_PROCS)) {
		/* We didn't exit the process and it is traced */
		if (IS_TRACED_FL(rp, F_TRACE_PROCS))
		    trace_proc(NULL, rp, am_getting_unlinked, port_id);
	    }
	}
    }
}

static void
port_link(Port *prt, erts_aint32_t state, Eterm to)
{
    if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP))
	erts_add_link(&ERTS_P_LINKS(prt), LINK_PID, to);
    else
	port_link_failure(prt->common.id, to);
}

static int
port_sig_link(Port *prt, erts_aint32_t state, int op, ErtsProc2PortSigData *sigdp)
{
    if (op == ERTS_PROC2PORT_SIG_EXEC)
	port_link(prt, state, sigdp->u.link.to);
    else
	port_link_failure(sigdp->u.link.port, sigdp->u.link.to);
    if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY)
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_true);
    return ERTS_PORT_REDS_LINK;
}

ErtsPortOpResult
erts_port_link(Process *c_p, Port *prt, Eterm to, Eterm *refp)
{
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(c_p,
					   prt,
					   ERTS_PORT_SFLGS_INVALID_LOOKUP,
					   0,
					   !refp,
					   am_link);

    switch (try_imm_drv_call(&try_call_state)) {
    case ERTS_TRY_IMM_DRV_CALL_OK:
	port_link(prt, try_call_state.state, to);
	finalize_imm_drv_call(&try_call_state);
	return ERTS_PORT_OP_DONE;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_BADARG;
    default:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.link.port = prt->common.id;
    sigdp->u.link.to = to;
    
    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p ? c_p->common.id : to,
					  refp,
					  sigdp,
					  0,
					  port_sig_link);
}

void erts_init_io(int port_tab_size,
		  int port_tab_size_ignore_files)
{
    ErlDrvEntry** dp;
    erts_smp_rwmtx_opt_t drv_list_rwmtx_opts = ERTS_SMP_RWMTX_OPT_DEFAULT_INITER;
    drv_list_rwmtx_opts.type = ERTS_SMP_RWMTX_TYPE_EXTREMELY_FREQUENT_READ;
    drv_list_rwmtx_opts.lived = ERTS_SMP_RWMTX_LONG_LIVED;

#ifdef ERTS_SMP
    init_xports_list_alloc();
#endif

    pdl_init();

    if (!port_tab_size_ignore_files) {
	int max_files = sys_max_files();
	if (port_tab_size < max_files)
	    port_tab_size = max_files;
    }

    if (port_tab_size > ERTS_MAX_PORTS)
	port_tab_size = ERTS_MAX_PORTS;
    else if (port_tab_size < ERTS_MIN_PORTS)
	port_tab_size = ERTS_MIN_PORTS;

    erts_smp_rwmtx_init_opt(&erts_driver_list_lock,
			    &drv_list_rwmtx_opts,
			    "driver_list");
    driver_list = NULL;
    erts_smp_tsd_key_create(&driver_list_lock_status_key);
    erts_smp_tsd_key_create(&driver_list_last_error_key);

    erts_ptab_init_table(&erts_port,
			 ERTS_ALC_T_PORT_TABLE,
			 NULL,
			 (ErtsPTabElementCommon *) &erts_invalid_port.common,
			 port_tab_size,
			 "port_table");

    erts_smp_atomic_init_nob(&erts_bytes_out, 0);
    erts_smp_atomic_init_nob(&erts_bytes_in, 0);

    sys_init_io();

    erts_smp_tsd_set(driver_list_lock_status_key, (void *) 1);
    erts_smp_rwmtx_rwlock(&erts_driver_list_lock);

    init_driver(&fd_driver, &fd_driver_entry, NULL);
    init_driver(&vanilla_driver, &vanilla_driver_entry, NULL);
    init_driver(&spawn_driver, &spawn_driver_entry, NULL);
    for (dp = driver_tab; *dp != NULL; dp++)
	erts_add_driver_entry(*dp, NULL, 1);

    erts_smp_tsd_set(driver_list_lock_status_key, NULL);
    erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
}

/*
 * Buffering of data when using line oriented I/O on ports
 */

/* 
 * Buffer states 
 */
#define LINEBUF_MAIN 0
#define LINEBUF_FULL 1
#define LINEBUF_CR_INSIDE 2
#define LINEBUF_CR_AFTER 3

/*
 * Creates a LineBuf to be added to the port structure,
 * Returns: Pointer to a newly allocated and initialized LineBuf.
 * Parameters:
 * bufsiz - The (maximum) size of the line buffer.
 */
LineBuf *allocate_linebuf(bufsiz)
int bufsiz;
{
    int ovsiz = (bufsiz < LINEBUF_INITIAL) ? bufsiz : LINEBUF_INITIAL;
    LineBuf *lb = (LineBuf *) erts_alloc(ERTS_ALC_T_LINEBUF,
					 sizeof(LineBuf)+ovsiz);
    lb->ovsiz = ovsiz;
    lb->bufsiz = bufsiz;
    lb->ovlen = 0;
    lb->data[0] = LINEBUF_MAIN; /* state */
    return lb;
}

/*
 * Initializes a LineBufContext to be used in calls to read_linebuf
 * or flush_linebuf.
 * Returns: 0 if ok, <0 on error.
 * Parameters:
 * lc - Pointer to an allocated LineBufContext.
 * lb - Pointer to a LineBuf structure (probably from the Port structure).
 * buf - A buffer containing the data to be read and split to lines.
 * len - The number of bytes in buf.
 */
static int init_linebuf_context(LineBufContext *lc, LineBuf **lb,
				char *buf, ErlDrvSizeT len)
{
    if(lc == NULL || lb == NULL)
	return -1;
    lc->b = lb;
    lc->buf = buf;
    lc->left = len;
    return 0;
}

static void resize_linebuf(LineBuf **b)
{
    int newsiz = (((*b)->ovsiz * 2) > (*b)->bufsiz) ? (*b)->bufsiz : 
	(*b)->ovsiz * 2;
    *b = (LineBuf *) erts_realloc(ERTS_ALC_T_LINEBUF,
				  (void *) *b,
				  sizeof(LineBuf)+newsiz);
    (*b)->ovsiz = newsiz;
}

/*
 * Delivers all data in the buffer regardless of newlines (always
 * an LINEBUF_NOEOL. Has to be called until it return LINEBUF_EMPTY.
 * Return values and barameters as read_linebuf (see below).
 */
static int flush_linebuf(LineBufContext *bp)
{
    bp->retlen = (*bp->b)->ovlen;
    switch(LINEBUF_STATE(*bp)){
    case LINEBUF_CR_INSIDE:
	if((*bp->b)->ovlen >= (*bp->b)->ovsiz)
	    resize_linebuf(bp->b);
	LINEBUF_DATA(*bp)[((*bp->b)->ovlen)++] = '\r';
	++bp->retlen; /* fall through instead of switching state... */
    case LINEBUF_MAIN:
    case LINEBUF_FULL:
	(*bp->b)->ovlen = 0;
	LINEBUF_STATE(*bp) = LINEBUF_MAIN;
	if(!bp->retlen)
	    return LINEBUF_EMPTY;
	return LINEBUF_NOEOL;
    case LINEBUF_CR_AFTER:
	LINEBUF_STATE(*bp) = LINEBUF_CR_INSIDE;
	(*bp->b)->ovlen = 0;
	if(!bp->retlen)
	    return LINEBUF_EMPTY;
	return LINEBUF_NOEOL;
    default:
	return LINEBUF_ERROR;
    }    
}

/*
 * Reads input from a buffer and "chops" it up in lines.
 * Has to be called repeatedly until it returns LINEBUF_EMPTY
 * to get all lines in buffer.
 * Handles both <LF> and <CR><LF> style newlines.
 * On Unix, this is slightly incorrect, as <CR><LF> is NOT to be regarded
 * as a newline together, but i treat newlines equally in all systems
 * to avoid putting this in sys.c or clutter it with #ifdef's.
 * Returns: LINEBUF_EMPTY if there is no more data that can be
 *   determined as a line (only part of a line left), LINEBUF_EOL if a whole
 *   line could be delivered and LINEBUF_NOEOL if the buffer size has been
 *   exceeded. The data and the data length can be accesed through the 
 *   LINEBUF_DATA and the LINEBUF_DATALEN macros applied to the LineBufContext.
 * Parameters: 
 * bp - A LineBufContext that is initialized with 
 *   the init_linebuf_context call. The context has to be retained during
 *   all calls that returns other than LINEBUF_EMPTY. When LINEBUF_EMPTY
 *   is returned the context can be discarded and a new can be created when new
 *   data arrives (the state is saved in the Port structure).
 */
static int read_linebuf(LineBufContext *bp)
{
    for(;;){
	if(bp->left == 0)
	    return LINEBUF_EMPTY;
	if(*bp->buf == '\n'){
	    LINEBUF_STATE(*bp) = LINEBUF_MAIN;
	    ++(bp->buf);
	    --(bp->left);
	    bp->retlen = (*bp->b)->ovlen;
	    (*bp->b)->ovlen = 0;
	    return LINEBUF_EOL;
	}
	switch(LINEBUF_STATE(*bp)){
	case LINEBUF_MAIN:
	    if((*bp->b)->ovlen == (*bp->b)->bufsiz)
		LINEBUF_STATE(*bp) = LINEBUF_FULL;
	    else if(*bp->buf == '\r'){
		++(bp->buf);
		--(bp->left);
		LINEBUF_STATE(*bp) = LINEBUF_CR_INSIDE;
	    } else {
		if((*bp->b)->ovlen >= (*bp->b)->ovsiz)
		    resize_linebuf(bp->b);
		LINEBUF_DATA(*bp)[((*bp->b)->ovlen)++] = *((bp->buf)++);
		--(bp->left);
	    }
	    continue;
	case LINEBUF_FULL:
	    if(*bp->buf == '\r'){
		++(bp->buf);
		--(bp->left);
		LINEBUF_STATE(*bp) = LINEBUF_CR_AFTER;
	    } else {
		bp->retlen = (*bp->b)->ovlen;
		(*bp->b)->ovlen = 0;
		LINEBUF_STATE(*bp) = LINEBUF_MAIN;
		return LINEBUF_NOEOL;
	    }
	    continue;
	case LINEBUF_CR_INSIDE:
	    if((*bp->b)->ovlen >= (*bp->b)->ovsiz)
		resize_linebuf(bp->b);
	    LINEBUF_DATA(*bp)[((*bp->b)->ovlen)++] = '\r';
	    LINEBUF_STATE(*bp) = LINEBUF_MAIN;
	    continue;
	case LINEBUF_CR_AFTER:
	    bp->retlen = (*bp->b)->ovlen;
	    (*bp->b)->ovlen = 0;
	    LINEBUF_STATE(*bp) = LINEBUF_CR_INSIDE;
	    return LINEBUF_NOEOL;
	default:
	    return LINEBUF_ERROR;
	}
    }
}

static void
deliver_result(Eterm sender, Eterm pid, Eterm res)
{
    Process *rp;
    ErtsProcLocks rp_locks = 0;
    int scheduler = erts_get_scheduler_id() != 0;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    ASSERT(is_internal_port(sender) && is_internal_pid(pid));

    rp = (scheduler
	  ? erts_proc_lookup(pid)
	  : erts_pid2proc_opt(NULL, 0, pid, 0, ERTS_P2P_FLG_SMP_INC_REFC));

    if (rp) {
	Eterm tuple;
	ErlHeapFragment *bp;
	ErlOffHeap *ohp;
	Eterm* hp;
	Uint sz_res;

	sz_res = size_object(res);
	hp = erts_alloc_message_heap(sz_res + 3, &bp, &ohp, rp, &rp_locks);
	res = copy_struct(res, sz_res, &hp, ohp);
	tuple = TUPLE2(hp, sender, res);
	erts_queue_message(rp, &rp_locks, bp, tuple, NIL
#ifdef USE_VM_PROBES
			   , NIL
#endif
			   );

	if (rp_locks)
	    erts_smp_proc_unlock(rp, rp_locks);
	if (!scheduler)
	    erts_smp_proc_dec_refc(rp);

    }
}


/* 
 * Deliver a "read" message.
 * hbuf -- byte that are always formated as a list
 * hlen -- number of byte in header
 * buf  -- data 
 * len  -- length of data
 */

static void deliver_read_message(Port* prt, erts_aint32_t state, Eterm to,
				 char *hbuf, ErlDrvSizeT hlen,
				 char *buf, ErlDrvSizeT len, int eol)
{
    ErlDrvSizeT need;
    Eterm listp;
    Eterm tuple;
    Process* rp;
    Eterm* hp;
    ErlHeapFragment *bp;
    ErlOffHeap *ohp;
    ErtsProcLocks rp_locks = 0;
    int scheduler = erts_get_scheduler_id() != 0;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));    
    ERTS_SMP_CHK_NO_PROC_LOCKS;

    need = 3 + 3 + 2*hlen;

    if (state & ERTS_PORT_SFLG_LINEBUF_IO) {
	need += 3;
    }
    if ((state & ERTS_PORT_SFLG_BINARY_IO) && buf != NULL) {
	need += PROC_BIN_SIZE;
    } else {
	need += 2*len;
    }

    rp = (scheduler
	  ? erts_proc_lookup(to)
	  : erts_pid2proc_opt(NULL, 0, to, 0, ERTS_P2P_FLG_SMP_INC_REFC));

    if (!rp)
	return;

    hp = erts_alloc_message_heap(need, &bp, &ohp, rp, &rp_locks);

    listp = NIL;
    if ((state & ERTS_PORT_SFLG_BINARY_IO) == 0) {
	listp = buf_to_intlist(&hp, buf, len, listp);
    } else if (buf != NULL) {
	ProcBin* pb;
	Binary* bptr;

	bptr = erts_bin_nrml_alloc(len);
	bptr->flags = 0;
	bptr->orig_size = len;
	erts_refc_init(&bptr->refc, 1);
	sys_memcpy(bptr->orig_bytes, buf, len);

	pb = (ProcBin *) hp;
	pb->thing_word = HEADER_PROC_BIN;
	pb->size = len;
	pb->next = ohp->first;
	ohp->first = (struct erl_off_heap_header*)pb;
	pb->val = bptr;
	pb->bytes = (byte*) bptr->orig_bytes;
	pb->flags = 0;
	hp += PROC_BIN_SIZE;

	OH_OVERHEAD(ohp, pb->size / sizeof(Eterm));
	listp = make_binary(pb);
    }

    /* Prepend the header */
    if (hlen > 0) {
	listp = buf_to_intlist(&hp, hbuf, hlen, listp);
    }

    if (state & ERTS_PORT_SFLG_LINEBUF_IO){
	listp = TUPLE2(hp, (eol) ? am_eol : am_noeol, listp); 
	hp += 3;
    }
    tuple = TUPLE2(hp, am_data, listp);
    hp += 3;

    tuple = TUPLE2(hp, prt->common.id, tuple);
    hp += 3;

    erts_queue_message(rp, &rp_locks, bp, tuple, am_undefined
#ifdef USE_VM_PROBES
			   , NIL
#endif
		       );
    if (rp_locks)
	erts_smp_proc_unlock(rp, rp_locks);
    if (!scheduler)
	erts_smp_proc_dec_refc(rp);
}

/* 
 * Deliver all lines in a line buffer, repeats calls to
 * deliver_read_message, and takes the same parameters.
 */
static void deliver_linebuf_message(Port* prt, erts_aint_t state,
				    Eterm to, 
				    char* hbuf, ErlDrvSizeT hlen,
				    char *buf, ErlDrvSizeT len)
{
    LineBufContext lc;
    int ret;
    if(init_linebuf_context(&lc,&(prt->linebuf), buf, len) < 0)
	return;
    while((ret = read_linebuf(&lc)) > LINEBUF_EMPTY)
	deliver_read_message(prt, state, to, hbuf, hlen, LINEBUF_DATA(lc), 
			     LINEBUF_DATALEN(lc), (ret == LINEBUF_EOL));
}

/*
 * Deliver any nonterminated lines in the line buffer before the
 * port gets closed.
 * Has to be called before terminate_port.
 * Parameters:
 * prt -  Pointer to a Port structure for this port.
 */
static void flush_linebuf_messages(Port *prt, erts_aint32_t state)
{
    LineBufContext lc;
    int ret;

    ERTS_SMP_LC_ASSERT(!prt || erts_lc_is_port_locked(prt));

    if (!prt)
	return;

    if (!(state & ERTS_PORT_SFLG_LINEBUF_IO))
	return;

    if(init_linebuf_context(&lc,&(prt->linebuf), NULL, 0) < 0)
	return;
    while((ret = flush_linebuf(&lc)) > LINEBUF_EMPTY)
	deliver_read_message(prt,
			     state,
			     ERTS_PORT_GET_CONNECTED(prt),
			     NULL,
			     0,
			     LINEBUF_DATA(lc), 
			     LINEBUF_DATALEN(lc),
			     (ret == LINEBUF_EOL));
}    

static void
deliver_vec_message(Port* prt,			/* Port */
		    Eterm to,			/* Receiving pid */
		    char* hbuf,			/* "Header" buffer... */
		    ErlDrvSizeT hlen,		/* ... and its length */
		    ErlDrvBinary** binv,	/* Vector of binaries */
		    SysIOVec* iov,		/* I/O vector */
		    int vsize,			/* Size of binv & iov */
		    ErlDrvSizeT csize)		/* Size of characters in
						   iov (not hlen) */
{
    ErlDrvSizeT need;
    Eterm listp;
    Eterm tuple;
    Process* rp;
    Eterm* hp;
    ErlHeapFragment *bp;
    ErlOffHeap *ohp;
    ErtsProcLocks rp_locks = 0;
    int scheduler = erts_get_scheduler_id() != 0;
    erts_aint32_t state;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    ERTS_SMP_CHK_NO_PROC_LOCKS;

    /*
     * Check arguments for validity.
     */


    rp = (scheduler
	  ? erts_proc_lookup(to)
	  : erts_pid2proc_opt(NULL, 0, to, 0, ERTS_P2P_FLG_SMP_INC_REFC));
    if (!rp)
	return;

    state = erts_atomic32_read_nob(&prt->state);
    /*
     * Calculate the exact number of heap words needed.
     */

    need = 3 + 3;		/* Heap space for two tuples */
    if (state & ERTS_PORT_SFLG_BINARY_IO) {
	need += (2+PROC_BIN_SIZE)*vsize - 2 + hlen*2;
    } else {
	need += (hlen+csize)*2;
    }

    hp = erts_alloc_message_heap(need, &bp, &ohp, rp, &rp_locks);

    listp = NIL;
    iov += vsize;

    if ((state & ERTS_PORT_SFLG_BINARY_IO) == 0) {
	Eterm* thp = hp;
	while (vsize--) {
	    iov--;
	    listp = buf_to_intlist(&thp, iov->iov_base, iov->iov_len, listp);
	}
	hp = thp;
    } else {
	binv += vsize;
	while (vsize--) {
	    ErlDrvBinary* b;
	    ProcBin* pb = (ProcBin*) hp;
	    byte* base;

	    iov--;
	    binv--;
	    if ((b = *binv) == NULL) {
		b = driver_alloc_binary(iov->iov_len);
		sys_memcpy(b->orig_bytes, iov->iov_base, iov->iov_len);
		base = (byte*) b->orig_bytes;
	    } else {
		/* Must increment reference count, caller calls free */
		driver_binary_inc_refc(b);
		base = iov->iov_base;
	    }
	    pb->thing_word = HEADER_PROC_BIN;
	    pb->size = iov->iov_len;
	    pb->next = ohp->first;
	    ohp->first = (struct erl_off_heap_header*)pb;
	    pb->val = ErlDrvBinary2Binary(b);
	    pb->bytes = base;
	    pb->flags = 0;
	    hp += PROC_BIN_SIZE;
	    
	    OH_OVERHEAD(ohp, iov->iov_len / sizeof(Eterm));

	    if (listp == NIL) {  /* compatible with deliver_bin_message */
		listp = make_binary(pb);
	    } else {
		listp = CONS(hp, make_binary(pb), listp);
		hp += 2;
	    }
	}
    }

    if (hlen != 0) {		/* Prepend the header */
	Eterm* thp = hp;
	listp = buf_to_intlist(&thp, hbuf, hlen, listp);
	hp = thp;
    }

    tuple = TUPLE2(hp, am_data, listp);
    hp += 3;
    tuple = TUPLE2(hp, prt->common.id, tuple);
    hp += 3;

    erts_queue_message(rp, &rp_locks, bp, tuple, am_undefined
#ifdef USE_VM_PROBES
		       , NIL
#endif
		       );
    erts_smp_proc_unlock(rp, rp_locks);
    if (!scheduler)
	erts_smp_proc_dec_refc(rp);
}


static void deliver_bin_message(Port*  prt,         /* port */
				Eterm to,           /* receiving pid */
				char* hbuf,         /* "header" buffer */
				ErlDrvSizeT hlen,   /* and it's length */
				ErlDrvBinary* bin,  /* binary data */
				ErlDrvSizeT offs,   /* offset into binary */
				ErlDrvSizeT len)    /* length of binary */
{
    SysIOVec vec;

    vec.iov_base = bin->orig_bytes+offs;
    vec.iov_len = len;
    deliver_vec_message(prt, to, hbuf, hlen, &bin, &vec, 1, len);
}

/* flush the port I/O queue and terminate if empty */
/* 
 * Note. 
 *
 * The test for ERTS_PORT_SFLGS_DEAD is important since the
 * driver's  flush function might call driver_async, which when using no
 * threads and being short circuited will notice that the io queue is empty
 * (after calling the driver's async_ready) and recursively call 
 * terminate_port. So when we get back here, the port is already terminated.
 */
static void flush_port(Port *p)
{
    int fpe_was_unmasked;

    ERTS_SMP_CHK_NO_PROC_LOCKS;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));

    if (p->drv_ptr->flush != NULL) {
#ifdef USE_VM_PROBES
        if (DTRACE_ENABLED(driver_flush)) {
            DTRACE_FORMAT_COMMON_PID_AND_PORT(ERTS_PORT_GET_CONNECTED(p), p)
            DTRACE3(driver_flush, process_str, port_str, p->name);
        }
#endif
        if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
	    trace_sched_ports_where(p, am_in, am_flush);
	}
	fpe_was_unmasked = erts_block_fpe();
	(*p->drv_ptr->flush)((ErlDrvData)p->drv_data);
	erts_unblock_fpe(fpe_was_unmasked);
        if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
	    trace_sched_ports_where(p, am_out, am_flush);
	}
#ifdef ERTS_SMP
	if (p->xports)
	    erts_smp_xports_unlock(p);
	ASSERT(!p->xports);
#endif
    }
    if ((erts_atomic32_read_nob(&p->state) & ERTS_PORT_SFLGS_DEAD) == 0
	&& is_port_ioq_empty(p)) {
	terminate_port(p);
    }
}

/* stop and delete a port that is ERTS_PORT_SFLG_CLOSING */
static void
terminate_port(Port *prt)
{
    Eterm send_closed_port_id;
    Eterm connected_id = NIL /* Initialize to silence compiler */;
    erts_driver_t *drv;
    erts_aint32_t state;

    ERTS_SMP_CHK_NO_PROC_LOCKS;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

    ASSERT(!ERTS_P_LINKS(prt));
    ASSERT(!ERTS_P_MONITORS(prt));

    /* state may be altered by kill_port() below */
    state = erts_atomic32_read_band_nob(&prt->state,
					~ERTS_PORT_SFLG_SEND_CLOSED);
    if (state & ERTS_PORT_SFLG_SEND_CLOSED) {
	send_closed_port_id = prt->common.id;
	connected_id = ERTS_PORT_GET_CONNECTED(prt);
    }
    else {
	send_closed_port_id = NIL;
    }

#ifdef ERTS_SMP
    erts_cancel_smp_ptimer(prt->common.u.alive.ptimer);
#else
    erts_cancel_timer(&prt->common.u.alive.tm);
#endif

    drv = prt->drv_ptr;
    if ((drv != NULL) && (drv->stop != NULL)) {
	int fpe_was_unmasked = erts_block_fpe();
#ifdef USE_VM_PROBES
        if (DTRACE_ENABLED(driver_stop)) {
            DTRACE_FORMAT_COMMON_PID_AND_PORT(connected_id, prt)
            DTRACE3(driver_stop, process_str, drv->name, port_str);
        }
#endif
	(*drv->stop)((ErlDrvData)prt->drv_data);
	erts_unblock_fpe(fpe_was_unmasked);
#ifdef ERTS_SMP
	if (prt->xports)
	    erts_smp_xports_unlock(prt);
	ASSERT(!prt->xports);
#endif
    }
    if(drv->handle != NULL) {
	erts_smp_rwmtx_rlock(&erts_driver_list_lock);
	erts_ddll_decrement_port_count(drv->handle); 
	erts_smp_rwmtx_runlock(&erts_driver_list_lock);
    }
    stopq(prt);        /* clear queue memory */
    if(prt->linebuf != NULL){
	erts_free(ERTS_ALC_T_LINEBUF, (void *) prt->linebuf);
	prt->linebuf = NULL;
    }
    if (prt->bp != NULL) {
	free_message_buffer(prt->bp);
	prt->bp = NULL;
	prt->data = am_undefined;
    }

    if (prt->psd)
	erts_free(ERTS_ALC_T_PRTSD, prt->psd);

    ASSERT(prt->dist_entry == NULL);

    kill_port(prt);

    /*
     * We don't want to send the closed message until after the
     * port has been removed from the port table (in kill_port()).
     */
    if ((state & ERTS_PORT_SFLG_HALT)
	&& (erts_smp_atomic32_dec_read_nob(&erts_halt_progress) == 0)) {
	erts_port_release(prt); /* We will exit and never return */
	erl_exit_flush_async(erts_halt_code, "");
    }
    if (is_internal_port(send_closed_port_id))
	deliver_result(send_closed_port_id, connected_id, am_closed);
}

void
erts_terminate_port(Port *pp)
{
    terminate_port(pp);
}

static void sweep_one_monitor(ErtsMonitor *mon, void *vpsc)
{
    ErtsMonitor *rmon;
    Process *rp;

    ASSERT(mon->type == MON_ORIGIN);
    ASSERT(is_internal_pid(mon->pid));
    rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK);
    if (!rp) {
	goto done;
    }
    rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), mon->ref);
    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
    if (rmon == NULL) {
	goto done;
    }
    erts_destroy_monitor(rmon);
 done:
    erts_destroy_monitor(mon);
}



typedef struct {
    Eterm port;
    Eterm reason;
} SweepContext;

static void sweep_one_link(ErtsLink *lnk, void *vpsc)
{
    SweepContext *psc = vpsc;
    DistEntry *dep;
    Process *rp;
    

    ASSERT(lnk->type == LINK_PID);
    
    if (is_external_pid(lnk->pid)) {
	dep = external_pid_dist_entry(lnk->pid);
	if(dep != erts_this_dist_entry) {
	    ErtsDistLinkData dld;
	    ErtsDSigData dsd;
	    int code;
	    code = erts_dsig_prepare(&dsd, dep, NULL, ERTS_DSP_NO_LOCK, 0);
	    switch (code) {
	    case ERTS_DSIG_PREP_NOT_ALIVE:
	    case ERTS_DSIG_PREP_NOT_CONNECTED:
		break;
	    case ERTS_DSIG_PREP_CONNECTED:
		erts_remove_dist_link(&dld, psc->port, lnk->pid, dep);
		erts_destroy_dist_link(&dld);
		code = erts_dsig_send_exit(&dsd, psc->port, lnk->pid,
					   psc->reason);
		ASSERT(code == ERTS_DSIG_SEND_OK);
		break;
	    default:
		ASSERT(! "Invalid dsig prepare result");
		break;
	    }
	}
    } else {
	ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK|ERTS_PROC_LOCKS_XSIG_SEND;
	ASSERT(is_internal_pid(lnk->pid));
	rp = erts_pid2proc(NULL, 0, lnk->pid, rp_locks);
	if (rp) {
	    ErtsLink *rlnk = erts_remove_link(&ERTS_P_LINKS(rp), psc->port);

	    if (rlnk) {
		int xres = erts_send_exit_signal(NULL,
						 psc->port,
						 rp,
						 &rp_locks, 
						 psc->reason,
						 NIL,
						 NULL,
						 0);
		if (xres >= 0 && IS_TRACED_FL(rp, F_TRACE_PROCS)) {
		    /* We didn't exit the process and it is traced */
		    if (IS_TRACED_FL(rp, F_TRACE_PROCS)) {
			trace_proc(NULL, rp, am_getting_unlinked,
				   psc->port);
		    }
		}
		erts_destroy_link(rlnk);
	    }

	    erts_smp_proc_unlock(rp, rp_locks);
	}
    }
    erts_destroy_link(lnk);
}

/* 'from' is sending 'this_port' an exit signal, (this_port must be internal).
 * If reason is normal we don't do anything, *unless* from is our connected
 * process in which case we close the port. Any other reason kills the port.
 * If 'from' is ourself we always die.
 * When a driver has data in ioq then driver will be set to closing
 * and become inaccessible to the processes. One exception exists and
 * that is to kill a port till reason kill. Then the port is stopped.
 * 
 */

int
erts_deliver_port_exit(Port *p, Eterm from, Eterm reason, int send_closed)
{
   ErtsLink *lnk;
   Eterm rreason;
   erts_aint32_t state;

   ERTS_SMP_CHK_NO_PROC_LOCKS;
   ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));

   rreason = (reason == am_kill) ? am_killed : reason;

#ifdef USE_VM_PROBES
   if (DTRACE_ENABLED(port_exit)) {
       DTRACE_CHARBUF(from_str, DTRACE_TERM_BUF_SIZE);
       DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);
       DTRACE_CHARBUF(rreason_str, 64);

       erts_snprintf(from_str, sizeof(from_str), "%T", from);
       dtrace_port_str(p, port_str);
       erts_snprintf(rreason_str, sizeof(rreason_str), "%T", rreason);
       DTRACE4(port_exit, from_str, port_str, p->name, rreason_str);
   }
#endif

   state = erts_atomic32_read_nob(&p->state);
   if (state & (ERTS_PORT_SFLGS_DEAD
		| ERTS_PORT_SFLG_EXITING
		| ERTS_PORT_SFLG_CLOSING))
       return 0;

   if (reason == am_normal && from != ERTS_PORT_GET_CONNECTED(p) && from != p->common.id)
       return 0;

   if (send_closed)
       erts_atomic32_read_bor_relb(&p->state,
				   ERTS_PORT_SFLG_SEND_CLOSED);

   if (IS_TRACED_FL(p, F_TRACE_PORTS)) {
   	trace_port(p, am_closed, reason);
   }

   erts_trace_check_exiting(p->common.id);

   /*
    * Setting the port to not busy here, frees the list of pending
    * processes and makes them runnable.
    */
   set_busy_port((ErlDrvPort) p, 0);

   if (p->common.u.alive.reg != NULL)
       (void) erts_unregister_name(NULL, 0, p, p->common.u.alive.reg->name);

   state = erts_atomic32_read_bor_relb(&p->state, ERTS_PORT_SFLG_EXITING);

   {
       SweepContext sc = {p->common.id, rreason};
       lnk = ERTS_P_LINKS(p);
       ERTS_P_LINKS(p) = NULL;
       erts_sweep_links(lnk, &sweep_one_link, &sc);
   }
   DRV_MONITOR_LOCK_PDL(p);
   {
       ErtsMonitor *moni = ERTS_P_MONITORS(p);
       ERTS_P_MONITORS(p) = NULL;
       erts_sweep_monitors(moni, &sweep_one_monitor, NULL);
   } 
   DRV_MONITOR_UNLOCK_PDL(p);

   if ((state & ERTS_PORT_SFLG_DISTRIBUTION) && p->dist_entry) {
       erts_do_net_exits(p->dist_entry, rreason);
       erts_deref_dist_entry(p->dist_entry); 
       p->dist_entry = NULL;
       erts_atomic32_read_band_relb(&p->state,
				    ~ERTS_PORT_SFLG_DISTRIBUTION);
   }
       
   if ((reason != am_kill) && !is_port_ioq_empty(p)) {
       /* must turn exiting flag off */
       erts_atomic32_read_bset_relb(&p->state,
				    (ERTS_PORT_SFLG_EXITING
				     | ERTS_PORT_SFLG_CLOSING),
				    ERTS_PORT_SFLG_CLOSING);
      flush_port(p);
   }
   else {
       terminate_port(p);
   }

   return 1;
}

/* About the states ERTS_PORT_SFLG_EXITING and ERTS_PORT_SFLG_CLOSING used above.
**
** ERTS_PORT_SFLG_EXITING is a recursion protection for erts_deliver_port_exit().
** It is unclear  whether this state is necessary or not, it might be possible
** to merge it with ERTS_PORT_SFLG_CLOSING. ERTS_PORT_SFLG_EXITING only persists
** over a section of sequential (but highly recursive) code.
**
** ERTS_PORT_SFLG_CLOSING is a state where the port is in Limbo, waiting to
** pass on. All links are removed, and the port receives in/out-put events so
** as soon as the port queue gets empty terminate_port() is called.
*/



/* Command should be of the form
**   {PID, close}
**   {PID, {command, io-list}}
**   {PID, {connect, New_PID}}
*/
ErtsPortOpResult
erts_port_command(Process *c_p,
		  int flags,
		  Port *port,
		  Eterm command,
		  Eterm *refp)
{
    Eterm *tp;

    ASSERT(port);

    flags |= ERTS_PORT_SIG_FLG_BANG_OP;

    if (is_tuple_arity(command, 2)) {
	Eterm cntd;
	tp = tuple_val(command);
	cntd = tp[1];
	if (is_internal_pid(cntd)) {
	    if (tp[2] == am_close) {
		if (!erts_port_synchronous_ops)
		    refp = NULL;
		flags &= ~ERTS_PORT_SIG_FLG_NOSUSPEND;
		return erts_port_exit(c_p, flags, port, cntd, am_normal, refp);
	    } else if (is_tuple_arity(tp[2], 2)) {
		tp = tuple_val(tp[2]);
		if (tp[1] == am_command) {
		    if (!(flags & ERTS_PORT_SIG_FLG_NOSUSPEND)
			&& !erts_port_synchronous_ops)
			refp = NULL;
		    return erts_port_output(c_p, flags, port, cntd, tp[2], refp);
		}
		else if (tp[1] == am_connect) {
		    if (!erts_port_synchronous_ops)
			refp = NULL;
		    flags &= ~ERTS_PORT_SIG_FLG_NOSUSPEND;
		    return erts_port_connect(c_p, flags, port, cntd, tp[2], refp);
		}
	    }
	}
    }

    /* badsig */
    if (!erts_port_synchronous_ops)
	refp = NULL;
    flags &= ~ERTS_PORT_SIG_FLG_NOSUSPEND;
    return bad_port_signal(c_p, flags, port, c_p->common.id, refp, am_command);
}

static ERTS_INLINE ErtsPortOpResult
call_driver_control(Eterm caller,
		    Port *prt,
		    unsigned int command,
		    char *bufp,
		    ErlDrvSizeT size,
		    char **resp_bufp,
		    ErlDrvSizeT *from_size)
{
    ErlDrvSSizeT cres;

    if (!prt->drv_ptr->control)
	return ERTS_PORT_OP_BADARG;


#ifdef USE_VM_PROBES
    if (DTRACE_ENABLED(port_control) || DTRACE_ENABLED(driver_control)) {
        DTRACE_FORMAT_COMMON_PID_AND_PORT(caller, prt);
        DTRACE4(port_control, process_str, port_str, prt->name, command);
        DTRACE5(driver_control, process_str, port_str, prt->name,
                command, size);
    }
#endif

    prt->caller = caller;
    cres = prt->drv_ptr->control((ErlDrvData) prt->drv_data,
				 command,
				 bufp,
				 size,
				 resp_bufp,
				 *from_size);
    prt->caller = NIL;

    if (cres < 0)
	return ERTS_PORT_OP_BADARG;

    *from_size = (ErlDrvSizeT) cres;

    return ERTS_PORT_OP_DONE;
}

static void
cleanup_scheduled_control(Binary *binp, char *bufp)
{
    if (binp) {
	if (erts_refc_dectest(&binp->refc, 0) == 0)
	    erts_bin_free(binp);
    }
    else {
	if (bufp)
	    erts_free(ERTS_ALC_T_DRV_CTRL_DATA, bufp);
    }
}


static ERTS_INLINE Uint
port_control_result_size(int control_flags,
			 char *resp_bufp,
			 ErlDrvSizeT *resp_size,
			 char *pre_alloc_buf)
{
    if (!resp_bufp)
	return (Uint) 0;

    if (control_flags & PORT_CONTROL_FLAG_BINARY) {
	if (resp_bufp != pre_alloc_buf) {
	    ErlDrvBinary *dbin = (ErlDrvBinary *) resp_bufp;
	    *resp_size = dbin->orig_size;
	    if (*resp_size > ERL_ONHEAP_BIN_LIMIT)
		return PROC_BIN_SIZE;
	}
	ASSERT(*resp_size <= ERL_ONHEAP_BIN_LIMIT);
	return (Uint) heap_bin_size((*resp_size));
    }

    return (Uint) 2*(*resp_size);
}

static ERTS_INLINE Eterm
write_port_control_result(int control_flags,
			  char *resp_bufp,
			  ErlDrvSizeT resp_size,
			  char *pre_alloc_buf,
			  Eterm **hpp,
			  ErlHeapFragment *bp,
			  ErlOffHeap *ohp)
{
    Eterm res;
    if (!resp_bufp)
	return NIL;
    if (control_flags & PORT_CONTROL_FLAG_BINARY) {
	/* Binary result */
	ErlDrvBinary *dbin;
	ErlHeapBin *hbin;

	if (resp_bufp == pre_alloc_buf)
	    dbin = NULL;
	else {
	    dbin = (ErlDrvBinary *) resp_bufp;
	    if (dbin->orig_size > ERL_ONHEAP_BIN_LIMIT) {
		ProcBin* pb = (ProcBin *) *hpp;
		*hpp += PROC_BIN_SIZE;
		pb->thing_word = HEADER_PROC_BIN;
		pb->size = dbin->orig_size;
		pb->next = ohp->first;
		ohp->first = (struct erl_off_heap_header *) pb;
		pb->val = ErlDrvBinary2Binary(dbin);
		pb->bytes = (byte*) dbin->orig_bytes;
		pb->flags = 0;
		OH_OVERHEAD(ohp, dbin->orig_size / sizeof(Eterm));
		return make_binary(pb);
	    }
	    resp_bufp = dbin->orig_bytes;
	    resp_size = dbin->orig_size;
	}

	hbin = (ErlHeapBin *) *hpp;
	*hpp += heap_bin_size(resp_size);
	ASSERT(resp_size <= ERL_ONHEAP_BIN_LIMIT);
	hbin->thing_word = header_heap_bin(resp_size);
	hbin->size = resp_size;
	sys_memcpy(hbin->data, resp_bufp, resp_size);
	if (dbin)
	    driver_free_binary(dbin);
	return make_binary(hbin);
    }

    /* List result */
    res = buf_to_intlist(hpp, resp_bufp, resp_size, NIL);
    if (resp_bufp != pre_alloc_buf)
	driver_free(resp_bufp);
    return res;
}

static int
port_sig_control(Port *prt,
		 erts_aint32_t state,
		 int op,
		 ErtsProc2PortSigData *sigdp)
{
    ASSERT(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY);

    if (op == ERTS_PROC2PORT_SIG_EXEC) {
	char resp_buf[ERL_ONHEAP_BIN_LIMIT];
	ErlDrvSizeT resp_size = sizeof(resp_buf);
	char *resp_bufp = &resp_buf[0];
	ErtsPortOpResult res;

	res = call_driver_control(sigdp->caller,
				  prt,
				  sigdp->u.control.command,
				  sigdp->u.control.bufp,
				  sigdp->u.control.size,
				  &resp_bufp,
				  &resp_size);

	if (res == ERTS_PORT_OP_DONE) {
	    Eterm msg;
	    Eterm *hp, *hp_start;
	    ErlHeapFragment *bp;
	    ErlOffHeap *ohp;
	    Process *rp;
	    ErtsProcLocks rp_locks = 0;
	    Uint hsz;
	    int control_flags;

	    rp = erts_proc_lookup_raw(sigdp->caller);
	    if (!rp)
		goto done;

	    control_flags = prt->control_flags;

	    hsz = ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE;
	    hsz += port_control_result_size(control_flags,
					    resp_bufp,
					    &resp_size,
					    &resp_buf[0]);

	    hp_start = hp = erts_alloc_message_heap(hsz,
						    &bp,
						    &ohp,
						    rp,
						    &rp_locks);

	    msg = write_port_control_result(control_flags,
					    resp_bufp,
					    resp_size,
					    &resp_buf[0],
					    &hp,
					    bp,
					    ohp);

	    queue_port_sched_op_reply(rp,
				      &rp_locks,
				      hp_start,
				      hp,
				      hsz,
				      bp,
				      sigdp->ref,
				      msg);

	    if (rp_locks)
		erts_smp_proc_unlock(rp, rp_locks);
	    goto done;
	}
    }

    /* failure */

    port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg);

done:

    cleanup_scheduled_control(sigdp->u.control.binp,
			      sigdp->u.control.bufp);

    return ERTS_PORT_REDS_CONTROL;
}


ErtsPortOpResult
erts_port_control(Process* c_p,
		  Port *prt,
		  unsigned int command,
		  Eterm data,
		  Eterm *retvalp)
{
    ErtsPortOpResult res;
    char *bufp = NULL;
    ErlDrvSizeT size = 0;
    int try_call;
    int tmp_alloced = 0;
    erts_aint32_t sched_flags;
    Binary *binp;
    int copy;
    ErtsProc2PortSigData *sigdp;

    sched_flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
    if (sched_flags & ERTS_PTS_FLG_EXIT)
	return ERTS_PORT_OP_BADARG;

    try_call = !(sched_flags & ERTS_PTS_FLGS_FORCE_SCHEDULE_OP);

    if (is_binary(data) && binary_bitoffset(data) == 0) {
	byte *bytep;
	ERTS_DECLARE_DUMMY(Uint bitoffs);
	ERTS_DECLARE_DUMMY(Uint bitsize);
	ERTS_GET_BINARY_BYTES(data, bytep, bitoffs, bitsize);
	bufp = (char *) bytep;
	size = binary_size(data);
    } else {
	int r;

	if (!try_call) {
	    if (erts_iolist_size(data, &size))
		return ERTS_PORT_OP_BADARG;
	    bufp = erts_alloc(ERTS_ALC_T_DRV_CTRL_DATA, size);
	    r = erts_iolist_to_buf(data, bufp, size);
	    ASSERT(r == 0);
	}
	else {
	    /* Try with an 8KB buffer first (will often be enough I guess). */
	    size = 8*1024;
	    bufp = erts_alloc(ERTS_ALC_T_TMP, size);
	    tmp_alloced = 1;

	    r = erts_iolist_to_buf(data, bufp, size);
	    if (ERTS_IOLIST_TO_BUF_SUCCEEDED(r)) {
		size -= r;
	    } else {
		if (r == ERTS_IOLIST_TO_BUF_TYPE_ERROR) { /* Type error */
		    erts_free(ERTS_ALC_T_TMP, bufp);
		    return ERTS_PORT_OP_BADARG;
		}
		else {
		    ASSERT(r == ERTS_IOLIST_TO_BUF_OVERFLOW); /* Overflow */
		    erts_free(ERTS_ALC_T_TMP, bufp);
		    if (erts_iolist_size(data, &size))
			return ERTS_PORT_OP_BADARG; /* Type error */
		}
		bufp = erts_alloc(ERTS_ALC_T_TMP, size);
		r = erts_iolist_to_buf(data, bufp, size);
		ASSERT(r == 0);
	    }
	}
    }

    if (try_call) {
	char resp_buf[ERL_ONHEAP_BIN_LIMIT];
	char* resp_bufp = &resp_buf[0];
	ErlDrvSizeT resp_size = sizeof(resp_buf);
	ErtsTryImmDrvCallResult try_call_res;
	ErtsTryImmDrvCallState try_call_state
	    = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
		c_p,
		prt,
		ERTS_PORT_SFLGS_INVALID_LOOKUP,
		0,
		0,
		am_control);

	try_call_res = try_imm_drv_call(&try_call_state);
	switch (try_call_res) {
	case ERTS_TRY_IMM_DRV_CALL_OK: {
	    Eterm *hp;
	    Uint hsz;
	    int control_flags;

	    res = call_driver_control(c_p->common.id,
				      prt,
				      command,
				      bufp,
				      size,
				      &resp_bufp,
				      &resp_size);
	    finalize_imm_drv_call(&try_call_state);
	    if (tmp_alloced)
		erts_free(ERTS_ALC_T_TMP, bufp);
	    if (res == ERTS_PORT_OP_BADARG) {
		return ERTS_PORT_OP_BADARG;
	    }

	    control_flags = prt->control_flags;

	    hsz = port_control_result_size(control_flags,
					   resp_bufp,
					   &resp_size,
					   &resp_buf[0]);
	    hp = HAlloc(c_p, hsz);
	    *retvalp = write_port_control_result(control_flags,
						 resp_bufp,
						 resp_size,
						 &resp_buf[0],
						 &hp,
						 NULL,
						 &c_p->off_heap);
	    return ERTS_PORT_OP_DONE;
	}
	case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	    if (tmp_alloced)
		erts_free(ERTS_ALC_T_TMP, bufp);
	    return ERTS_PORT_OP_BADARG;
	default:
	    /* Schedule control() call instead... */
	    break;
	}
    }

    /* Convert data into something that can be scheduled */

    copy = tmp_alloced;

    binp = NULL;

    if (is_binary(data) && binary_bitoffset(data) == 0) {
	Eterm *ebinp = binary_val_rel(data, NULL);
	ASSERT(!tmp_alloced);
	if (*ebinp == HEADER_SUB_BIN)
	    ebinp = binary_val_rel(((ErlSubBin *) ebinp)->orig, NULL);
	if (*ebinp != HEADER_PROC_BIN)
	    copy = 1;
	else {
	    binp = ((ProcBin *) ebinp)->val;
	    ASSERT(bufp < bufp + size);
	    ASSERT(binp->orig_bytes <= bufp
		   && bufp + size <= binp->orig_bytes + binp->orig_size);
	    erts_refc_inc(&binp->refc, 1);
	}
    }

    if (copy) {
	char *old_bufp = bufp;
	bufp = erts_alloc(ERTS_ALC_T_DRV_CTRL_DATA, size);
	sys_memcpy(bufp, old_bufp, size);
	if (tmp_alloced)
	    erts_free(ERTS_ALC_T_TMP, old_bufp);
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.control.binp = binp;
    sigdp->u.control.command = command;
    sigdp->u.control.bufp = bufp;
    sigdp->u.control.size = size;
    
    res = erts_schedule_proc2port_signal(c_p,
					 prt,
					 c_p->common.id,
					 retvalp,
					 sigdp,
					 0,
					 port_sig_control);
    if (res != ERTS_PORT_OP_SCHEDULED) {
	cleanup_scheduled_control(binp, bufp);
	return ERTS_PORT_OP_BADARG;
    }
    return res;
}

static ERTS_INLINE ErtsPortOpResult
call_driver_call(Eterm caller,
		 Port *prt,
		 unsigned int command,
		 char *bufp,
		 ErlDrvSizeT size,
		 char **resp_bufp,
		 ErlDrvSizeT *from_size,
		 unsigned *ret_flagsp)
{
    ErlDrvSSizeT cres;

    if (!prt->drv_ptr->call)
	return ERTS_PORT_OP_BADARG;

#ifdef USE_VM_PROBES
    if (DTRACE_ENABLED(driver_call)) {
        DTRACE_CHARBUF(process_str, DTRACE_TERM_BUF_SIZE);
        DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);

        dtrace_pid_str(caller, process_str);
        dtrace_port_str(prt, port_str);
        DTRACE5(driver_call, process_str, port_str, prt->name, command, size);
    }
#endif

    prt->caller = caller;
    cres = prt->drv_ptr->call((ErlDrvData) prt->drv_data,
			      command,
			      bufp,
			      size,
			      resp_bufp,
			      *from_size,
			      ret_flagsp);
    prt->caller = NIL;

    if (cres <= 0
	|| ((byte) (*resp_bufp)[0]) != VERSION_MAGIC)
	return ERTS_PORT_OP_BADARG;

    *from_size = (ErlDrvSizeT) cres;

    return ERTS_PORT_OP_DONE;
}


static
void cleanup_scheduled_call(char *bufp)
{
    if (bufp)
	erts_free(ERTS_ALC_T_DRV_CALL_DATA, bufp);
}

static int
port_sig_call(Port *prt,
	      erts_aint32_t state,
	      int op,
	      ErtsProc2PortSigData *sigdp)
{
    char resp_buf[256];
    ErlDrvSizeT resp_size = sizeof(resp_buf);
    char *resp_bufp = &resp_buf[0];
    unsigned ret_flags = 0U;


    ASSERT(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY);

    if (op == ERTS_PROC2PORT_SIG_EXEC) {
	ErtsPortOpResult res;

	res = call_driver_call(sigdp->caller,
			       prt,
			       sigdp->u.call.command,
			       sigdp->u.call.bufp,
			       sigdp->u.call.size,
			       &resp_bufp,
			       &resp_size,
			       &ret_flags);

	if (res == ERTS_PORT_OP_DONE) {
	    Eterm msg;
	    Eterm *hp;
	    ErlHeapFragment *bp;
	    ErlOffHeap *ohp;
	    Process *rp;
	    ErtsProcLocks rp_locks = 0;
	    Uint hsz;

	    rp = erts_proc_lookup_raw(sigdp->caller);
	    if (!rp)
		goto done;

	    hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size);
	    if (hsz >= 0) {
		Eterm *hp_start;
		byte *endp;

		hsz += 3; /* ok tuple */
		hsz += ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE;

		hp_start = hp = erts_alloc_message_heap(hsz,
							&bp,
							&ohp,
							rp,
							&rp_locks);
		endp = (byte *) resp_bufp;
		msg = erts_decode_ext(&hp, ohp, &endp);
		if (is_value(msg)) {
		    msg = TUPLE2(hp, am_ok, msg);
		    hp += 3;

		    queue_port_sched_op_reply(rp,
					      &rp_locks,
					      hp_start,
					      hp,
					      hsz,
					      bp,
					      sigdp->ref,
					      msg);

		    if (rp_locks)
			erts_smp_proc_unlock(rp, rp_locks);
		    goto done;
		}
		if (bp)
		    free_message_buffer(bp);
		if (rp_locks)
		    erts_smp_proc_unlock(rp, rp_locks);
	    }
	}
    }

    port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg);

done:

    if (resp_bufp != &resp_buf[0] && !(ret_flags & DRIVER_CALL_KEEP_BUFFER))
	driver_free(resp_bufp);

    cleanup_scheduled_call(sigdp->u.call.bufp);

    return ERTS_PORT_REDS_CALL;
}


ErtsPortOpResult
erts_port_call(Process* c_p,
	       Port *prt,
	       unsigned int command,
	       Eterm data,
	       Eterm *retvalp)
{
    ErtsPortOpResult res;
    char input_buf[256];
    char *bufp;
    byte *endp;
    ErlDrvSizeT size;
    int try_call;
    erts_aint32_t sched_flags;
    ErtsProc2PortSigData *sigdp;

    sched_flags = erts_smp_atomic32_read_nob(&prt->sched.flags);
    if (sched_flags & ERTS_PTS_FLG_EXIT) {
	return ERTS_PORT_OP_BADARG;
    }

    try_call = !(sched_flags & ERTS_PTS_FLGS_FORCE_SCHEDULE_OP);

    size = erts_encode_ext_size(data);

    if (!try_call)
	bufp = erts_alloc(ERTS_ALC_T_DRV_CALL_DATA, size);
    else if (size <= sizeof(input_buf))
	bufp = &input_buf[0];
    else
	bufp = erts_alloc(ERTS_ALC_T_TMP, size);

    endp = (byte *) bufp;
    erts_encode_ext(data, &endp);

    if (endp - (byte *) bufp > size)
	ERTS_INTERNAL_ERROR("erts_internal:port_call() - Buffer overflow");

    size = endp - (byte *) bufp;

    if (try_call) {
	char resp_buf[255];
	char* resp_bufp = &resp_buf[0];
	ErlDrvSizeT resp_size = sizeof(resp_buf);
	ErtsTryImmDrvCallResult try_call_res;
	ErtsTryImmDrvCallState try_call_state
	    = ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
		c_p,
		prt,
		ERTS_PORT_SFLGS_INVALID_LOOKUP,
		0,
		0,
		am_call);

	try_call_res = try_imm_drv_call(&try_call_state);
	switch (try_call_res) {
	case ERTS_TRY_IMM_DRV_CALL_OK: {
	    Eterm *hp, *hp_end;
	    Uint hsz;
	    unsigned ret_flags = 0U;
	    Eterm term;

	    res = call_driver_call(c_p->common.id,
				   prt,
				   command,
				   bufp,
				   size,
				   &resp_bufp,
				   &resp_size,
				   &ret_flags);

	    finalize_imm_drv_call(&try_call_state);
	    if (bufp != &input_buf[0])
		erts_free(ERTS_ALC_T_TMP, bufp);
	    if (res == ERTS_PORT_OP_BADARG)
		return ERTS_PORT_OP_BADARG;
	    hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size);
	    if (hsz < 0)
		return ERTS_PORT_OP_BADARG;
	    hsz += 3;
	    hp = HAlloc(c_p, hsz);
	    hp_end = hp + hsz;
	    endp = (byte *) resp_bufp;
	    term = erts_decode_ext(&hp, &MSO(c_p), &endp);
	    if (term == THE_NON_VALUE)
		return ERTS_PORT_OP_BADARG;
	    *retvalp = TUPLE2(hp, am_ok, term);
	    hp += 3;
	    HRelease(c_p, hp_end, hp);
	    if (resp_buf != &resp_buf[0]
		&& !(ret_flags & DRIVER_CALL_KEEP_BUFFER))
		driver_free(resp_buf);
	    return ERTS_PORT_OP_DONE;
	}
	case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	    if (bufp != &input_buf[0])
		erts_free(ERTS_ALC_T_TMP, bufp);
	    return ERTS_PORT_OP_BADARG;
	default:
	    /* Schedule call() call instead... */
	    break;
	}
    }

    /* Convert data into something that can be scheduled */

    if (bufp == &input_buf[0] || try_call) {
	char *new_bufp = erts_alloc(ERTS_ALC_T_DRV_CALL_DATA, size);
	sys_memcpy(new_bufp, bufp, size);
	if (bufp != &input_buf[0])
	    erts_free(ERTS_ALC_T_TMP, bufp);
	bufp = new_bufp;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.call.command = command;
    sigdp->u.call.bufp = bufp;
    sigdp->u.call.size = size;
    
    res = erts_schedule_proc2port_signal(c_p,
					 prt,
					 c_p->common.id,
					 retvalp,
					 sigdp,
					 0,
					 port_sig_call);
    if (res != ERTS_PORT_OP_SCHEDULED) {
	cleanup_scheduled_call(bufp);
	return ERTS_PORT_OP_BADARG;
    }
    return res;
}

static Eterm
make_port_info_term(Eterm **hpp_start,
		    Eterm **hpp,
		    Uint *hszp,
		    ErlHeapFragment **bpp,
		    Port *prt,
		    Eterm item)
{
    ErlOffHeap *ohp;

    if (is_value(item)) {
	if (erts_bld_port_info(NULL, NULL, hszp, prt, item) == am_false)
	    return THE_NON_VALUE;
	if (*hszp) {
	    *bpp = new_message_buffer(*hszp);
	    *hpp_start = *hpp = (*bpp)->mem;
	    ohp = &(*bpp)->off_heap;
	}
	else {
	    *bpp = NULL;
	    *hpp_start = *hpp = NULL;
	    ohp = NULL;
	}
	return erts_bld_port_info(hpp, ohp, NULL, prt, item);
    }
    else {
	int i;
	int len;
	int start;
	static Eterm item[] = ERTS_PORT_INFO_1_ITEMS;
	static Eterm value[sizeof(item)/sizeof(item[0])];

	start = 0;
	len = sizeof(item)/sizeof(item[0]);

	for (i = start; i < sizeof(item)/sizeof(item[0]); i++) {
	    ASSERT(is_atom(item[i]));
	    value[i] = erts_bld_port_info(NULL, NULL, hszp, prt, item[i]);
	}

	if (value[0] == am_undefined) {
	    start++;
	    len--;
	}

	erts_bld_list(NULL, hszp, len, &value[start]);

	*bpp = new_message_buffer(*hszp);
	*hpp_start = *hpp = (*bpp)->mem;
	ohp = &(*bpp)->off_heap;

	for (i = start; i < sizeof(item)/sizeof(item[0]); i++)
	    value[i] = erts_bld_port_info(hpp, ohp, NULL, prt, item[i]);

	return erts_bld_list(hpp, NULL, len, &value[start]);
    }
}

static int
port_sig_info(Port *prt,
	      erts_aint32_t state,
	      int op,
	      ErtsProc2PortSigData *sigdp)
{
    ASSERT(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY);
    if (op != ERTS_PROC2PORT_SIG_EXEC)
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_undefined);
    else {
	Eterm *hp, *hp_start;
	Uint hsz;
	ErlHeapFragment *bp;
	Eterm value;
	Process *rp;
	ErtsProcLocks rp_locks = 0;

	rp = erts_proc_lookup_raw(sigdp->caller);
	if (!rp)
	    return ERTS_PORT_REDS_INFO;

	hsz = ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE;
	value = make_port_info_term(&hp_start,
				    &hp,
				    &hsz,
				    &bp,
				    prt,
				    sigdp->u.info.item);
	if (is_value(value)) {
	    queue_port_sched_op_reply(rp,
				      &rp_locks,
				      hp_start,
				      hp,
				      hsz,
				      bp,
				      sigdp->ref,
				      value);
	}
	if (rp_locks)
	    erts_smp_proc_unlock(rp, rp_locks);
    }
    return ERTS_PORT_REDS_INFO;
}

ErtsPortOpResult
erts_port_info(Process* c_p,
	       Port *prt,
	       Eterm item,
	       Eterm *retvalp)
{
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallResult try_call_res;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
	    c_p,
	    prt,
	    ERTS_PORT_SFLGS_INVALID_LOOKUP,
	    0,
	    0,
	    am_info);

    try_call_res = try_imm_drv_call(&try_call_state);
    switch (try_call_res) {
    case ERTS_TRY_IMM_DRV_CALL_OK: {
	Eterm *hp, *hp_start;
	ErlHeapFragment *bp;
	Uint hsz = 0;
	Eterm value = make_port_info_term(&hp_start, &hp, &hsz, &bp, prt, item);
	finalize_imm_drv_call(&try_call_state);
	if (is_non_value(value))
	    return ERTS_PORT_OP_BADARG;
	else if (is_immed(value))
	    *retvalp = value;
	else {
	    Uint used_h_size = hp - hp_start;
	    hp = HAlloc(c_p, used_h_size);
	    *retvalp = copy_struct(value, used_h_size, &hp, &MSO(c_p));
	    free_message_buffer(bp);
	}
	return ERTS_PORT_OP_DONE;
    }
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.info.item = item;

    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p->common.id,
					  retvalp,
					  sigdp,
					  0,
					  port_sig_info);
}

static int
port_sig_set_data(Port *prt,
		  erts_aint32_t state,
		  int op,
		  ErtsProc2PortSigData *sigdp)
{
    ASSERT(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY);

    if (op == ERTS_PROC2PORT_SIG_EXEC) {
	if (prt->bp)
	    free_message_buffer(prt->bp);
	prt->bp = sigdp->u.set_data.bp;
	prt->data = sigdp->u.set_data.data;
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_true);
    }
    else {
	if (sigdp->u.set_data.bp)
	    free_message_buffer(sigdp->u.set_data.bp);
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg);
    }
    return ERTS_PORT_REDS_SET_DATA;
}

ErtsPortOpResult
erts_port_set_data(Process* c_p,
		   Port *prt,
		   Eterm data,
		   Eterm *refp)
{
    ErtsPortOpResult res;
    Eterm set_data;
    ErlHeapFragment *bp;
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallResult try_call_res;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
	    c_p,
	    prt,
	    ERTS_PORT_SFLGS_INVALID_LOOKUP,
	    0,
	    !refp,
	    am_set_data);

    if (is_immed(data)) {
	set_data = data;
	bp = NULL;
    }
    else {
	Eterm *hp;
	Uint sz = size_object(data);
	bp = new_message_buffer(sz);
	hp = bp->mem;
	set_data = copy_struct(data, sz, &hp, &bp->off_heap);
    }

    try_call_res = try_imm_drv_call(&try_call_state);
    switch (try_call_res) {
    case ERTS_TRY_IMM_DRV_CALL_OK:
	if (prt->bp)
	    free_message_buffer(prt->bp);
	prt->bp = bp;
	prt->data = set_data;
	finalize_imm_drv_call(&try_call_state);
	return ERTS_PORT_OP_DONE;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;
    sigdp->u.set_data.data = set_data;
    sigdp->u.set_data.bp = bp;

    res = erts_schedule_proc2port_signal(c_p,
					 prt,
					 c_p->common.id,
					 refp,
					 sigdp,
					 0,
					 port_sig_set_data);
    if (res != ERTS_PORT_OP_SCHEDULED && bp)
	free_message_buffer(bp);
    return res;
}

static int
port_sig_get_data(Port *prt,
		  erts_aint32_t state,
		  int op,
		  ErtsProc2PortSigData *sigdp)
{
    ASSERT(sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY);
    if (op != ERTS_PROC2PORT_SIG_EXEC)
	port_sched_op_reply(sigdp->caller, sigdp->ref, am_badarg);
    else {
	Process *rp;
	ErtsProcLocks rp_locks = 0;

	rp = erts_proc_lookup_raw(sigdp->caller);
	if (rp) {
	    Uint hsz;
	    Eterm *hp, *hp_start;
	    Eterm data, msg;
	    ErlHeapFragment *bp;
	    ErlOffHeap *ohp;

	    hsz = ERTS_QUEUE_PORT_SCHED_OP_REPLY_SIZE;
	    hsz += 3;
	    if (prt->bp)
		hsz += prt->bp->used_size;

	    hp_start = hp = erts_alloc_message_heap(hsz,
						    &bp,
						    &ohp,
						    rp,
						    &rp_locks);

	    if (is_immed(prt->data))
		data = prt->data;
	    else
		data = copy_struct(prt->data,
				   prt->bp->used_size,
				   &hp,
				   &bp->off_heap);



	    msg = TUPLE2(hp, am_ok, data);
	    hp += 3;

	    queue_port_sched_op_reply(rp,
				      &rp_locks,
				      hp_start,
				      hp,
				      hsz,
				      bp,
				      sigdp->ref,
				      msg);
	    if (rp_locks)
		erts_smp_proc_unlock(rp, rp_locks);
	}
    }
    return ERTS_PORT_REDS_GET_DATA;
}

ErtsPortOpResult
erts_port_get_data(Process* c_p,
		   Port *prt,
		   Eterm *retvalp)
{
    ErtsProc2PortSigData *sigdp;
    ErtsTryImmDrvCallResult try_call_res;
    ErtsTryImmDrvCallState try_call_state
	= ERTS_INIT_TRY_IMM_DRV_CALL_STATE(
	    c_p,
	    prt,
	    ERTS_PORT_SFLGS_INVALID_LOOKUP,
	    0,
	    0,
	    am_get_data);

    try_call_res = try_imm_drv_call(&try_call_state);
    switch (try_call_res) {
    case ERTS_TRY_IMM_DRV_CALL_OK: {
	Eterm *hp;
	Eterm data;
	ErlHeapFragment *bp;
	Uint sz;
	if (is_immed(prt->data)) {
	    bp = NULL;
	    data = prt->data;
	}
	else {
	    bp = new_message_buffer(prt->bp->used_size);
	    data = copy_struct(prt->data,
			       prt->bp->used_size,
			       &hp,
			       &bp->off_heap);
	}	    
	finalize_imm_drv_call(&try_call_state);
	if (is_immed(data))
	    sz = 0;
	else 
	    sz = bp->used_size;

	hp = HAlloc(c_p,  sz + 3);
	if (is_not_immed(data)) {
	    data = copy_struct(data, bp->used_size, &hp, &MSO(c_p));
	    free_message_buffer(bp);
	}
	*retvalp = TUPLE2(hp, am_ok, data);
	return ERTS_PORT_OP_DONE;
    }
    case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT:
	return ERTS_PORT_OP_DROPPED;
    case ERTS_TRY_IMM_DRV_CALL_INVALID_SCHED_FLAGS:
    case ERTS_TRY_IMM_DRV_CALL_BUSY_LOCK:
	/* Schedule call instead... */
	break;
    }

    sigdp = erts_port_task_alloc_p2p_sig_data();
    sigdp->flags = 0;

    return erts_schedule_proc2port_signal(c_p,
					  prt,
					  c_p->common.id,
					  retvalp,
					  sigdp,
					  0,
					  port_sig_get_data);
}

typedef struct {
    int to;
    void *arg;
} prt_one_lnk_data;

static void prt_one_monitor(ErtsMonitor *mon, void *vprtd)
{
    prt_one_lnk_data *prtd = (prt_one_lnk_data *) vprtd;
    erts_print(prtd->to, prtd->arg, "(%T,%T)", mon->pid,mon->ref);
}

static void prt_one_lnk(ErtsLink *lnk, void *vprtd)
{
    prt_one_lnk_data *prtd = (prt_one_lnk_data *) vprtd;
    erts_print(prtd->to, prtd->arg, "%T", lnk->pid);
}

void
print_port_info(Port *p, int to, void *arg)
{
    erts_aint32_t state = erts_atomic32_read_nob(&p->state);

    if (state & ERTS_PORT_SFLGS_DEAD)
	return;

    erts_print(to, arg, "=port:%T\n", p->common.id);
    erts_print(to, arg, "Slot: %d\n", internal_port_index(p->common.id));
    if (state & ERTS_PORT_SFLG_CONNECTED) {
	erts_print(to, arg, "Connected: %T", ERTS_PORT_GET_CONNECTED(p));
	erts_print(to, arg, "\n");
    }

    if (ERTS_P_LINKS(p)) {
	prt_one_lnk_data prtd;
	prtd.to = to;
	prtd.arg = arg;
	erts_print(to, arg, "Links: ");
	erts_doforall_links(ERTS_P_LINKS(p), &prt_one_lnk, &prtd);
	erts_print(to, arg, "\n");
    }
    if (ERTS_P_MONITORS(p)) {
	prt_one_lnk_data prtd;
	prtd.to = to;
	prtd.arg = arg;
	erts_print(to, arg, "Monitors: ");
	erts_doforall_monitors(ERTS_P_MONITORS(p), &prt_one_monitor, &prtd);
	erts_print(to, arg, "\n");
    }

    if (p->common.u.alive.reg != NULL)
	erts_print(to, arg, "Registered as: %T\n", p->common.u.alive.reg->name);

    if (p->drv_ptr == &fd_driver) {
	erts_print(to, arg, "Port is UNIX fd not opened by emulator: %s\n", p->name);
    } else if (p->drv_ptr == &vanilla_driver) {
	erts_print(to, arg, "Port is a file: %s\n",p->name);
    } else if (p->drv_ptr == &spawn_driver) {
	erts_print(to, arg, "Port controls external process: %s\n",p->name);
    } else {
	erts_print(to, arg, "Port controls linked-in driver: %s\n",p->name);
    }
}

void
set_busy_port(ErlDrvPort dprt, int on)
{
    Port *prt;
    erts_aint32_t flags;

#ifdef USE_VM_PROBES
    DTRACE_CHARBUF(port_str, 16);
#endif

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    prt = erts_drvport2port_raw(dprt);
    if (!prt)
	return;

    if (on) {
	flags = erts_smp_atomic32_read_bor_acqb(&prt->sched.flags,
						ERTS_PTS_FLG_BUSY);
	if (flags & ERTS_PTS_FLG_BUSY)
	    return; /* Already busy */

	if (flags & ERTS_PTS_FLG_HAVE_NS_TASKS)
	    erts_port_task_abort_nosuspend_tasks(prt);

#ifdef USE_VM_PROBES
        if (DTRACE_ENABLED(port_busy)) {
            erts_snprintf(port_str, sizeof(port_str),
                          "%T", prt->common.id);
            DTRACE1(port_busy, port_str);
        }
#endif
    } else {
        ErtsProcList *plp;

	flags = erts_smp_atomic32_read_band_acqb(&prt->sched.flags,
						 ~ERTS_PTS_FLG_BUSY);
	if (!(flags & ERTS_PTS_FLG_BUSY))
	    return; /* Already non-busy */

#ifdef USE_VM_PROBES
        if (DTRACE_ENABLED(port_not_busy)) {
            erts_snprintf(port_str, sizeof(port_str),
                          "%T", prt->common.id);
            DTRACE1(port_not_busy, port_str);
        }
#endif
	if (prt->dist_entry) {
	    /*
	     * Processes suspended on distribution ports are
	     * normally queued on the dist entry.
	     */
	    erts_dist_port_not_busy(prt);
	}

	/*
	 * Resume, in a round-robin fashion, all processes waiting on the port.
	 * 
	 * This version submitted by Tony Rogvall. The earlier version used
	 * to resume the processes in order, which caused starvation of all but
	 * the first process.
	 */

	erts_port_task_sched_lock(&prt->sched);
	plp = prt->suspended;
	prt->suspended = NULL;
	erts_port_task_sched_unlock(&prt->sched);

        if (erts_proclist_fetch(&plp, NULL)) {

#ifdef USE_VM_PROBES
            /*
             * Hrm, for blocked dist ports, plp always seems to be NULL.
             * That's not so fun.
             * Well, another way to get the same info is using a D
             * script to correlate an earlier process-port_blocked+pid
             * event with a later process-scheduled event.  That's
             * subject to the multi-CPU races with how events are
             * handled, but hey, that way works most of the time.
             */
            if (DTRACE_ENABLED(process_port_unblocked)) {
                DTRACE_CHARBUF(pid_str, 16);
                ErtsProcList* plp2 = plp;

                erts_snprintf(port_str, sizeof(port_str),
                             "%T", prt->common.id);
                while (plp2 != NULL) {
                    erts_snprintf(pid_str, sizeof(pid_str), "%T", plp2->pid);
                    DTRACE2(process_port_unblocked, pid_str, port_str);
                }
            }
#endif

            /* First proc should be resumed last */
	    if (plp->next) {
		plp->next->prev = NULL;
		erts_resume_processes(plp->next);
		plp->next = NULL;
	    }
	    erts_resume_processes(plp);
        }
    }
}

void set_port_control_flags(ErlDrvPort port_num, int flags)
{
    Port *prt = erts_drvport2port_raw(port_num);
    if (prt)
	prt->control_flags = flags;
}

int get_port_flags(ErlDrvPort ix)
{
    int flags;
    Port *prt;
    erts_aint32_t state;

    prt = erts_drvport2port(ix, &state);
    if (!prt)
	return 0;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

    flags = 0;
    if (state & ERTS_PORT_SFLG_BINARY_IO)
	flags |= PORT_FLAG_BINARY;
    if (state & ERTS_PORT_SFLG_LINEBUF_IO)
	flags |= PORT_FLAG_LINE;

    return flags;
}

void erts_raw_port_command(Port* p, byte* buf, Uint len)
{
    int fpe_was_unmasked;

    ERTS_SMP_CHK_NO_PROC_LOCKS;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));

    if (len > (Uint) INT_MAX)
	erl_exit(ERTS_ABORT_EXIT,
		 "Absurdly large data buffer (%beu bytes) passed to"
		 "output callback of %s driver.\n",
		 len,
		 p->drv_ptr->name ? p->drv_ptr->name : "unknown");

    p->caller = NIL;
#ifdef USE_VM_PROBES
    if (DTRACE_ENABLED(driver_output)) {
        DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE);

        dtrace_port_str(p, port_str);
        DTRACE4(driver_output, "-raw-", port_str, p->name, len);
    }
#endif
    fpe_was_unmasked = erts_block_fpe();
    (*p->drv_ptr->output)((ErlDrvData)p->drv_data, (char*) buf, (int) len);
    erts_unblock_fpe(fpe_was_unmasked);
}

int async_ready(Port *p, void* data)
{
    int need_free = 1;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (p) {
	erts_aint32_t state = erts_atomic32_read_nob(&p->state);
	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));
	ASSERT(!(state & ERTS_PORT_SFLGS_DEAD));
	if (p->drv_ptr->ready_async != NULL) {
#ifdef USE_VM_PROBES
            if (DTRACE_ENABLED(driver_ready_async)) {
                DTRACE_FORMAT_COMMON_PID_AND_PORT(ERTS_PORT_GET_CONNECTED(p), p)
                DTRACE3(driver_ready_async, process_str, port_str, p->name);
            }
#endif
	    (*p->drv_ptr->ready_async)((ErlDrvData)p->drv_data, data);
	    need_free = 0;
#ifdef ERTS_SMP
	    if (p->xports)
		erts_smp_xports_unlock(p);
	    ASSERT(!p->xports);
#endif
	}
	if ((state & ERTS_PORT_SFLG_CLOSING) && is_port_ioq_empty(p)) {
	    terminate_port(p);
	}
    }
    return need_free;
}

static void
report_missing_drv_callback(Port *p, char *drv_type, char *callback)
{
    ErtsPortNames *pnp = erts_get_port_names(p->common.id);
    char *unknown = "<unknown>";
    char *drv_name = pnp->driver_name ? pnp->driver_name : unknown;
    char *prt_name = pnp->name ? pnp->name : unknown;
    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();    
    erts_dsprintf(dsbufp, "%T: %s driver '%s' ", p->common.id, drv_type, drv_name);
    if (sys_strcmp(drv_name, prt_name) != 0)
	erts_dsprintf(dsbufp, "(%s) ", prt_name);
    erts_dsprintf(dsbufp, "does not implement the %s callback!\n", callback);
    erts_free_port_names(pnp);
    erts_send_error_to_logger_nogl(dsbufp);
}

void
erts_stale_drv_select(Eterm port,
		      ErlDrvEvent hndl,
		      int mode,
		      int deselect)
{
    char *type;
    ErlDrvPort drv_port = (ErlDrvPort) erts_port_lookup_raw(port);
    ErtsPortNames *pnp = erts_get_port_names(port);
    erts_dsprintf_buf_t *dsbufp;

    switch (mode) {
    case ERL_DRV_READ | ERL_DRV_WRITE:
	type = "Input/Output";
	goto deselect;
    case ERL_DRV_WRITE:
	type = "Output";
	goto deselect;
    case ERL_DRV_READ:
	type = "Input";
    deselect:
	if (deselect) {
	    driver_select(drv_port, hndl,
			  mode | ERL_DRV_USE_NO_CALLBACK,
			  0);
	}
	break;
    default:
	type = "Event";
	if (deselect)
	    driver_event(drv_port, hndl, NULL);
	break;
    }

    dsbufp = erts_create_logger_dsbuf();
    erts_dsprintf(dsbufp,
		  "%T: %s: %s driver gone away without deselecting!\n",
		  port,
		  pnp->name ? pnp->name : "<unknown>",
		  type);
    erts_free_port_names(pnp);
    erts_send_error_to_logger_nogl(dsbufp);
}

ErtsPortNames *
erts_get_port_names(Eterm id)
{
    Port *prt = erts_port_lookup_raw(id);
    ErtsPortNames *pnp;
    ASSERT(is_nil(id) || is_internal_port(id));

    if (!prt) {
	pnp = erts_alloc(ERTS_ALC_T_PORT_NAMES, sizeof(ErtsPortNames));
	pnp->name = NULL;
	pnp->driver_name = NULL;
    }
    else {
	int do_realloc = 1;
	int len = -1;
	size_t pnp_len = sizeof(ErtsPortNames);
#ifndef DEBUG
	pnp_len += 100; /* In most cases 100 characters will be enough... */
#endif
	pnp = erts_alloc(ERTS_ALC_T_PORT_NAMES, pnp_len);
	do {
	    int nlen;
	    char *name, *driver_name;
	    if (len > 0) {
		erts_free(ERTS_ALC_T_PORT_NAMES, pnp);
		pnp_len = sizeof(ErtsPortNames) + len;
		pnp = erts_alloc(ERTS_ALC_T_PORT_NAMES, pnp_len);
	    }
	    name = prt->name;
	    len = nlen = name ? sys_strlen(name) + 1 : 0;
	    driver_name = (prt->drv_ptr ? prt->drv_ptr->name : NULL);
	    len += driver_name ? sys_strlen(driver_name) + 1 : 0;
	    if (len <= pnp_len - sizeof(ErtsPortNames)) {
		if (!name)
		    pnp->name = NULL;
		else {
		    pnp->name = ((char *) pnp) + sizeof(ErtsPortNames);
		    sys_strcpy(pnp->name, name);
		}
		if (!driver_name)
		    pnp->driver_name = NULL;
		else {
		    pnp->driver_name = (((char *) pnp)
					+ sizeof(ErtsPortNames)
					+ nlen);
		    sys_strcpy(pnp->driver_name, driver_name);
		}
		do_realloc = 0;
	    }
	} while (do_realloc);
    }
    return pnp;
}

void
erts_free_port_names(ErtsPortNames *pnp)
{
    erts_free(ERTS_ALC_T_PORT_NAMES, pnp);
}

static void schedule_port_timeout(Port *p)
{
    /*
     * Scheduling of port timeouts can be done without port locking, but
     * since the task handle is stored in the port structure and the ptimer
     * structure is protected by the port lock we require the port to be
     * locked for now...
     *
     * TODO: Implement scheduling of port timeouts without locking
     *       the port.
     * /Rickard
     */
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));
    erts_port_task_schedule(p->common.id,
			    &p->timeout_task,
			    ERTS_PORT_TASK_TIMEOUT);
}

ErlDrvTermData driver_mk_term_nil(void)
{
    return driver_term_nil;
}

void driver_report_exit(ErlDrvPort ix, int status)
{
    Port* prt = erts_drvport2port(ix, NULL);
   Eterm* hp;
   Eterm tuple;
   Process *rp;
   Eterm pid;
   ErlHeapFragment *bp = NULL;
   ErlOffHeap *ohp;
   ErtsProcLocks rp_locks = 0;
   int scheduler = erts_get_scheduler_id() != 0;

   ERTS_SMP_CHK_NO_PROC_LOCKS;
   ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

   pid = ERTS_PORT_GET_CONNECTED(prt);
   ASSERT(is_internal_pid(pid));

   rp = (scheduler
	 ? erts_proc_lookup(pid)
	 : erts_pid2proc_opt(NULL, 0, pid, 0, ERTS_P2P_FLG_SMP_INC_REFC));
   if (!rp)
       return;

   hp = erts_alloc_message_heap(3+3, &bp, &ohp, rp, &rp_locks);

   tuple = TUPLE2(hp, am_exit_status, make_small(status));
   hp += 3;
   tuple = TUPLE2(hp, prt->common.id, tuple);

   erts_queue_message(rp, &rp_locks, bp, tuple, am_undefined
#ifdef USE_VM_PROBES
		      , NIL
#endif
		      );

   erts_smp_proc_unlock(rp, rp_locks);
   if (!scheduler)
       erts_smp_proc_dec_refc(rp);
}

#define ERTS_B2T_STATES_DEF_STATES_SZ 5
#define ERTS_B2T_STATES_DEF_STATES_INC 100

struct b2t_states__ {
    int len;
    int ix;
    int used;
    ErtsBinary2TermState *state;
    ErtsBinary2TermState def_states[ERTS_B2T_STATES_DEF_STATES_SZ];
#ifdef DEBUG
    byte **org_ext;
    byte *def_org_ext[ERTS_B2T_STATES_DEF_STATES_SZ];
#endif
};

static ERTS_INLINE void
init_b2t_states(struct b2t_states__ *b2tsp)
{
    b2tsp->len = ERTS_B2T_STATES_DEF_STATES_SZ;
    b2tsp->ix = 0;
    b2tsp->used = 0;
    b2tsp->state = &b2tsp->def_states[0];
#ifdef DEBUG
    b2tsp->org_ext = &b2tsp->def_org_ext[0];
#endif
}

static ERTS_INLINE void
grow_b2t_states(struct b2t_states__ *b2tsp)
{
    if (b2tsp->state != &b2tsp->def_states[0]) {
	b2tsp->len += ERTS_B2T_STATES_DEF_STATES_INC;
	b2tsp->state = erts_realloc(ERTS_ALC_T_TMP,
				    b2tsp->state,
				    sizeof(ErtsBinary2TermState)*b2tsp->len);
#ifdef DEBUG
	b2tsp->org_ext = erts_realloc(ERTS_ALC_T_TMP,
				      b2tsp->org_ext,
				      sizeof(char *)*b2tsp->len);
#endif
    }
    else {
	ErtsBinary2TermState *new_states;
	new_states = erts_alloc(ERTS_ALC_T_TMP,
				(sizeof(ErtsBinary2TermState)
				 *ERTS_B2T_STATES_DEF_STATES_INC));
	sys_memcpy((void *) new_states,
		   (void *) b2tsp->state,
		   sizeof(ErtsBinary2TermState)*ERTS_B2T_STATES_DEF_STATES_SZ);
	b2tsp->state = new_states;
	b2tsp->len = ERTS_B2T_STATES_DEF_STATES_INC;
#ifdef DEBUG
	{
	    byte **new_org_ext = erts_alloc(ERTS_ALC_T_TMP,
					    (sizeof(char *)
					     *ERTS_B2T_STATES_DEF_STATES_INC));
	    sys_memcpy((void *) new_org_ext,
		       (void *) b2tsp->org_ext,
		       sizeof(char *)*ERTS_B2T_STATES_DEF_STATES_SZ);
	    b2tsp->org_ext = new_org_ext;
	}
#endif
    }
}

static ERTS_INLINE void
cleanup_b2t_states(struct b2t_states__ *b2tsp)
{
    if (b2tsp->state != &b2tsp->def_states[0]) {
	erts_free(ERTS_ALC_T_TMP, b2tsp->state);
#ifdef DEBUG
	erts_free(ERTS_ALC_T_TMP, b2tsp->org_ext);
#endif
    }
}


/*
 * Generate an Erlang term from data in an array (representing a simple stack
 * machine to build terms).
 * Returns:
 * 	-1 on error in input data
 *       0 if the message was not delivered (bad to pid or closed port)
 *       1 if the message was delivered successfully
 */

static int
driver_deliver_term(Eterm to, ErlDrvTermData* data, int len)
{
#define ERTS_DDT_FAIL do { res = -1; goto done; } while (0)
    Uint need = 0;
    int depth = 0;
    int res;
    Eterm *hp = NULL, *hp_start = NULL, *hp_end = NULL;
    ErlDrvTermData* ptr;
    ErlDrvTermData* ptr_end;
    DECLARE_ESTACK(stack); 
    Eterm mess = NIL;		/* keeps compiler happy */
    Process* rp = NULL;
    ErlHeapFragment *bp = NULL;
    ErlOffHeap *ohp;
    ErtsProcLocks rp_locks = 0;
    struct b2t_states__ b2t;
    int scheduler = 1; /* Silence erroneous warning... */

    init_b2t_states(&b2t);

    /*
     * We used to check port and process here. In the SMP enabled emulator,
     * however, we don't want to that until we have verified the term.
     */

    /*
     * Check ErlDrvTermData for consistency and calculate needed heap size
     * and stack depth.
     */
    ptr = data;
    ptr_end = ptr + len;

    while (ptr < ptr_end) {
	ErlDrvTermData tag = *ptr++;

#define ERTS_DDT_CHK_ENOUGH_ARGS(NEED) \
	if (ptr+((NEED)-1) >= ptr_end) ERTS_DDT_FAIL;

	switch(tag) {
	case ERL_DRV_NIL: /* no arguments */
	    depth++;
	    break;
	case ERL_DRV_ATOM: /* atom argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    if (is_not_atom(ptr[0])) ERTS_DDT_FAIL;
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_INT:  /* signed int argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
#if HALFWORD_HEAP
	    erts_bld_sint64(NULL, &need, (Sint64)ptr[0]);
#else
	    /* check for bignum */
	    if (!IS_SSMALL((Sint)ptr[0]))
		need += BIG_UINT_HEAP_SIZE;  /* use small_to_big */
#endif
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_UINT:  /* unsigned int argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
#if HALFWORD_HEAP
	    erts_bld_uint64(NULL, &need, (Uint64)ptr[0]);
#else
	    /* check for bignum */
	    if (!IS_USMALL(0, (Uint)ptr[0]))
		need += BIG_UINT_HEAP_SIZE;  /* use small_to_big */
#endif
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_INT64:  /* pointer to signed 64-bit int argument  */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    erts_bld_sint64(NULL, &need, *((Sint64 *) ptr[0]));
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_UINT64:  /* pointer to unsigned 64-bit int argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    erts_bld_uint64(NULL, &need, *((Uint64 *) ptr[0]));
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_PORT:  /* port argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    if (is_not_internal_port(ptr[0])) ERTS_DDT_FAIL;
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_BINARY: { /* ErlDrvBinary*, size, offs */
	    ErlDrvBinary* b;
	    Uint size;
	    Uint offset;
	    ERTS_DDT_CHK_ENOUGH_ARGS(3);
	    b = (ErlDrvBinary*) ptr[0];
	    size = ptr[1];
	    offset = ptr[2];
	    if (!b || size + offset > b->orig_size)
		ERTS_DDT_FAIL; /* No binary or outside the binary */
	    need += (size <= ERL_ONHEAP_BIN_LIMIT
			 ? heap_bin_size(size)
			 : PROC_BIN_SIZE);
	    ptr += 3;
	    depth++;
	    break;
	}
	case ERL_DRV_BUF2BINARY: { /* char*, size */
	    byte *bufp;
	    Uint size;
	    ERTS_DDT_CHK_ENOUGH_ARGS(2);
	    bufp = (byte *) ptr[0];
	    size = (Uint) ptr[1];
	    if (!bufp && size > 0) ERTS_DDT_FAIL; 
	    need += (size <= ERL_ONHEAP_BIN_LIMIT
		     ? heap_bin_size(size)
		     : PROC_BIN_SIZE);
	    ptr += 2;
	    depth++;
	    break;
	}
	case ERL_DRV_STRING: /* char*, length */
	    ERTS_DDT_CHK_ENOUGH_ARGS(2);
	    if ((char *) ptr[0] == NULL || (int) ptr[1] < 0) ERTS_DDT_FAIL;
	    need += ptr[1] * 2;
	    ptr += 2;
	    depth++;
	    break;
	case ERL_DRV_STRING_CONS: /* char*, length */
	    ERTS_DDT_CHK_ENOUGH_ARGS(2);
	    if ((char *) ptr[0] == NULL || (int) ptr[1] < 0) ERTS_DDT_FAIL;
	    need += ptr[1] * 2;
	    if (depth < 1) ERTS_DDT_FAIL;
	    ptr += 2;
	    break;
	case ERL_DRV_LIST: /* int */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    if ((int) ptr[0] <= 0) ERTS_DDT_FAIL;
	    need += (ptr[0]-1)*2;  /* list cells */
	    depth -= ptr[0];
	    if (depth < 0) ERTS_DDT_FAIL;
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_TUPLE: { /* int */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    if ((int) ptr[0] < 0) ERTS_DDT_FAIL;
	    need += ptr[0]+1;   /* vector positions + arityval */
	    depth -= ptr[0];
	    if (depth < 0) ERTS_DDT_FAIL;
	    ptr++;
	    depth++;
	    break;
	}
	case ERL_DRV_PID: /* pid argument */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    if (is_not_internal_pid(ptr[0])) ERTS_DDT_FAIL;
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_FLOAT: /* double * */
	    ERTS_DDT_CHK_ENOUGH_ARGS(1);
	    need += FLOAT_SIZE_OBJECT;
	    ptr++;
	    depth++;
	    break;
	case ERL_DRV_EXT2TERM: { /* char *ext, int size */
	    byte* ext;
	    Sint size;
	    Sint hsz;

	    ERTS_DDT_CHK_ENOUGH_ARGS(2);
	    ext = (byte *) ptr[0];
	    size = (Sint) ptr[1];
	    if (!ext || size <= 0)
		ERTS_DDT_FAIL;
	    if (b2t.len <= b2t.ix)
		grow_b2t_states(&b2t);
#ifdef DEBUG
	    b2t.org_ext[b2t.ix] = ext;
#endif
	    hsz = erts_binary2term_prepare(&b2t.state[b2t.ix++], ext, size);
	    if (hsz < 0)
		ERTS_DDT_FAIL; /* Invalid data */
	    need += hsz;
	    ptr += 2;
	    depth++;
	    break;
	}
	default:
	    ERTS_DDT_FAIL;
	}
#undef ERTS_DDT_CHK_ENOUGH_ARGS
    }

    if ((depth != 1) || (ptr != ptr_end))
	ERTS_DDT_FAIL;

    b2t.used = b2t.ix;
    b2t.ix = 0;

    /*
     * The term is OK. Go ahead and validate the process.
     */

    /*
     * Increase refc on proc if done from a non-scheduler thread.
     */
    scheduler = erts_get_scheduler_id() != 0;
    rp = (scheduler
	  ? erts_proc_lookup(to)
	  : erts_pid2proc_opt(NULL, 0, to, 0, ERTS_P2P_FLG_SMP_INC_REFC));
    if (!rp) {
	res = 0;
	goto done;
    }

    hp_start = hp = erts_alloc_message_heap(need, &bp, &ohp, rp, &rp_locks);
    hp_end = hp + need;

    /*
     * Interpret the instructions and build the term.
     */
    ptr = data;
    while (ptr < ptr_end) {
	ErlDrvTermData tag = *ptr++;

	switch(tag) {
	case ERL_DRV_NIL: /* no arguments */
	    mess = NIL;
	    break;

	case ERL_DRV_ATOM: /* atom argument */
	    mess = ptr[0];
	    ptr++;
	    break;

	case ERL_DRV_INT:  /* signed int argument */
#if HALFWORD_HEAP
	    mess = erts_bld_sint64(&hp, NULL, (Sint64)ptr[0]);
#else
	    if (IS_SSMALL((Sint)ptr[0]))
		mess = make_small((Sint)ptr[0]);
	    else {
		mess = small_to_big((Sint)ptr[0], hp);
		hp += BIG_UINT_HEAP_SIZE;
	    }
#endif
	    ptr++;
	    break;

	case ERL_DRV_UINT:  /* unsigned int argument */
#if HALFWORD_HEAP
	    mess = erts_bld_uint64(&hp, NULL, (Uint64)ptr[0]);
#else
	    if (IS_USMALL(0, (Uint)ptr[0]))
		mess = make_small((Uint)ptr[0]);
	    else {
		mess = uint_to_big((Uint)ptr[0], hp);
		hp += BIG_UINT_HEAP_SIZE;
	    }
#endif
	    ptr++;
	    break;

	case ERL_DRV_INT64: /* pointer to unsigned 64-bit int argument */
	    mess = erts_bld_sint64(&hp, NULL, *((Sint64 *) ptr[0]));
	    ptr++;
	    break;

	case ERL_DRV_UINT64: /* pointer to unsigned 64-bit int argument */
	    mess = erts_bld_uint64(&hp, NULL, *((Uint64 *) ptr[0]));
	    ptr++;
	    break;

	case ERL_DRV_PORT:  /* port argument */
	    mess = ptr[0];
	    ptr++;
	    break;

	case ERL_DRV_BINARY: { /* ErlDrvBinary*, size, offs */
	    ErlDrvBinary* b = (ErlDrvBinary*) ptr[0];
	    Uint size = ptr[1];
	    Uint offset = ptr[2];

	    if (size <= ERL_ONHEAP_BIN_LIMIT) {
		ErlHeapBin* hbp = (ErlHeapBin *) hp;
		hp += heap_bin_size(size);
		hbp->thing_word = header_heap_bin(size);
		hbp->size = size;
		if (size > 0) {
		    sys_memcpy((void *) hbp->data, (void *) (((byte*) b->orig_bytes) + offset), size);
		}
		mess = make_binary(hbp);
	    }
	    else {
		ProcBin* pb = (ProcBin *) hp;
		driver_binary_inc_refc(b);  /* caller will free binary */
		pb->thing_word = HEADER_PROC_BIN;
		pb->size = size;
		pb->next = ohp->first;
		ohp->first = (struct erl_off_heap_header*)pb;
		pb->val = ErlDrvBinary2Binary(b);
		pb->bytes = ((byte*) b->orig_bytes) + offset;
		pb->flags = 0;
		mess =  make_binary(pb);
		hp += PROC_BIN_SIZE;
		OH_OVERHEAD(ohp, pb->size / sizeof(Eterm));
	    }
	    ptr += 3;
	    break;
	}

	case ERL_DRV_BUF2BINARY: { /* char*, size */
	    byte *bufp = (byte *) ptr[0];
	    Uint size = (Uint) ptr[1];
	    if (size <= ERL_ONHEAP_BIN_LIMIT) {
		ErlHeapBin* hbp = (ErlHeapBin *) hp;
		hp += heap_bin_size(size);
		hbp->thing_word = header_heap_bin(size);
		hbp->size = size;
		if (size > 0) {
		    ASSERT(bufp);
		    sys_memcpy((void *) hbp->data, (void *) bufp, size);
		}
		mess = make_binary(hbp);
	    }
	    else {
		ProcBin* pbp;
		Binary* bp = erts_bin_nrml_alloc(size);
		ASSERT(bufp);
		bp->flags = 0;
		bp->orig_size = (SWord) size;
		erts_refc_init(&bp->refc, 1);
		sys_memcpy((void *) bp->orig_bytes, (void *) bufp, size);
		pbp = (ProcBin *) hp;
		hp += PROC_BIN_SIZE;
		pbp->thing_word = HEADER_PROC_BIN;
		pbp->size = size;
		pbp->next = ohp->first;
		ohp->first = (struct erl_off_heap_header*)pbp;
		pbp->val = bp;
		pbp->bytes = (byte*) bp->orig_bytes;
		pbp->flags = 0;
		OH_OVERHEAD(ohp, pbp->size / sizeof(Eterm));
		mess = make_binary(pbp);
	    }
	    ptr += 2;
	    break;
	}

	case ERL_DRV_STRING: /* char*, length */
	    mess = buf_to_intlist(&hp, (char*)ptr[0], ptr[1], NIL);
	    ptr += 2;
	    break;

	case ERL_DRV_STRING_CONS:  /* char*, length */
	    mess = ESTACK_POP(stack);
	    mess = buf_to_intlist(&hp, (char*)ptr[0], ptr[1], mess);
	    ptr += 2;
	    break;

	case ERL_DRV_LIST: { /* unsigned */
	    Uint i = (int) ptr[0]; /* i > 0 */

	    mess = ESTACK_POP(stack);
	    i--;
	    while(i > 0) {
		Eterm hd = ESTACK_POP(stack);

		mess = CONS(hp, hd, mess);
		hp += 2;
		i--;
	    }
	    ptr++;
	    break;
	}

	case ERL_DRV_TUPLE: { /* int */
	    int size = (int)ptr[0];
	    Eterm* tp = hp;

	    *tp = make_arityval(size);
	    mess = make_tuple(tp);

	    tp += size;   /* point at last element */
	    hp = tp+1;    /* advance "heap" pointer */

	    while(size--) {
		*tp-- = ESTACK_POP(stack);
	    }
	    ptr++;
	    break;
	}

	case ERL_DRV_PID: /* pid argument */
	    mess = ptr[0];
	    ptr++;
	    break;

	case ERL_DRV_FLOAT: { /* double * */
	    FloatDef f;

	    mess = make_float(hp);
	    f.fd = *((double *) ptr[0]);
	    PUT_DOUBLE(f, hp);
	    hp += FLOAT_SIZE_OBJECT;
	    ptr++;
	    break;
	}

	case ERL_DRV_EXT2TERM: /* char *ext, int size */
	    ASSERT(b2t.org_ext[b2t.ix] == (byte *) ptr[0]);
	    mess = erts_binary2term_create(&b2t.state[b2t.ix++], &hp, ohp);
	    if (mess == THE_NON_VALUE)
		ERTS_DDT_FAIL;
	    ptr += 2;
	    break;

	}
	ESTACK_PUSH(stack, mess);
    }

    res = 1;

 done:

    if (res > 0) {
	mess = ESTACK_POP(stack);  /* get resulting value */
	if (bp)
	    bp = erts_resize_message_buffer(bp, hp - hp_start, &mess, 1);
	else {
	    ASSERT(hp);
	    HRelease(rp, hp_end, hp);	    
	}
	/* send message */
	erts_queue_message(rp, &rp_locks, bp, mess, am_undefined
#ifdef USE_VM_PROBES
			   , NIL
#endif
			   );
    }
    else {
	if (b2t.ix > b2t.used)
	    b2t.used = b2t.ix;
	for (b2t.ix = 0; b2t.ix < b2t.used; b2t.ix++)
	    erts_binary2term_abort(&b2t.state[b2t.ix]);
	if (bp)
	    free_message_buffer(bp);
	else if (hp) {
	    HRelease(rp, hp_end, hp);
	}
    }
#ifdef ERTS_SMP
    if (rp) {
	if (rp_locks)
	    erts_smp_proc_unlock(rp, rp_locks);
	if (!scheduler)
	    erts_smp_proc_dec_refc(rp);
    }
#endif
    cleanup_b2t_states(&b2t);
    DESTROY_ESTACK(stack);
    return res;
#undef ERTS_DDT_FAIL
}

static ERTS_INLINE int
deliver_term_check_port(ErlDrvTermData port_id, Eterm *connected_p)
{
#ifdef ERTS_SMP
    ErtsThrPrgrDelayHandle dhndl = erts_thr_progress_unmanaged_delay();
#endif
    Port *prt = erts_port_lookup_raw((Eterm) port_id);
    erts_aint32_t state = erts_atomic32_read_nob(&prt->state);
    if (connected_p) {
#ifdef ERTS_SMP
	if (dhndl != ERTS_THR_PRGR_DHANDLE_MANAGED)
	    ETHR_MEMBAR(ETHR_LoadLoad);
#endif
	*connected_p = ERTS_PORT_GET_CONNECTED(prt);
    }
#ifdef ERTS_SMP
    if (dhndl != ERTS_THR_PRGR_DHANDLE_MANAGED) {
	erts_thr_progress_unmanaged_continue(dhndl);
	ETHR_MEMBAR(ETHR_LoadLoad|ETHR_LoadStore);
    }
#endif
    ERTS_SMP_LC_ASSERT(dhndl == ERTS_THR_PRGR_DHANDLE_MANAGED
		       ? erts_lc_is_port_locked(prt)
		       : !erts_lc_is_port_locked(prt));
    return ((state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
	    ? -1
	    : ((state & ERTS_PORT_SFLG_CLOSING) ? 0 : 1));
}

int erl_drv_output_term(ErlDrvTermData port_id, ErlDrvTermData* data, int len)
{
    /* May be called from arbitrary thread */
    Eterm connected;
    int res = deliver_term_check_port(port_id, &connected);
    if (res <= 0)
	return res;
    return driver_deliver_term(connected, data, len);
}

/*
 * driver_output_term() is deprecated, and has been scheduled for
 * removal in OTP-R17. It is replaced by erl_drv_output_term()
 * above.
 */
int 
driver_output_term(ErlDrvPort drvport, ErlDrvTermData* data, int len)
{
    erts_aint32_t state;
    Port* prt;

    ERTS_SMP_CHK_NO_PROC_LOCKS;
    /* NOTE! It *not* safe to access 'drvport' from unmanaged threads. */
    prt = erts_drvport2port(drvport, &state);
    if (!prt)
	return -1; /* invalid (dead) */
    ERTS_SMP_CHK_NO_PROC_LOCKS;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
	return -1;
    else if (state & ERTS_PORT_SFLG_CLOSING)
	return 0;

    return driver_deliver_term(ERTS_PORT_GET_CONNECTED(prt), data, len);
}

int erl_drv_send_term(ErlDrvTermData port_id,
		      ErlDrvTermData to,
		      ErlDrvTermData* data,
		      int len)
{
    /* May be called from arbitrary thread */
    int res = deliver_term_check_port(port_id, NULL);
    if (res <= 0)
	return res;
    return driver_deliver_term(to, data, len);
}

/*
 * driver_send_term() is deprecated, and has been scheduled for
 * removal in OTP-R17. It is replaced by erl_drv_send_term() above.
 */
int
driver_send_term(ErlDrvPort drvport,
		 ErlDrvTermData to,
		 ErlDrvTermData* data,
		 int len)
{
    /*
     * NOTE! It is *not* safe to access the 'drvport' parameter
     * from unmanaged threads. Also note that it is impossible
     * to make this access safe without using a less efficient
     * internal data representation for ErlDrvPort.
     */
    ERTS_SMP_CHK_NO_PROC_LOCKS;
#ifdef ERTS_SMP
    if (erts_thr_progress_is_managed_thread())
#endif
    {
	erts_aint32_t state;
	Port* prt = erts_drvport2port(drvport, &state);
	if (!prt)
	    return -1; /* invalid (dead) */
	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
	if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP)
	    return -1;
	else if (state & ERTS_PORT_SFLG_CLOSING)
	    return 0;
    }
    return driver_deliver_term(to, data, len);
}


/*
 * Output a binary with hlen bytes from hbuf as list header
 * and data is len length of bin starting from offset offs.
 */

int driver_output_binary(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen,
			 ErlDrvBinary* bin, ErlDrvSizeT offs, ErlDrvSizeT len)
{
    erts_aint32_t state;
    Port* prt = erts_drvport2port(ix, &state);

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (prt == NULL)
	return -1;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    if (state & ERTS_PORT_SFLG_CLOSING)
	return 0;

    prt->bytes_in += (hlen + len);
    erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + len));
    if (state & ERTS_PORT_SFLG_DISTRIBUTION) {
	return erts_net_message(prt,
				prt->dist_entry,
				(byte*) hbuf, hlen,
				(byte*) (bin->orig_bytes+offs), len);
    }
    else
	deliver_bin_message(prt, ERTS_PORT_GET_CONNECTED(prt), 
			    hbuf, hlen, bin, offs, len);
    return 0;
}

/* driver_output2:
** Delivers hlen bytes from hbuf to the port owner as a list;
** after that, the port settings apply, buf is sent as binary or list.
**
** Example: if hlen = 3 then the port owner will receive the data
** [H1,H2,H3 | T]
*/
int driver_output2(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen,
		   char* buf, ErlDrvSizeT len)
{
    erts_aint32_t state;
    Port* prt = erts_drvport2port(ix, &state);

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (prt == NULL)
	return -1;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

    if (state & ERTS_PORT_SFLG_CLOSING)
	return 0;
    
    prt->bytes_in += (hlen + len);
    erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + len));
    if (state & ERTS_PORT_SFLG_DISTRIBUTION) {
	if (len == 0)
	    return erts_net_message(prt,
				    prt->dist_entry,
				    NULL, 0,
				    (byte*) hbuf, hlen);
	else
	    return erts_net_message(prt,
				    prt->dist_entry,
				    (byte*) hbuf, hlen,
				    (byte*) buf, len);
    }
    else if (state & ERTS_PORT_SFLG_LINEBUF_IO)
	deliver_linebuf_message(prt, state, ERTS_PORT_GET_CONNECTED(prt),
				hbuf, hlen, buf, len);
    else
	deliver_read_message(prt, state, ERTS_PORT_GET_CONNECTED(prt),
			     hbuf, hlen, buf, len, 0);
    return 0;
}

/* Interface functions available to driver writers */

int driver_output(ErlDrvPort ix, char* buf, ErlDrvSizeT len)
{
    ERTS_SMP_CHK_NO_PROC_LOCKS;
    return driver_output2(ix, NULL, 0, buf, len);
}

int driver_outputv(ErlDrvPort ix, char* hbuf, ErlDrvSizeT hlen,
		   ErlIOVec* vec, ErlDrvSizeT skip)
{
    int n;
    ErlDrvSizeT len;
    ErlDrvSizeT size;
    SysIOVec* iov;
    ErlDrvBinary** binv;
    Port* prt;
    erts_aint32_t state;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    ASSERT(vec->size >= skip);
    if (vec->size <= skip)
	return driver_output2(ix, hbuf, hlen, NULL, 0);
    size = vec->size - skip;   /* Size of remaining bytes in vector */

    ASSERT(hlen >= 0);       /* debug only */
    if (hlen < 0)
	hlen = 0;

    prt = erts_drvport2port(ix, &state);
    if (prt == NULL)
	return -1;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));

    if (state & ERTS_PORT_SFLG_CLOSING)
	return 0;

    /* size > 0 ! */
    iov = vec->iov;
    binv = vec->binv;
    n = vec->vsize;
    /* we use do here to strip iov_len=0 from beginning */
    do {
	len = iov->iov_len;
	if (len <= skip) {
	    skip -= len;
	    iov++;
	    binv++;
	    n--;
	} else {
	    iov->iov_base += skip;
	    iov->iov_len -= skip;
	    skip = 0;
	}
    } while (skip > 0);

    /* XXX handle distribution !!! */
    prt->bytes_in += (hlen + size);
    erts_smp_atomic_add_nob(&erts_bytes_in, (erts_aint_t) (hlen + size));
    deliver_vec_message(prt, ERTS_PORT_GET_CONNECTED(prt), hbuf, hlen,
			binv, iov, n, size);
    return 0;
}

/* Copy bytes from a vector into a buffer
** input is a vector a buffer and a max length
** return bytes copied
*/
ErlDrvSizeT driver_vec_to_buf(ErlIOVec *vec, char *buf, ErlDrvSizeT len)
{
    SysIOVec* iov = vec->iov;
    int n = vec->vsize;
    ErlDrvSizeT orig_len = len;

    while(n--) {
	size_t ilen = iov->iov_len;
	if (ilen < len) {
	    sys_memcpy(buf, iov->iov_base, ilen);
	    len -= ilen;
	    buf += ilen;
	    iov++;
	}
	else {
	    sys_memcpy(buf, iov->iov_base, len);
	    return orig_len;
	}
    }
    return (orig_len - len);
}


/*
 * - driver_alloc_binary() is thread safe (efile driver depend on it).
 * - driver_realloc_binary(), and driver_free_binary() are *not* thread safe.
 */

/*
 * reference count on driver binaries...
 */

ErlDrvSInt
driver_binary_get_refc(ErlDrvBinary *dbp)
{
    Binary* bp = ErlDrvBinary2Binary(dbp);
    return (ErlDrvSInt) erts_refc_read(&bp->refc, 1);
}

ErlDrvSInt
driver_binary_inc_refc(ErlDrvBinary *dbp)
{
    Binary* bp = ErlDrvBinary2Binary(dbp);
    return (ErlDrvSInt) erts_refc_inctest(&bp->refc, 2);
}

ErlDrvSInt
driver_binary_dec_refc(ErlDrvBinary *dbp)
{
    Binary* bp = ErlDrvBinary2Binary(dbp);
    return (ErlDrvSInt) erts_refc_dectest(&bp->refc, 1);
}


/*
** Allocation/Deallocation of binary objects 
*/

ErlDrvBinary*
driver_alloc_binary(ErlDrvSizeT size)
{
    Binary* bin;

    bin = erts_bin_drv_alloc_fnf((Uint) size);
    if (!bin)
	return NULL; /* The driver write must take action */
    bin->flags = BIN_FLAG_DRV;
    erts_refc_init(&bin->refc, 1);
    bin->orig_size = (SWord) size;
    return Binary2ErlDrvBinary(bin);
}

/* Reallocate space hold by binary */

ErlDrvBinary* driver_realloc_binary(ErlDrvBinary* bin, ErlDrvSizeT size)
{
    Binary* oldbin;
    Binary* newbin;

    if (!bin) {
	erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	erts_dsprintf(dsbufp,
		      "Bad use of driver_realloc_binary(%p, %lu): "
		      "called with ",
		      bin, (unsigned long)size);
	if (!bin) {
	    erts_dsprintf(dsbufp, "NULL pointer as first argument");
	}
	erts_send_warning_to_logger_nogl(dsbufp);
	if (!bin)
	    return driver_alloc_binary(size);
    }

    oldbin = ErlDrvBinary2Binary(bin);
    newbin = (Binary *) erts_bin_realloc_fnf(oldbin, size);
    if (!newbin)
	return NULL;

    newbin->orig_size = size;
    return Binary2ErlDrvBinary(newbin);
}


void driver_free_binary(ErlDrvBinary* dbin)
{
    Binary *bin;
    if (!dbin) {
	erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();
	erts_dsprintf(dsbufp,
		      "Bad use of driver_free_binary(%p): called with "
		      "NULL pointer as argument", dbin);
	erts_send_warning_to_logger_nogl(dsbufp);
	return;
    }

    bin = ErlDrvBinary2Binary(dbin);
    if (erts_refc_dectest(&bin->refc, 0) == 0)
	erts_bin_free(bin);
}


/* 
 * Allocation/deallocation of memory for drivers 
 */

void *driver_alloc(ErlDrvSizeT size)
{
    return erts_alloc_fnf(ERTS_ALC_T_DRV, (Uint) size);
}

void *driver_realloc(void *ptr, ErlDrvSizeT size)
{
    return erts_realloc_fnf(ERTS_ALC_T_DRV, ptr, (Uint) size);
}

void driver_free(void *ptr)
{
    erts_free(ERTS_ALC_T_DRV, ptr);
}

/*
 * Port Data Lock
 */

static void
pdl_init(void)
{
}

static ERTS_INLINE void
pdl_init_refc(ErlDrvPDL pdl)
{
    erts_atomic_init_nob(&pdl->refc, 1);
}

static ERTS_INLINE ErlDrvSInt
pdl_read_refc(ErlDrvPDL pdl)
{
    erts_aint_t refc = erts_atomic_read_nob(&pdl->refc);
    ERTS_LC_ASSERT(refc >= 0);
    return (ErlDrvSInt) refc;
}

static ERTS_INLINE void
pdl_inc_refc(ErlDrvPDL pdl)
{
    erts_atomic_inc_nob(&pdl->refc);
    ERTS_LC_ASSERT(driver_pdl_get_refc(pdl) > 1);
}

static ERTS_INLINE ErlDrvSInt
pdl_inctest_refc(ErlDrvPDL pdl)
{
    erts_aint_t refc = erts_atomic_inc_read_nob(&pdl->refc);
    ERTS_LC_ASSERT(refc > 1);
    return (ErlDrvSInt) refc;
}

#if 0 /* unused */
static ERTS_INLINE void
pdl_dec_refc(ErlDrvPDL pdl)
{
    erts_atomic_dec_nob(&pdl->refc);
    ERTS_LC_ASSERT(driver_pdl_get_refc(pdl) > 0);
}
#endif

static ERTS_INLINE ErlDrvSInt
pdl_dectest_refc(ErlDrvPDL pdl)
{
    erts_aint_t refc = erts_atomic_dec_read_nob(&pdl->refc);
    ERTS_LC_ASSERT(refc >= 0);
    return (ErlDrvSInt) refc;
}

static ERTS_INLINE void pdl_destroy(ErlDrvPDL pdl)
{
    ERTS_LC_ASSERT(driver_pdl_get_refc(pdl) == 0);
    erts_mtx_destroy(&pdl->mtx);
    erts_port_dec_refc(pdl->prt);
    erts_free(ERTS_ALC_T_PORT_DATA_LOCK, pdl);
}

#ifdef ERTS_SMP

static void driver_monitor_lock_pdl(Port *p) {
    if (p->port_data_lock) {
	driver_pdl_lock(p->port_data_lock);
    }
    /* Now we either have the port lock or the port_data_lock */
    ERTS_LC_ASSERT(!p->port_data_lock
		   || erts_lc_mtx_is_locked(&(p->port_data_lock->mtx)));
    ERTS_SMP_LC_ASSERT(p->port_data_lock
		       || erts_lc_is_port_locked(p));
}

static void driver_monitor_unlock_pdl(Port *p) {
    /* We should either have the port lock or the port_data_lock */
    ERTS_LC_ASSERT(!p->port_data_lock
		   || erts_lc_mtx_is_locked(&(p->port_data_lock->mtx)));
    ERTS_SMP_LC_ASSERT(p->port_data_lock
		       || erts_lc_is_port_locked(p));
    if (p->port_data_lock) {
	driver_pdl_unlock(p->port_data_lock);
    }
}

#endif

/*
 * exported driver_pdl_* functions ...
 */

ErlDrvPDL
driver_pdl_create(ErlDrvPort dp)
{
    ErlDrvPDL pdl;
    Port *pp = erts_drvport2port(dp, NULL);
    if (!pp || pp->port_data_lock)
	return NULL;
    pdl = erts_alloc(ERTS_ALC_T_PORT_DATA_LOCK,
		     sizeof(struct erl_drv_port_data_lock));
    erts_mtx_init(&pdl->mtx, "port_data_lock");
    pdl_init_refc(pdl);
    erts_port_inc_refc(pp);
    pdl->prt = pp;
    pp->port_data_lock = pdl;
#ifdef HARDDEBUG
    erts_fprintf(stderr, "driver_pdl_create(%T) -> 0x%08X\r\n",pp->common.id,(unsigned) pdl);
#endif
    return pdl;
}

void
driver_pdl_lock(ErlDrvPDL pdl)
{
#ifdef HARDDEBUG
    erts_fprintf(stderr, "driver_pdl_lock(0x%08X)\r\n",(unsigned) pdl);
#endif
    pdl_inc_refc(pdl);
    erts_mtx_lock(&pdl->mtx);
}

void
driver_pdl_unlock(ErlDrvPDL pdl)
{
    ErlDrvSInt refc;
#ifdef HARDDEBUG
    erts_fprintf(stderr, "driver_pdl_unlock(0x%08X)\r\n",(unsigned) pdl);
#endif
    erts_mtx_unlock(&pdl->mtx);
    refc = pdl_dectest_refc(pdl);
    if (!refc)
	pdl_destroy(pdl);
}

ErlDrvSInt
driver_pdl_get_refc(ErlDrvPDL pdl)
{
    return pdl_read_refc(pdl);
}

ErlDrvSInt
driver_pdl_inc_refc(ErlDrvPDL pdl)
{
    ErlDrvSInt refc = pdl_inctest_refc(pdl);
#ifdef HARDDEBUG
    erts_fprintf(stderr, "driver_pdl_inc_refc(%p) -> %bed\r\n",
		 pdl, refc);
#endif
    return refc;
}

ErlDrvSInt
driver_pdl_dec_refc(ErlDrvPDL pdl)
{
    ErlDrvSInt refc = pdl_dectest_refc(pdl);
#ifdef HARDDEBUG
    erts_fprintf(stderr, "driver_pdl_dec_refc(%p) -> %bpd\r\n",
		 pdl, refc);
#endif
    if (!refc)
	pdl_destroy(pdl);
    return refc;
}

/* expand queue to hold n elements in tail or head */
static int expandq(ErlIOQueue* q, int n, int tail)
/* tail: 0 if make room in head, make room in tail otherwise */
{
    int h_sz;  /* room before header */
    int t_sz;  /* room after tail */
    int q_sz;  /* occupied */
    int nvsz;
    SysIOVec* niov;
    ErlDrvBinary** nbinv;

    h_sz = q->v_head - q->v_start;
    t_sz = q->v_end -  q->v_tail;
    q_sz = q->v_tail - q->v_head;

    if (tail && (n <= t_sz)) /* do we need to expand tail? */
	return 0;
    else if (!tail && (n <= h_sz))  /* do we need to expand head? */
	return 0;
    else if (n > (h_sz + t_sz)) { /* need to allocate */
	/* we may get little extra but it ok */
	nvsz = (q->v_end - q->v_start) + n; 

	niov = erts_alloc_fnf(ERTS_ALC_T_IOQ, nvsz * sizeof(SysIOVec));
	if (!niov)
	    return -1;
	nbinv = erts_alloc_fnf(ERTS_ALC_T_IOQ, nvsz * sizeof(ErlDrvBinary**));
	if (!nbinv) {
	    erts_free(ERTS_ALC_T_IOQ, (void *) niov);
	    return -1;
	}
	if (tail) {
	    sys_memcpy(niov, q->v_head, q_sz*sizeof(SysIOVec));
	    if (q->v_start != q->v_small)
		erts_free(ERTS_ALC_T_IOQ, (void *) q->v_start);
	    q->v_start = niov;
	    q->v_end = niov + nvsz;
	    q->v_head = q->v_start;
	    q->v_tail = q->v_head + q_sz;

	    sys_memcpy(nbinv, q->b_head, q_sz*sizeof(ErlDrvBinary*));
	    if (q->b_start != q->b_small)
		erts_free(ERTS_ALC_T_IOQ, (void *) q->b_start);
	    q->b_start = nbinv;
	    q->b_end = nbinv + nvsz;
	    q->b_head = q->b_start;
	    q->b_tail = q->b_head + q_sz;	
	}
	else {
	    sys_memcpy(niov+nvsz-q_sz, q->v_head, q_sz*sizeof(SysIOVec));
	    if (q->v_start != q->v_small)
		erts_free(ERTS_ALC_T_IOQ, (void *) q->v_start);
	    q->v_start = niov;
	    q->v_end = niov + nvsz;
	    q->v_tail = q->v_end;
	    q->v_head = q->v_tail - q_sz;
	    
	    sys_memcpy(nbinv+nvsz-q_sz, q->b_head, q_sz*sizeof(ErlDrvBinary*));
	    if (q->b_start != q->b_small)
		erts_free(ERTS_ALC_T_IOQ, (void *) q->b_start);
	    q->b_start = nbinv;
	    q->b_end = nbinv + nvsz;
	    q->b_tail = q->b_end;
	    q->b_head = q->b_tail - q_sz;
	}
    }
    else if (tail) {  /* move to beginning to make room in tail */
	sys_memmove(q->v_start, q->v_head, q_sz*sizeof(SysIOVec));
	q->v_head = q->v_start;
	q->v_tail = q->v_head + q_sz;
	sys_memmove(q->b_start, q->b_head, q_sz*sizeof(ErlDrvBinary*));
	q->b_head = q->b_start;
	q->b_tail = q->b_head + q_sz;
    }
    else {   /* move to end to make room */
	sys_memmove(q->v_end-q_sz, q->v_head, q_sz*sizeof(SysIOVec));
	q->v_tail = q->v_end;
	q->v_head = q->v_tail-q_sz;
	sys_memmove(q->b_end-q_sz, q->b_head, q_sz*sizeof(ErlDrvBinary*));
	q->b_tail = q->b_end;
	q->b_head = q->b_tail-q_sz;
    }

    return 0;
}



/* Put elements from vec at q tail */
int driver_enqv(ErlDrvPort ix, ErlIOVec* vec, ErlDrvSizeT skip)
{
    int n;
    size_t len;
    ErlDrvSizeT size;
    SysIOVec* iov;
    ErlDrvBinary** binv;
    ErlDrvBinary*  b;
    ErlIOQueue* q = drvport2ioq(ix);

    if (q == NULL)
	return -1;

    ASSERT(vec->size >= skip);       /* debug only */
    if (vec->size <= skip)
	return 0;
    size = vec->size - skip;

    iov = vec->iov;
    binv = vec->binv;
    n = vec->vsize;

    /* we use do here to strip iov_len=0 from beginning */
    do {
	len = iov->iov_len;
	if (len <= skip) {
	    skip -= len;
	    iov++;
	    binv++;
	    n--;
	}
	else {
	    iov->iov_base += skip;
	    iov->iov_len -= skip;
	    skip = 0;
	}
    } while(skip > 0);

    if (q->v_tail + n >= q->v_end)
	expandq(q, n, 1);

    /* Queue and reference all binaries (remove zero length items) */
    while(n--) {
	if ((len = iov->iov_len) > 0) {
	    if ((b = *binv) == NULL) { /* speical case create binary ! */
		b = driver_alloc_binary(len);
		sys_memcpy(b->orig_bytes, iov->iov_base, len);
		*q->b_tail++ = b;
		q->v_tail->iov_len = len;
		q->v_tail->iov_base = b->orig_bytes;
		q->v_tail++;
	    }
	    else {
		driver_binary_inc_refc(b);
		*q->b_tail++ = b;
		*q->v_tail++ = *iov;
	    }
	}
	iov++;
	binv++;
    }
    q->size += size;      /* update total size in queue */
    return 0;
}

/* Put elements from vec at q head */
int driver_pushqv(ErlDrvPort ix, ErlIOVec* vec, ErlDrvSizeT skip)
{
    int n;
    size_t len;
    ErlDrvSizeT size;
    SysIOVec* iov;
    ErlDrvBinary** binv;
    ErlDrvBinary* b;
    ErlIOQueue* q = drvport2ioq(ix);

    if (q == NULL)
	return -1;

    if (vec->size <= skip)
	return 0;
    size = vec->size - skip;

    iov = vec->iov;
    binv = vec->binv;
    n = vec->vsize;

    /* we use do here to strip iov_len=0 from beginning */
    do {
	len = iov->iov_len;
	if (len <= skip) {
	    skip -= len;
	    iov++;
	    binv++;
	    n--;
	}
	else {
	    iov->iov_base += skip;
	    iov->iov_len -= skip;
	    skip = 0;
	}
    } while(skip > 0);

    if (q->v_head - n < q->v_start)
	expandq(q, n, 0);

    /* Queue and reference all binaries (remove zero length items) */
    iov += (n-1);  /* move to end */
    binv += (n-1); /* move to end */
    while(n--) {
	if ((len = iov->iov_len) > 0) {
	    if ((b = *binv) == NULL) { /* speical case create binary ! */
		b = driver_alloc_binary(len);
		sys_memcpy(b->orig_bytes, iov->iov_base, len);
		*--q->b_head = b;
		q->v_head--;
		q->v_head->iov_len = len;
		q->v_head->iov_base = b->orig_bytes;
	    }
	    else {
		driver_binary_inc_refc(b);
		*--q->b_head = b;
		*--q->v_head = *iov;
	    }
	}
	iov--;
	binv--;
    }
    q->size += size;      /* update total size in queue */
    return 0;
}


/*
** Remove size bytes from queue head
** Return number of bytes that remain in queue
*/
ErlDrvSizeT driver_deq(ErlDrvPort ix, ErlDrvSizeT size)
{
    ErlIOQueue* q = drvport2ioq(ix);
    ErlDrvSizeT len;

    if ((q == NULL) || (q->size < size))
	return -1;
    q->size -= size;
    while (size > 0) {
	ASSERT(q->v_head != q->v_tail);

	len = q->v_head->iov_len;
	if (len <= size) {
	    size -= len;
	    driver_free_binary(*q->b_head);
	    *q->b_head++ = NULL;
	    q->v_head++;
	}
	else {
	    q->v_head->iov_base += size;
	    q->v_head->iov_len -= size;
	    size = 0;
	}
    }

    /* restart pointers (optimised for enq) */
    if (q->v_head == q->v_tail) {
	q->v_head = q->v_tail = q->v_start;
	q->b_head = q->b_tail = q->b_start;
    }
    return q->size;
}


ErlDrvSizeT driver_peekqv(ErlDrvPort ix, ErlIOVec *ev) {
    ErlIOQueue *q = drvport2ioq(ix);
    ASSERT(ev);

    if (! q) {
	return (ErlDrvSizeT) -1;
    } else {
	if ((ev->vsize = q->v_tail - q->v_head) == 0) {
	    ev->size = 0;
	    ev->iov = NULL;
	    ev->binv = NULL;
	} else {
	    ev->size = q->size;
	    ev->iov = q->v_head;
	    ev->binv = q->b_head;
	}
	return q->size;
    }
}

SysIOVec* driver_peekq(ErlDrvPort ix, int* vlenp)  /* length of io-vector */
{
    ErlIOQueue* q = drvport2ioq(ix);

    if (q == NULL) {
	*vlenp = -1;
	return NULL;
    }
    if ((*vlenp = (q->v_tail - q->v_head)) == 0)
	return NULL;
    return q->v_head;
}


ErlDrvSizeT driver_sizeq(ErlDrvPort ix)
{
    ErlIOQueue* q = drvport2ioq(ix);

    if (q == NULL)
	return (size_t) -1;
    return q->size;
}


/* Utils */

/* Enqueue a binary */
int driver_enq_bin(ErlDrvPort ix, ErlDrvBinary* bin,
		   ErlDrvSizeT offs, ErlDrvSizeT len)
{
    SysIOVec      iov;
    ErlIOVec      ev;

    ASSERT(len >= 0);
    if (len == 0)
	return 0;
    iov.iov_base = bin->orig_bytes + offs;
    iov.iov_len  = len;
    ev.vsize = 1;
    ev.size = len;
    ev.iov = &iov;
    ev.binv = &bin;
    return driver_enqv(ix, &ev, 0);
}

int driver_enq(ErlDrvPort ix, char* buffer, ErlDrvSizeT len)
{
    int code;
    ErlDrvBinary* bin;

    ASSERT(len >= 0);
    if (len == 0)
	return 0;
    if ((bin = driver_alloc_binary(len)) == NULL)
	return -1;
    sys_memcpy(bin->orig_bytes, buffer, len);
    code = driver_enq_bin(ix, bin, 0, len);
    driver_free_binary(bin);  /* dereference */
    return code;
}

int driver_pushq_bin(ErlDrvPort ix, ErlDrvBinary* bin,
		     ErlDrvSizeT offs, ErlDrvSizeT len)
{
    SysIOVec      iov;
    ErlIOVec      ev;

    ASSERT(len >= 0);
    if (len == 0)
	return 0;
    iov.iov_base = bin->orig_bytes + offs;
    iov.iov_len  = len;
    ev.vsize = 1;
    ev.size = len;
    ev.iov = &iov;
    ev.binv = &bin;
    return driver_pushqv(ix, &ev, 0);
}

int driver_pushq(ErlDrvPort ix, char* buffer, ErlDrvSizeT len)
{
    int code;
    ErlDrvBinary* bin;

    ASSERT(len >= 0);
    if (len == 0)
	return 0;

    if ((bin = driver_alloc_binary(len)) == NULL)
	return -1;
    sys_memcpy(bin->orig_bytes, buffer, len);
    code = driver_pushq_bin(ix, bin, 0, len);
    driver_free_binary(bin);  /* dereference */
    return code;
}

static ERTS_INLINE void
drv_cancel_timer(Port *prt)
{
#ifdef ERTS_SMP
    erts_cancel_smp_ptimer(prt->common.u.alive.ptimer);
#else
    erts_cancel_timer(&prt->common.u.alive.tm);
#endif
    if (erts_port_task_is_scheduled(&prt->timeout_task))
	erts_port_task_abort(&prt->timeout_task);
}

int driver_set_timer(ErlDrvPort ix, unsigned long t)
{
    Port* prt = erts_drvport2port(ix, NULL);

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (prt == NULL)
	return -1;

    if (prt->drv_ptr->timeout == NULL)
	return -1;
    drv_cancel_timer(prt);
#ifdef ERTS_SMP
    erts_create_smp_ptimer(&prt->common.u.alive.ptimer,
			   prt->common.id,
			   (ErlTimeoutProc) schedule_port_timeout,
			   t);
#else
    erts_set_timer(&prt->common.u.alive.tm,
		  (ErlTimeoutProc) schedule_port_timeout,
		  NULL,
		  prt,
		  t);
#endif
    return 0;
}

int driver_cancel_timer(ErlDrvPort ix)
{
    Port* prt = erts_drvport2port(ix, NULL);
    if (prt == NULL)
	return -1;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    drv_cancel_timer(prt);
    return 0;
}


int
driver_read_timer(ErlDrvPort ix, unsigned long* t)
{
    Port* prt = erts_drvport2port(ix, NULL);

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (prt == NULL)
	return -1;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
#ifdef ERTS_SMP
    *t = (prt->common.u.alive.ptimer
	  ? erts_time_left(&prt->common.u.alive.ptimer->timer.tm)
	  : 0);
#else
    *t = erts_time_left(&prt->common.u.alive.tm);
#endif
    return 0;
}

int 
driver_get_now(ErlDrvNowData *now_data)
{
    Uint mega,secs,micro;
    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (now_data == NULL) {
	return -1;
    }
    get_now(&mega,&secs,&micro);
    now_data->megasecs = (unsigned long) mega;
    now_data->secs = (unsigned long) secs;
    now_data->microsecs = (unsigned long) micro;
    return 0;
}

static void ref_to_driver_monitor(Eterm ref, ErlDrvMonitor *mon)
{
    RefThing *refp;
    ASSERT(is_internal_ref(ref));
    ASSERT(sizeof(RefThing) <= sizeof(ErlDrvMonitor));
    refp = ref_thing_ptr(ref);
    memset(mon,0,sizeof(ErlDrvMonitor));
    memcpy(mon,refp,sizeof(RefThing));
}


static int do_driver_monitor_process(Port *prt,
				     Eterm *buf,
				     ErlDrvTermData process,
				     ErlDrvMonitor *monitor)
{
    Process *rp;
    Eterm ref;

    if (prt->drv_ptr->process_exit == NULL) {
	return -1;
    }
    rp = erts_pid2proc_opt(NULL, 0,
			   (Eterm) process, ERTS_PROC_LOCK_LINK,
			   ERTS_P2P_FLG_ALLOW_OTHER_X);
    if (!rp) {
	return 1;
    }

    ref = erts_make_ref_in_buffer(buf);
    erts_add_monitor(&ERTS_P_MONITORS(prt), MON_ORIGIN, ref, rp->common.id, NIL);
    erts_add_monitor(&ERTS_P_MONITORS(rp), MON_TARGET, ref, prt->common.id, NIL);

    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
    ref_to_driver_monitor(ref,monitor);
    return 0;
}

/*
 * This can be called from a non scheduler thread iff a port_data_lock exists
 */
int driver_monitor_process(ErlDrvPort drvport,
			   ErlDrvTermData process,
			   ErlDrvMonitor *monitor)
{
    Port *prt;
    int ret;
    erts_aint32_t state;
#if !HEAP_ON_C_STACK || (defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_CHECK))
    ErtsSchedulerData *sched = erts_get_scheduler_data();
#endif

    prt = erts_thr_drvport2port_raw(drvport);

    DRV_MONITOR_LOCK_PDL(prt);

    state = erts_atomic32_read_nob(&prt->state);

    if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP) {
	DRV_MONITOR_UNLOCK_PDL(prt);
	return -1;
    }

    /* Now (in SMP) we should have either the port lock (if we have a scheduler) or the port data lock
       (if we're a driver thread) */
    ERTS_SMP_LC_ASSERT((sched != NULL || prt->port_data_lock));

#if !HEAP_ON_C_STACK
    if (!sched) {
	/* Need a separate allocation for the ref :( */
	Eterm *buf = erts_alloc(ERTS_ALC_T_TEMP_TERM,
				sizeof(Eterm)*REF_THING_SIZE);
	ret = do_driver_monitor_process(prt,buf,process,monitor);
	erts_free(ERTS_ALC_T_TEMP_TERM,buf);
    } else
#endif
    {
	DeclareTmpHeapNoproc(buf,REF_THING_SIZE);
	UseTmpHeapNoproc(REF_THING_SIZE);
	ret = do_driver_monitor_process(prt,buf,process,monitor);
	UnUseTmpHeapNoproc(REF_THING_SIZE);
    }
    DRV_MONITOR_UNLOCK_PDL(prt);
    return ret;
}

static int do_driver_demonitor_process(Port *prt, Eterm *buf,
				       const ErlDrvMonitor *monitor)
{
    Process *rp;
    Eterm ref;
    ErtsMonitor *mon;
    Eterm to;

    memcpy(buf,monitor,sizeof(Eterm)*REF_THING_SIZE);
    ref = make_internal_ref(buf);
    mon = erts_lookup_monitor(ERTS_P_MONITORS(prt), ref);
    if (mon == NULL) {
	return 1;
    }
    ASSERT(mon->type == MON_ORIGIN);
    to = mon->pid;
    ASSERT(is_internal_pid(to));
    rp = erts_pid2proc_opt(NULL,
			   0,
			   to,
			   ERTS_PROC_LOCK_LINK,
			   ERTS_P2P_FLG_ALLOW_OTHER_X);
    mon = erts_remove_monitor(&ERTS_P_MONITORS(prt), ref);
    if (mon) {
	erts_destroy_monitor(mon);
    }
    if (rp) {
	ErtsMonitor *rmon;
	rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), ref);
	erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
	if (rmon != NULL) {
	    erts_destroy_monitor(rmon);
	}
    } 
    return 0;
}

int driver_demonitor_process(ErlDrvPort drvport,
			     const ErlDrvMonitor *monitor)
{
    Port *prt;
    int ret;
    erts_aint32_t state;
#if !HEAP_ON_C_STACK || (defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_CHECK))
    ErtsSchedulerData *sched = erts_get_scheduler_data();
#endif

    prt = erts_thr_drvport2port_raw(drvport);

    DRV_MONITOR_LOCK_PDL(prt);

    state = erts_atomic32_read_nob(&prt->state);

    if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP) {
	DRV_MONITOR_UNLOCK_PDL(prt);
	return -1;
    }

    /* Now we should have either the port lock (if we have a scheduler) or the port data lock
       (if we're a driver thread) */
    ERTS_SMP_LC_ASSERT((sched != NULL || prt->port_data_lock));
#if !HEAP_ON_C_STACK
    if (!sched) {
	/* Need a separate allocation for the ref :( */
	Eterm *buf = erts_alloc(ERTS_ALC_T_TEMP_TERM,
				sizeof(Eterm)*REF_THING_SIZE);
	ret = do_driver_demonitor_process(prt,buf,monitor);
	erts_free(ERTS_ALC_T_TEMP_TERM,buf);
    } else
#endif
    {
	DeclareTmpHeapNoproc(buf,REF_THING_SIZE);
	UseTmpHeapNoproc(REF_THING_SIZE);
	ret = do_driver_demonitor_process(prt,buf,monitor);
	UnUseTmpHeapNoproc(REF_THING_SIZE);
    }
    DRV_MONITOR_UNLOCK_PDL(prt);
    return ret;
}

static ErlDrvTermData do_driver_get_monitored_process(Port *prt, Eterm *buf,
					    const ErlDrvMonitor *monitor)
{
    Eterm ref;
    ErtsMonitor *mon;
    Eterm to;

    memcpy(buf,monitor,sizeof(Eterm)*REF_THING_SIZE);
    ref = make_internal_ref(buf);
    mon = erts_lookup_monitor(ERTS_P_MONITORS(prt), ref);
    if (mon == NULL) {
	return driver_term_nil;
    }
    ASSERT(mon->type == MON_ORIGIN);
    to = mon->pid;
    ASSERT(is_internal_pid(to));
    return (ErlDrvTermData) to;
}


ErlDrvTermData driver_get_monitored_process(ErlDrvPort drvport,
					    const ErlDrvMonitor *monitor)
{
    Port *prt;
    ErlDrvTermData ret;
    erts_aint32_t state;
#if !HEAP_ON_C_STACK || (defined(ERTS_SMP) && defined(ERTS_ENABLE_LOCK_CHECK))
    ErtsSchedulerData *sched = erts_get_scheduler_data();
#endif

    prt = erts_thr_drvport2port_raw(drvport);

    DRV_MONITOR_LOCK_PDL(prt);

    state = erts_atomic32_read_nob(&prt->state);
    if (state & ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP) {
	DRV_MONITOR_UNLOCK_PDL(prt);
	return driver_term_nil;
    }

    /* Now we should have either the port lock (if we have a scheduler) or the port data lock
       (if we're a driver thread) */
    ERTS_SMP_LC_ASSERT((sched != NULL || prt->port_data_lock));

#if !HEAP_ON_C_STACK
    if (!sched) {
	/* Need a separate allocation for the ref :( */
	Eterm *buf = erts_alloc(ERTS_ALC_T_TEMP_TERM,
				sizeof(Eterm)*REF_THING_SIZE);
	ret = do_driver_get_monitored_process(prt,buf,monitor);
	erts_free(ERTS_ALC_T_TEMP_TERM,buf);
    } else
#endif
    {
	DeclareTmpHeapNoproc(buf,REF_THING_SIZE);
	UseTmpHeapNoproc(REF_THING_SIZE);
	ret = do_driver_get_monitored_process(prt,buf,monitor);
	UnUseTmpHeapNoproc(REF_THING_SIZE);
    }
    DRV_MONITOR_UNLOCK_PDL(prt);
    return ret;
}


int driver_compare_monitors(const ErlDrvMonitor *monitor1,
			    const ErlDrvMonitor *monitor2)
{
    return memcmp(monitor1,monitor2,sizeof(ErlDrvMonitor));
}

void erts_fire_port_monitor(Port *prt, Eterm ref)
{
    ErtsMonitor *rmon;
    void (*callback)(ErlDrvData drv_data, ErlDrvMonitor *monitor);
    ErlDrvMonitor drv_monitor;
    int fpe_was_unmasked;

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    ASSERT(prt->drv_ptr != NULL);    
    DRV_MONITOR_LOCK_PDL(prt);
    if (erts_lookup_monitor(ERTS_P_MONITORS(prt), ref) == NULL) {
	DRV_MONITOR_UNLOCK_PDL(prt);
	return;
    }
    callback = prt->drv_ptr->process_exit;
    ASSERT(callback != NULL);
    ref_to_driver_monitor(ref,&drv_monitor);
    DRV_MONITOR_UNLOCK_PDL(prt);
#ifdef USE_VM_PROBES
    if (DTRACE_ENABLED(driver_process_exit)) {
        DTRACE_FORMAT_COMMON_PID_AND_PORT(ERTS_PORT_GET_CONNECTED(prt), prt)
        DTRACE3(driver_process_exit, process_str, port_str, prt->name);
    }
#endif
    fpe_was_unmasked = erts_block_fpe();
    (*callback)((ErlDrvData) (prt->drv_data), &drv_monitor);
    erts_unblock_fpe(fpe_was_unmasked);
    DRV_MONITOR_LOCK_PDL(prt);
    /* remove monitor *after* callback */
    rmon = erts_remove_monitor(&ERTS_P_MONITORS(prt), ref);
    DRV_MONITOR_UNLOCK_PDL(prt);
    if (rmon) {
	erts_destroy_monitor(rmon);
    }
}


static int
driver_failure_term(ErlDrvPort ix, Eterm term, int eof)
{
    erts_aint32_t state;
    Port* prt = erts_drvport2port(ix, &state);

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    if (prt == NULL)
	return -1;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    if (eof)
	flush_linebuf_messages(prt, state);
    if (state & ERTS_PORT_SFLG_CLOSING) {
	terminate_port(prt);
    } else if (eof && (state & ERTS_PORT_SFLG_SOFT_EOF)) {
	deliver_result(prt->common.id, ERTS_PORT_GET_CONNECTED(prt), am_eof);
    } else {
	/* XXX UGLY WORK AROUND, Let erts_deliver_port_exit() terminate the port */
	if (prt->port_data_lock)
	    driver_pdl_lock(prt->port_data_lock);
	prt->ioq.size = 0;
	if (prt->port_data_lock)
	    driver_pdl_unlock(prt->port_data_lock);
	erts_deliver_port_exit(prt, prt->common.id, eof ? am_normal : term, 0);
    }
    return 0;
}



/*
** Do a (soft) exit. unlink the connected process before doing
** driver posix error or (normal)
*/
int driver_exit(ErlDrvPort ix, int err)
{
    Port* prt = erts_drvport2port(ix, NULL);
    Process* rp;
    ErtsLink *lnk, *rlnk = NULL;
    Eterm connected;

    ERTS_SMP_CHK_NO_PROC_LOCKS;
  
    if (prt == NULL)
        return -1;

    connected = ERTS_PORT_GET_CONNECTED(prt);
    rp = erts_pid2proc(NULL, 0, connected, ERTS_PROC_LOCK_LINK);
    if (rp) {
	rlnk = erts_remove_link(&ERTS_P_LINKS(rp),prt->common.id);
    }

    lnk = erts_remove_link(&ERTS_P_LINKS(prt), connected);

#ifdef ERTS_SMP
    if (rp)
	erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);
#endif

    if (rlnk != NULL) {
	erts_destroy_link(rlnk);
    }

    if (lnk != NULL) {
	erts_destroy_link(lnk);
    }

    if (err == 0)
        return driver_failure_term(ix, am_normal, 0);
    else {
        char* err_str = erl_errno_id(err);
        Eterm am_err = am_atom_put(err_str, sys_strlen(err_str));
        return driver_failure_term(ix, am_err, 0);
    }
}


int driver_failure(ErlDrvPort ix, int code)
{
    return driver_failure_term(ix, make_small(code), code == 0);
}

int driver_failure_atom(ErlDrvPort ix, char* string)
{
    Eterm am = am_atom_put(string, strlen(string));
    return driver_failure_term(ix, am, 0);
}

int driver_failure_posix(ErlDrvPort ix, int err)
{
    return driver_failure_atom(ix, erl_errno_id(err));
}

int driver_failure_eof(ErlDrvPort ix)
{
    return driver_failure_term(ix, NIL, 1);
}



ErlDrvTermData driver_mk_atom(char* string)
{
    Eterm am = am_atom_put(string, sys_strlen(string));
    ERTS_SMP_CHK_NO_PROC_LOCKS;
    return (ErlDrvTermData) am;
}

ErlDrvTermData driver_mk_port(ErlDrvPort ix)
{
    Port* prt = erts_drvport2port(ix, NULL);
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    return (ErlDrvTermData) prt->common.id;
}

ErlDrvTermData driver_connected(ErlDrvPort ix)
{
    Port* prt = erts_drvport2port(ix, NULL);
    ERTS_SMP_CHK_NO_PROC_LOCKS;
    if (prt == NULL)
	return NIL;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    return ERTS_PORT_GET_CONNECTED(prt);
}

ErlDrvTermData driver_caller(ErlDrvPort ix)
{
    Port* prt = erts_drvport2port(ix, NULL);
    ERTS_SMP_CHK_NO_PROC_LOCKS;
    if (prt == NULL)
	return NIL;
    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    return prt->caller;
}

int driver_lock_driver(ErlDrvPort ix)
{
    Port* prt = erts_drvport2port(ix, NULL);
    DE_Handle* dh;

    ERTS_SMP_CHK_NO_PROC_LOCKS;

    erts_smp_rwmtx_rwlock(&erts_driver_list_lock);

    if (prt == NULL) {
	erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
	return -1;
    }

    ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt));
    if ((dh = (DE_Handle*)prt->drv_ptr->handle ) == NULL) {
	erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
	return -1;
    }
    erts_ddll_lock_driver(dh, prt->drv_ptr->name);
    erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
    return 0;
}


static int maybe_lock_driver_list(void) 
{
    void *rec_lock;
    rec_lock = erts_smp_tsd_get(driver_list_lock_status_key);
    if (rec_lock == 0) {
	erts_smp_rwmtx_rwlock(&erts_driver_list_lock);
	return 1;
    }
    return 0;
}
static void maybe_unlock_driver_list(int doit)
{
    if (doit) {
	erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
    }
}
/* 
   These old interfaces are certainly not MT friendly. Hopefully they are only used internally,
   but you never know, so they are kept for BC. As The sys ddll code has no notion
   of locking, I use the driver list lock to mutex this from the code in erl_bif_ddll.c.
   To allow dynamic code loading in the init functions of a driver, recursive locking is 
   handled as in add_driver_entry etc.
   A TSD variable holds the last error for a thread, so that code like
   ...
   x = driver_dl_open(...);
   if (x == NULL)
      y = driver_dl_error();
   ...
   works as long as execution happens in one driver callback even in an SMP emulator. 
   Writing code using these interfaces spanning several driver callbacks between loading/lookup 
   and error handling may give undesired results...
*/
void *driver_dl_open(char * path)
{
    void *ptr;
    int res;
    int *last_error_p = erts_smp_tsd_get(driver_list_last_error_key);
    int locked = maybe_lock_driver_list();
    if ((res = erts_sys_ddll_open(path, &ptr)) == 0) {
	maybe_unlock_driver_list(locked);
	return ptr;
    } else {
	if (!last_error_p) {
	    last_error_p = erts_alloc(ERTS_ALC_T_DDLL_ERRCODES, sizeof(int));
	    erts_smp_tsd_set(driver_list_last_error_key,last_error_p);
	}
	*last_error_p = res;
	maybe_unlock_driver_list(locked);
	return NULL;
    }
}

void *driver_dl_sym(void * handle, char *func_name)
{
    void *ptr;
    int res;
    int *last_error_p = erts_smp_tsd_get(driver_list_lock_status_key);
    int locked = maybe_lock_driver_list();
    if ((res = erts_sys_ddll_sym(handle, func_name, &ptr)) == 0) {
	maybe_unlock_driver_list(locked);
	return ptr;
    } else {
	if (!last_error_p) {
	    last_error_p = erts_alloc(ERTS_ALC_T_DDLL_ERRCODES, sizeof(int));
	    erts_smp_tsd_set(driver_list_lock_status_key,last_error_p);
	}
	*last_error_p = res;
	maybe_unlock_driver_list(locked);
	return NULL;
    }
}
    
int driver_dl_close(void *handle)
{
    int res;
    int locked = maybe_lock_driver_list();
    res = erts_sys_ddll_close(handle);
    maybe_unlock_driver_list(locked);
    return res;
}

char *driver_dl_error(void) 
{
    char *res;
    int *last_error_p = erts_smp_tsd_get(driver_list_lock_status_key);
    int locked = maybe_lock_driver_list();
    res = erts_ddll_error((last_error_p != NULL) ? (*last_error_p) : ERL_DE_ERROR_UNSPECIFIED); 
    maybe_unlock_driver_list(locked);
    return res;
}


#define ERL_DRV_SYS_INFO_SIZE(LAST_FIELD) \
  (((size_t) &((ErlDrvSysInfo *) 0)->LAST_FIELD) \
   + sizeof(((ErlDrvSysInfo *) 0)->LAST_FIELD))

void
driver_system_info(ErlDrvSysInfo *sip, size_t si_size)
{
    /*
     * When adding fields in the ErlDrvSysInfo struct
     * remember to increment ERL_DRV_EXTENDED_MINOR_VERSION
     */

    /*
     * 'smp_support' is the last field in the first version
     * of ErlDrvSysInfo (introduced in driver version 1.0).
     */
    if (!sip || si_size < ERL_DRV_SYS_INFO_SIZE(smp_support)) 
	erl_exit(1,
		 "driver_system_info(%p, %ld) called with invalid arguments\n",
		 sip, si_size);

    /*
     * 'smp_support' is the last field in the first version
     * of ErlDrvSysInfo (introduced in driver version 1.0).
     */
    if (si_size >= ERL_DRV_SYS_INFO_SIZE(smp_support)) {
	sip->driver_major_version = ERL_DRV_EXTENDED_MAJOR_VERSION;
	sip->driver_minor_version = ERL_DRV_EXTENDED_MINOR_VERSION;
	sip->erts_version = ERLANG_VERSION;
	sip->otp_release = ERLANG_OTP_RELEASE;
	sip->thread_support = 
#ifdef USE_THREADS
	    1
#else
	    0
#endif
	    ;
	sip->smp_support = 
#ifdef ERTS_SMP
	    1
#else
	    0
#endif
	    ;

    }

    /*
     * 'scheduler_threads' is the last field in the second version
     * of ErlDrvSysInfo (introduced in driver version 1.1).
     */
    if (si_size >= ERL_DRV_SYS_INFO_SIZE(scheduler_threads)) {
	sip->async_threads = erts_async_max_threads;
	sip->scheduler_threads = erts_no_schedulers;
    }
    /*
     * 'nif_minor_version' is the last field in the third version
     * (driver version 1.5, NIF version 1.0) 
     */
    if (si_size >= ERL_DRV_SYS_INFO_SIZE(nif_minor_version)) {
	sip->nif_major_version = ERL_NIF_MAJOR_VERSION;
	sip->nif_minor_version = ERL_NIF_MINOR_VERSION;
    }
}


static ERTS_INLINE Port *
get_current_port(void)
{
    ErtsSchedulerData *esdp = erts_get_scheduler_data();
    ASSERT(esdp);
    ASSERT(esdp->current_port);
    return esdp->current_port;
}

/*
 * Default callbacks used if not supplied by driver.
 */

static void
no_output_callback(ErlDrvData drv_data, char *buf, ErlDrvSizeT len)
{

}

static void
no_event_callback(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data)
{
    Port *prt = get_current_port();
    report_missing_drv_callback(prt, "Event", "event()");
    driver_event((ErlDrvPort) prt, event, NULL);
}

static void
no_ready_input_callback(ErlDrvData drv_data, ErlDrvEvent event)
{
    Port *prt = get_current_port();
    report_missing_drv_callback(prt, "Input", "ready_input()");
    driver_select((ErlDrvPort) prt, event, 
		  (ERL_DRV_READ | ERL_DRV_USE_NO_CALLBACK), 0);
}

static void
no_ready_output_callback(ErlDrvData drv_data, ErlDrvEvent event)
{
    Port *prt = get_current_port();
    report_missing_drv_callback(prt, "Output", "ready_output()");
    driver_select((ErlDrvPort) prt, event,
		  (ERL_DRV_WRITE | ERL_DRV_USE_NO_CALLBACK), 0);
}

static void
no_timeout_callback(ErlDrvData drv_data)
{

}

static void
no_stop_select_callback(ErlDrvEvent event, void* private)
{
    erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf();    
    erts_dsprintf(dsbufp, "Driver does not implement stop_select callback "
			  "(event=%ld, private=%p)!\n", (long)event, private);
    erts_send_error_to_logger_nogl(dsbufp);
}


static int
init_driver(erts_driver_t *drv, ErlDrvEntry *de, DE_Handle *handle)
{
    drv->name = de->driver_name;
    ASSERT(de->extended_marker == ERL_DRV_EXTENDED_MARKER);
    ASSERT(de->major_version >= 2);
    drv->version.major = de->major_version;
    drv->version.minor = de->minor_version;
    drv->flags = de->driver_flags;
    drv->handle = handle;
#ifdef ERTS_SMP
    if (drv->flags & ERL_DRV_FLAG_USE_PORT_LOCKING)
	drv->lock = NULL;
    else {
	drv->lock = erts_alloc(ERTS_ALC_T_DRIVER_LOCK,
			       sizeof(erts_mtx_t));
	erts_mtx_init_x(drv->lock,
			"driver_lock",
#if defined(ERTS_ENABLE_LOCK_CHECK) || defined(ERTS_ENABLE_LOCK_COUNT)
			am_atom_put(drv->name, sys_strlen(drv->name))
#else
			NIL
#endif
	    );
    }
#endif
    drv->entry = de;

    drv->start = de->start;
    drv->stop = de->stop;
    drv->finish = de->finish;
    drv->flush = de->flush;
    drv->output = de->output ? de->output : no_output_callback;
    drv->outputv = de->outputv;
    drv->control = de->control;
    drv->call = de->call;
    drv->event = de->event ? de->event : no_event_callback;
    drv->ready_input = de->ready_input ? de->ready_input : no_ready_input_callback;
    drv->ready_output = de->ready_output ? de->ready_output : no_ready_output_callback;
    drv->timeout = de->timeout ? de->timeout : no_timeout_callback;
    drv->ready_async = de->ready_async;
    drv->process_exit = de->process_exit;
    if (de->stop_select)
	drv->stop_select = de->stop_select;
    else
	drv->stop_select = no_stop_select_callback;

    if (!de->init)
	return 0;
    else {
	int res;
	int fpe_was_unmasked = erts_block_fpe();
        DTRACE4(driver_init, drv->name, drv->version.major, drv->version.minor,
                drv->flags);
	res = (*de->init)();
	erts_unblock_fpe(fpe_was_unmasked);
	return res;
    }
}

void
erts_destroy_driver(erts_driver_t *drv)
{
#ifdef ERTS_SMP
    if (drv->lock) {
	erts_smp_mtx_destroy(drv->lock);
	erts_free(ERTS_ALC_T_DRIVER_LOCK, drv->lock);
    }
#endif    
    erts_free(ERTS_ALC_T_DRIVER, drv);
}

/* 
 * Functions for maintaining a list of driver_entry struct
 * Exposed in the driver interface, and therefore possibly locking directly.
 */

void add_driver_entry(ErlDrvEntry *drv){
    void *rec_lock;
    rec_lock = erts_smp_tsd_get(driver_list_lock_status_key);
    /* 
     * Ignore result of erts_add_driver_entry, the init is not
     * allowed to fail when drivers are added by drivers.
     */
    erts_add_driver_entry(drv, NULL, rec_lock != NULL); 
}

int erts_add_driver_entry(ErlDrvEntry *de, DE_Handle *handle, int driver_list_locked)
{
    erts_driver_t *dp = erts_alloc(ERTS_ALC_T_DRIVER, sizeof(erts_driver_t));
    int res;

    if (!driver_list_locked) {
	erts_smp_rwmtx_rwlock(&erts_driver_list_lock);
    }

    dp->next = driver_list;
    dp->prev = NULL;
    if (driver_list != NULL) {
	driver_list->prev = dp;
    }
    driver_list = dp;

    if (!driver_list_locked) {
	erts_smp_tsd_set(driver_list_lock_status_key, (void *) 1);
    }

    res = init_driver(dp, de, handle);

    if (res != 0) {
	/* 
	 * Remove it all again...
	 */
	driver_list = dp->next;
	if (driver_list != NULL) {
	    driver_list->prev = NULL;
	}
	erts_destroy_driver(dp);
    }
	
    if (!driver_list_locked) {
	erts_smp_tsd_set(driver_list_lock_status_key, NULL);
	erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
    }
    return res;
}

/* Not allowed for dynamic drivers */
int remove_driver_entry(ErlDrvEntry *drv)
{
    erts_driver_t *dp;
    void *rec_lock;
    
    rec_lock = erts_smp_tsd_get(driver_list_lock_status_key);
    if (rec_lock == NULL) {
	erts_smp_rwmtx_rwlock(&erts_driver_list_lock);
    }
    dp = driver_list;
    while (dp && dp->entry != drv)
	dp = dp->next;
    if (dp) {
	if (dp->handle) {
	    if (rec_lock == NULL) {
		erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
	    }
	    return -1;
	}
	if (dp->prev == NULL) {
	    driver_list = dp->next;
	} else {
	    dp->prev->next = dp->next;
	}
	if (dp->next != NULL) {
	    dp->next->prev = dp->prev;
	}
	erts_destroy_driver(dp);
	if (rec_lock == NULL) {
	    erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
	}
	return 1;
    }
    if (rec_lock == NULL) {
	erts_smp_rwmtx_rwunlock(&erts_driver_list_lock);
    }
    return 0;
}

/* very useful function that can be used in entries that are not used
 * so that not every driver writer must supply a personal version
 */
int null_func(void)
{
    return 0;
}

int
erl_drv_putenv(char *key, char *value)
{
    return erts_write_env(key, value);
}

int
erl_drv_getenv(char *key, char *value, size_t *value_size)
{
    return erts_sys_getenv(key, value, value_size);
}