summaryrefslogtreecommitdiffstats
path: root/private/mvdm/vdmredir/vrdlcbuf.c
blob: 6ba549c3fd401bb1917e2948eb778b3f31baf7fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
/*++

Copyright (c) 1991  Microsoft Corporation
Copyright (c) 1991  Nokia Data Systems

Module Name:

    vrdlcbuf.c

Abstract:

    The module implements the buffer management used by DOS DLC applications

    Contents:
        InitializeBufferPools
        CreateBufferPool
        DeleteBufferPool
        GetBuffers
        FreeBuffers
        CalculateBufferRequirement
        CopyFrame
        AllBuffersInPool

Author:

    Antti Saarenheimo (o-anttis) 26-DEC-1991

Notes:

    Originally, this code created a list of DOS buffers by keeping the segment
    constant, and updating the offset. For example, if a buffer pool was
    supplied starting at 0x1234:0000, buffers 0x100 bytes long, then the
    chain would be:

        1234:0000 -> 1234:0100 -> 1234:0200 -> ... -> 0000:0000

    But it turns out that some DOS DLC apps (Rumba) expect the offset to remain
    constant (at 0), and the segment to change(!). Thus, given the same buffer
    pool address, we would have a chain:

        1234:0000 -> 1244:0000 -> 1254:0000 -> ... -> 0000:0000

    As far as DOS apps are concerned, there is no difference, since the
    effective 20-bit address is the same.

    This is mainly done so that an app can take the USER_OFFSET field of a
    received buffer and glue it to the segment, without having to do any
    arithmetic

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>      // ASSERT, DbgPrint
#include <nturtl.h>
#include <windows.h>
#include <softpc.h>     // x86 virtual machine definitions
#include <vrdlctab.h>
#include <vdmredir.h>
#include <dlcapi.h>     // Official DLC API definition
#include <ntdddlc.h>    // IOCTL commands
#include <dlcio.h>      // Internal IOCTL API interface structures
#include "vrdlc.h"
#include "vrdebug.h"
#include "vrdlcdbg.h"

//
// defines
//

//
// BUFFER_2_SIZE - this is the size of the fixed part of a DOS receive Buffer 2.
// It just so happens that DOS and NT Buffer 2 (aka Next) are the same size
//

#define BUFFER_2_SIZE   sizeof(((PLLC_DOS_BUFFER)0)->Next)

//
// macros
//

//
// BUFFER_1_SIZE - return the size of the fixed part of a DOS receive Buffer 1.
// The size is dependent on whether the receive options specified contiguous or
// non-contiguous receive buffers. The size of a DOS Buffer 1 (of either type)
// is 4 bytes smaller than the equivalent NT Buffer 1 because the NEXT_FRAME
// field is absent
//

#define BUFFER_1_SIZE(contiguous)   ((contiguous) \
                                    ? sizeof(((PLLC_DOS_BUFFER)0)->Contiguous) \
                                    : sizeof(((PLLC_DOS_BUFFER)0)->NotContiguous))

//
// private prototypes
//

//
// public data
//

//
// private data
//

//
// DOS Buffer Pools - there can be one buffer pool per SAP. Protect access
// using critical section.
// There are 256 SAPs max, which breaks down into a maximum of 128 SAPs per
// adapter. We can accomodate a maximum of 2 adapters - one Token Ring (adapter
// 0) and one Ether Link (adapter 1)
//

DOS_DLC_BUFFER_POOL aBufferPools[DOS_DLC_MAX_SAPS * DOS_DLC_MAX_ADAPTERS];
CRITICAL_SECTION BufferSemaphore;


//
// functions
//

VOID
InitializeBufferPools(
    VOID
    )

/*++

Routine Description:

    Clears all buffer pools - sets structures to 0 - and initializes the buffer
    synchronization semaphore

Arguments:

    None.

Return Value:

    None.

--*/

{
    IF_DEBUG(DLC_BUFFERS) {
        DPUT("InitializeBufferPools\n");
    }

    InitializeCriticalSection(&BufferSemaphore);
    RtlZeroMemory(aBufferPools, sizeof(aBufferPools));
}


LLC_STATUS
CreateBufferPool(
    IN DWORD PoolIndex,
    IN DOS_ADDRESS dpBuffer,
    IN WORD PoolBlocks,
    IN WORD BufferSize
    )

/*++

Routine Description:

    The function initializes buffer pool for a DLC application.
    DOS DLC applications do not necessarily need to create the
    buffer pool immediately in DlcOpenSap (or DirOpenAdapter).
    We initialize the buffer pool for DOS memory mode using
    parallel pointers in flat and DOS side.

Arguments:

    PoolIndex   - SAP and adapter number (bit 0 defines 0 or 1 adapter)

    dpBuffer    - DOS pointer, space for the buffer segments. May be 0 in which
                  case the app maintains its own buffers, we just get to know
                  how many there are

    PoolBlocks  - number of 16 byte blocks which comprise the buffer pool.
                  If this is 0 then the default of 256 (*16 = 4096) is used

    BufferSize  - size of an individual buffer in bytes and an integral multiple
                  of 16. The minimum size is 80. If it is zero then the default
                  of 160 is used

Return Value:

    LLC_STATUS

        Success - LLC_STATUS_SUCCESS
                    Buffer pool for this SAP has been created

        Failure - LLC_STATUS_DUPLICATE_COMMAND
                    The buffer pool for this SAP already exists

                  LLC_STATUS_INVALID_BUFFER_LENGTH
                    The given buffer size is not a multiple of 16 or is less
                    than 80 bytes (default minimum buffer size)

                  LLC_STATUS_BUFFER_SIZE_EXCEEDED
                    The buffer pool isn't big enough to hold 1 buffer of the
                    requested size

--*/

{
    WORD BufferSizeInBlocks;
    WORD BufferCount;
    DWORD i;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT4("CreateBufferPool(PoolIndex=%#x, dpBuffer=%#x, PoolBlocks=%d, BufferSize=%d)\n",
            PoolIndex, dpBuffer, PoolBlocks, BufferSize);
    }

    //
    // An app may reinitialize the buffer with DIR.MODIFY.OPEN.PARMS but the
    // command must fail, if there are already buffers in the pool. We should
    // also check if there is a pending receive command, but we cannot do it
    // without major changes in the receive handling architecture
    //

    if (aBufferPools[PoolIndex].BufferSize) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT1("CreateBufferPool: already have buffer pool for %#x\n", PoolIndex);
        }

        return LLC_STATUS_DUPLICATE_COMMAND;
    }

    //
    //  Use the default, if the orginal value is 0
    //

    if (BufferSize == 0) {
        BufferSize = 160;
    }
    if (PoolBlocks == 0) {
        PoolBlocks = 256;
    }

    //
    //  The buffer size must be at least 80 and an even 16 bytes
    //

    if ((BufferSize < 80) || (BufferSize % 16)) {
        return LLC_STATUS_INVALID_BUFFER_LENGTH;
    }

    BufferSizeInBlocks = BufferSize / 16;
    if (BufferSizeInBlocks > PoolBlocks) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("CreateBufferPool: Error: BufferSizeInBlocks > PoolBlocks\n");
        }

        return LLC_STATUS_BUFFER_SIZE_EXCEEDED;
    }

    EnterCriticalSection(&BufferSemaphore);

    //
    // A DLC application may want to manage the buffer pool itself and to
    // provide the receive buffers with FreeBuffers, but the buffer size must
    // always be defined here.
    //

    aBufferPools[PoolIndex].BufferSize = BufferSize;
    aBufferPools[PoolIndex].dpBuffer = dpBuffer;    // may be 0!
    aBufferPools[PoolIndex].BufferCount = 0;

    //
    // if the app has actually given us a buffer to use then we initialize it
    // else the app must manage its own buffers; we just maintain the metrics.
    // Note that the app's buffer might be aligned any old way, but we have to
    // put up with it at the expense of speed
    //

    if (dpBuffer) {

        LPBYTE ptr32;

        BufferCount = PoolBlocks/BufferSizeInBlocks;

        //
        // if the number of buffers we can fit in our pool is zero then inform
        // the app that we can't proceed with this request and clear out the
        // information for this buffer pool
        //

        if (BufferCount == 0) {
            aBufferPools[PoolIndex].BufferSize = 0;
            aBufferPools[PoolIndex].dpBuffer = 0;
            aBufferPools[PoolIndex].BufferCount = 0;
            LeaveCriticalSection(&BufferSemaphore);
            return LLC_STATUS_BUFFER_SIZE_EXCEEDED;
        }

        aBufferPools[PoolIndex].BufferCount = BufferCount;
        aBufferPools[PoolIndex].MaximumBufferCount = BufferCount;

        //
        // convert the DOS address to a flat 32-bit pointer
        //

        ptr32 = (LPBYTE)DOS_PTR_TO_FLAT(dpBuffer);

        //
        // link the buffers together and initialize the headers. The headers
        // are only intialized with 2 pieces of info: the size of the buffer
        // and the pointer to the next buffer
        //

        aBufferPools[PoolIndex].dpBuffer = dpBuffer;

        //
        // update the segment only - leave the offset
        //

        dpBuffer += BufferSize / 16 * 65536;
        for (i = BufferCount; i; --i) {

            //
            // do we really need this? I don't think so: looking at the manual,
            // this field is to report size of data received, not size of
            // buffer, which we know anyway
            //

            //WRITE_WORD(((PLLC_DOS_BUFFER)ptr32)->Next.cbBuffer, BufferSize);

            //
            // if this is the last buffer then set its NextBuffer field to
            // NULL. All buffers get the size info
            //

            if (i - 1) {
                WRITE_DWORD(&((PLLC_DOS_BUFFER)ptr32)->Next.pNextBuffer, dpBuffer);

                //
                // update the segment only - leave the offset
                //

                dpBuffer += BufferSize / 16 * 65536;
                ptr32 += BufferSize;
            } else {
                WRITE_DWORD(&((PLLC_DOS_BUFFER)ptr32)->Next.pNextBuffer, NULL);
            }
        }

#if DBG
        IF_DEBUG(DLC_BUFFERS) {
            DumpDosDlcBufferPool(&aBufferPools[PoolIndex]);
        }
#endif

    }

    LeaveCriticalSection(&BufferSemaphore);
    return LLC_STATUS_SUCCESS;
}


VOID
DeleteBufferPool(
    IN DWORD PoolIndex
    )

/*++

Routine Description:

    The function deletes a buffer pool

Arguments:

    PoolIndex   - pool index based on SAP and adapter number

Return Value:

    None.

--*/

{
    IF_DEBUG(DLC_BUFFERS) {
        DPUT("DeleteBufferPool\n");
    }

    //
    // DLC.RESET for all adapters calls this 127 times
    //

    EnterCriticalSection(&BufferSemaphore);
    if (aBufferPools[PoolIndex].BufferSize != 0) {
        RtlZeroMemory(&aBufferPools[PoolIndex], sizeof(aBufferPools[PoolIndex]));
    }
    LeaveCriticalSection(&BufferSemaphore);
}


LLC_STATUS
GetBuffers(
    IN  DWORD PoolIndex,
    IN  WORD BuffersToGet,
    OUT DPLLC_DOS_BUFFER *pdpBuffer,
    OUT LPWORD pusBuffersLeft,
    IN  BOOLEAN PartialList,
    OUT PWORD BuffersGot OPTIONAL
    )

/*++

Routine Description:

    The function allocates DLC buffers. It will allocate buffers as a chain
    if >1 is requested. If PartialList is TRUE then will allocate as many
    buffers as are available up to BuffersToGet and return the number in
    BuffersGot

Arguments:

    PoolIndex       - SAP and adapter number
    BuffersToGet    - numbers of buffers to get. If this is 0, defaults to 1
    pdpBuffer       - the returned link list of LLC buffers
    pusBuffersLeft  - returned count of buffers left after this call
    PartialList     - TRUE if the caller wants a partial list
    BuffersGot      - pointer to returned number of buffers allocated

Return Value:

    LLC_STATUS
        Success - LLC_STATUS_SUCCESS
                    The requested number of buffers have been returned, or a
                    number of buffers less than the original request if
                    PartialList is TRUE

        Failure - LLC_STATUS_LOST_DATA_NO_BUFFERS
                    The request could not be satistfied - not enough buffers
                    in pool

                  LLC_STATUS_INVALID_STATION_ID
                    The request was mad to an invalid SAP

--*/

{
    PLLC_DOS_BUFFER pBuffer;
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];
    LLC_STATUS status;
    WORD n;
    WORD bufferSize;

#if DBG
    DWORD numBufs = BuffersToGet;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("GetBuffers(PoolIndex=%#02x, BuffersToGet=%d)\n", PoolIndex, BuffersToGet);
    }
#endif

    EnterCriticalSection(&BufferSemaphore);

    //
    // if the caller specified PartialList then return whatever we've got. If
    // whatever we've got is 0 then we'll default it to 1 and fail the allocation
    // since 0 is less than 1
    //

    if (PartialList) {
        if (pBufferPool->BufferCount < BuffersToGet) {
            BuffersToGet = pBufferPool->BufferCount;
        }
    }

    //
    // IBM DLC allows a default value of 1 to be used if the caller specified 0
    //

    if (!BuffersToGet) {
        ++BuffersToGet;
    }

    //
    // default the returned DOS buffer chain pointer to NULL
    //

    *pdpBuffer = 0;

    //
    // if there are no buffers defined then this is an erroneous request
    //

    if (pBufferPool->BufferSize) {

        //
        // calculate the size of the data part of the buffer. We put this value
        // in the LENGTH_IN_BUFFER field
        //

        bufferSize = pBufferPool->BufferSize
                   - (WORD)sizeof(pBuffer->Next);

        //
        // there may be no buffers left, in which case the next buffer pointer
        // (in DOS 16:16 format) will be 0 (0:0). If, on the other hand, it's
        // not 0 then we're in business: see if we can't allocate the buffers
        // requested
        //

        if (pBufferPool->dpBuffer && pBufferPool->BufferCount >= BuffersToGet) {

            pBuffer = (PLLC_DOS_BUFFER)DOS_PTR_TO_FLAT(pBufferPool->dpBuffer);
            *pdpBuffer = pBufferPool->dpBuffer;
            pBufferPool->BufferCount -= BuffersToGet;
            n = BuffersToGet;

            //
            // Eicon Access wants the size of the buffer in the buffer
            // when it is returned by BUFFER.GET. Oblige
            //

            WRITE_WORD(&pBuffer->Next.cbBuffer, bufferSize);

            //
            // we will return a chain of buffers, so we (nicely) terminate it
            // with a NULL for the last NextBuffer field. It doesn't say in
            // the lovely IBM Tech Ref whether this should be done, but its
            // probably the best thing to do. Because this buffer pool lives
            // in DOS memory, we have to use READ_POINTER and WRITE_FAR_POINTER
            // macros, lest we get an alignment fault on RISC
            //

            for (--BuffersToGet; BuffersToGet; --BuffersToGet) {
                pBuffer = (PLLC_DOS_BUFFER)READ_FAR_POINTER(&(pBuffer->pNext));
                if (pBuffer) {

                    //
                    // Eicon Access wants the size of the buffer in the buffer
                    // when it is returned by BUFFER.GET. Oblige
                    //

                    WRITE_WORD(&pBuffer->Next.cbBuffer, bufferSize);
                }
            }

            //
            // set the new buffer pool head
            //

            pBufferPool->dpBuffer = READ_DWORD(&pBuffer->pNext);

            //
            // terminate the chain
            //

            WRITE_FAR_POINTER(&pBuffer->pNext, NULL);

            status = LLC_STATUS_SUCCESS;

#if DBG
            IF_DEBUG(DLC_BUFFERS) {
                DumpDosDlcBufferChain(*pdpBuffer, numBufs ? numBufs : 1);
            }
#endif

        } else {

            //
            // if no buffers are obtained, the returned list is set to 0
            //


            status = LLC_STATUS_LOST_DATA_NO_BUFFERS;
            n = 0;
        }

        //
        // return the number of buffers left after this call. Works if we
        // allocated some or not
        //

        *pusBuffersLeft = pBufferPool->BufferCount;

    } else {

        //
        // bad SAP - no buffer pool for this one
        //

        status = LLC_STATUS_INVALID_STATION_ID;
        n = 0;
    }

    LeaveCriticalSection(&BufferSemaphore);

    //
    // if BuffersGot was specified then return the number of buffers allocated
    // and chained
    //

    if (ARGUMENT_PRESENT(BuffersGot)) {
        *BuffersGot = n;
    }

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("GetBuffers returning status=%x, BuffersLeft=%d\n", status, *pusBuffersLeft);
    }

    return status;
}


LLC_STATUS
FreeBuffers(
    IN DWORD PoolIndex,
    IN DPLLC_DOS_BUFFER dpBuffer,
    OUT LPWORD pusBuffersLeft
    )

/*++

Routine Description:

    Free a DOS buffer to a DLC buffer pool

Arguments:

    PoolIndex       - SAP and adapter number (bit0 defines 0 or 1 adapter)
    dpBuffer        - the released buffers (DOS pointer)
    pusBuffersLeft  - the number of buffers left after the free

Return Value:

--*/

{
    DPLLC_DOS_BUFFER dpBase;        // DOS pointer
    PLLC_DOS_BUFFER pNextBuffer;    // flat NT pointer
    PLLC_DOS_BUFFER pBuffer;        // flat NT pointer
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];

#if DBG
    int n = 0;
#endif

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("FreeBuffers: PoolIndex=%x dpBuffer=%x\n", PoolIndex, dpBuffer);
    }

    if (pBufferPool->BufferSize == 0) {
        return LLC_STATUS_INVALID_STATION_ID;
    }

    EnterCriticalSection(&BufferSemaphore);

    dpBase = dpBuffer;
    pNextBuffer = pBuffer = DOS_PTR_TO_FLAT(dpBuffer);

    //
    // the manual says for BUFFER.FREE (p3-4):
    //
    //  "When the buffer is placed back in the buffer pool, bytes 4 and 5
    // (buffer length) of the buffer are set to zero."
    //
    // So, we oblige
    //

    WRITE_WORD(&pBuffer->Next.cbFrame, 0);
    if (pNextBuffer) {

        //
        // count the number of buffers being freed. Hopefully, the application
        // hasn't screwed over our terminating NULL pointer
        //

        while (pNextBuffer) {
            ++pBufferPool->BufferCount;

#if DBG
            ++n;
#endif

            pBuffer = pNextBuffer;
            pNextBuffer = (PLLC_DOS_BUFFER)READ_FAR_POINTER(&pBuffer->pNext);

            //
            // see above, about bytes 4 and 5
            //

            WRITE_WORD(&pBuffer->Next.cbFrame, 0);
        }

        //
        // put the freed chain at the head of the list, after linking the
        // buffer currently at the head of the list to the end of the freed
        // chain
        //

        WRITE_DWORD(&pBuffer->pNext, pBufferPool->dpBuffer);
        pBufferPool->dpBuffer = dpBase;

#if DBG
        IF_DEBUG(DLC_BUFFERS) {
            DumpDosDlcBufferChain(dpBuffer, n);
        }

    } else {

        DPUT("ERROR: App tried to free NULL buffer chain\n");
#endif

    }

    *pusBuffersLeft = pBufferPool->BufferCount;

    if (pBufferPool->BufferCount > pBufferPool->MaximumBufferCount) {
        pBufferPool->MaximumBufferCount = pBufferPool->BufferCount;
    }

    LeaveCriticalSection(&BufferSemaphore);

    return STATUS_SUCCESS;
}


WORD
CalculateBufferRequirement(
    IN UCHAR Adapter,
    IN WORD StationId,
    IN PLLC_BUFFER pFrame,
    IN LLC_DOS_PARMS UNALIGNED * pDosParms,
    OUT PWORD BufferSize
    )

/*++

Routine Description:

    Calculate the number of DOS buffers required to hold the data received into
    an NT buffer. We have to go through this laborious phase because we need to
    know ahead of time if we have enough DOS buffers into which we will receive
    an I-Frame.

    Also, the size of DOS buffers is fixed, but the size of NT receive frame
    buffers can vary depending on the size of the received frame, the options
    requested and the 'binary buddy' allocator algorithm in the DLC driver. You
    may think that since we specify to the driver the buffer pool size in the
    DLC.OPEN.SAP call that it would allocate buffers of the specified size.
    Well, you'd be wrong: the DLC driver ignores this information and creates
    a buffer pool which can dole out variable size buffers, making my life more
    difficult than it ought to be

Arguments:

    Adapter     - which adapter we're receiving from
    StationId   - the Station Id we're receiving on
    pFrame      - pointer to the received frame in NT buffer
    pDosParms   - pointer to the original DOS RECEIVE parameters
    BufferSize  - pointer to the returned DOS buffer size

Return Value:

    WORD

--*/

{
    //
    // pBufferPool points to the DOS buffer pool for this adapter/station ID
    //

    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[GET_POOL_INDEX(Adapter, StationId)];

    //
    // dosUserLength is the USER_LENGTH value the DOS client requested when
    // the RECEIVE was submitted. This value may well be different than the
    // USER_LENGTH in the NT receive frame buffer
    //

    WORD dosUserLength = READ_WORD(&pDosParms->DosReceive.usUserLength);

    //
    // buffersRequired is the number of DOS buffers we need to allocate in order
    // to return the received frame. It will be at least 1
    //

    WORD buffersRequired = 1;

    //
    // dataSpace is the area in a DOS buffer available for data (after the
    // Buffer 1 or Buffer 2 header and the USER_LENGTH consideration)
    //

    WORD dataSpace;

    //
    // dataLeft is the amount of data in an NT buffer needing to be copied to
    // a DOS buffer
    //

    WORD dataLeft;

    //
    // calculate the number of DOS buffers required to hold the data frame
    // received into the NT buffer. Note that we can't simply use the size
    // of the received frame because we need the size of the buffer headers
    // and if the NT frame is larger than the DOS buffers then we may end
    // up with more DOS buffers required than NT buffers which in turn
    // results in more overhead which we have to factor in
    //

    WORD bufferSize = pBufferPool->BufferSize;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT("CalculateBufferRequirement\n");
    }

    //
    // calculate the amount of space in a DOS buffer after the Buffer 1 structure
    // (contiguous or non-contiguous, smoking or non-smoking, ah, ah, ah ahh-haa)
    // The buffer size MUST be large enough to hold the Buffer 1 overhead. This
    // is a FACT
    //

    dataSpace = bufferSize
              - (BUFFER_1_SIZE(
                    pFrame->Contiguous.uchOptions
                        & (LLC_CONTIGUOUS_MAC | LLC_CONTIGUOUS_DATA)
                    )
                 + dosUserLength
                );

    //
    // if there is less data space available in the first DOS receive buffer
    // than received data in the NT buffer then our first NT buffer will be
    // mapped to >1 DOS buffers: a Buffer 1 and 1 or more Buffer 2s. This is
    // before we even get to any associated Buffer 2s in the NT receive frame.
    //
    // Also: if the LLC_BREAK option is specified in the receive parameters
    // then the first data buffer will contain the header information. Note:
    // we assume this can only be for NotContiguous data, else how would we
    // know the size of the header information?
    //

    if (pFrame->Contiguous.uchOptions & LLC_BREAK) {
        if (!(pFrame->Contiguous.uchOptions & (LLC_CONTIGUOUS_MAC | LLC_CONTIGUOUS_DATA))) {
            dataLeft = pFrame->NotContiguous.cbBuffer;
        }

#if DBG
        else {
            DPUT("CalculateBufferRequirement: Error: invalid options: BREAK && CONTIGUOUS???\n");
        }
#endif

    } else if (dataSpace < pFrame->Contiguous.cbBuffer) {
        dataLeft = pFrame->Contiguous.cbBuffer - dataSpace;
    } else {

        //
        // we have enough space in the DOS buffer to copy all the received data
        // and some more
        //

        dataSpace -= pFrame->Next.cbBuffer;
        dataLeft = 0;
    }

    //
    // if there is more data in the NT buffer than we can fit in a DOS buffer,
    // either because the buffer sizes are different or due to the DOS client
    // requesting the BREAK option, then generate Buffer 2 requirements
    //

    while (dataLeft) {
        ++buffersRequired;
        dataSpace = bufferSize - (BUFFER_2_SIZE + dosUserLength);
        if (dataLeft > dataSpace) {
            dataLeft -= dataSpace;
            dataSpace = 0;
        } else {
            dataSpace -= dataLeft;
            dataLeft = 0;
        }
    }

    //
    // if the NT received frame has any associated Buffer 2 structures then
    // calculate the additional buffer requirement. Again, the NT buffers may
    // be a different size(s) than the DOS buffers.
    //
    // At this point, dataSpace is the amount of remaining data area in the
    // previous DOS buffer - Buffer 1 or Buffer 2. Use this before we allocate
    // a new DOS buffer
    //

    for (pFrame = pFrame->pNext; pFrame; pFrame = pFrame->pNext) {
        if (pFrame->Next.cbBuffer > dataSpace) {
            dataLeft = pFrame->Next.cbBuffer - dataSpace;
            dataSpace = 0;
            while (dataLeft) {
                ++buffersRequired;
                dataSpace = bufferSize - (BUFFER_2_SIZE + dosUserLength);
                if (dataLeft > dataSpace) {
                    dataLeft -= dataSpace;
                    dataSpace = 0;
                } else {
                    dataSpace -= dataLeft;
                    dataLeft = 0;
                }
            }
        } else {

            //
            // we have enough space in the DOS buffer to copy all the received data
            // and some more
            //

            dataSpace -= pFrame->Next.cbBuffer;
            dataLeft = 0;
        }
    }

    IF_DEBUG(DLC_BUFFERS) {
        DPUT1("CalculateBufferRequirement: %d buffers required\n", buffersRequired);
    }

    //
    // set the output DOS buffer size and return the number of DOS buffers
    // required
    //

    *BufferSize = bufferSize;
    return buffersRequired;
}


LLC_STATUS
CopyFrame(
    IN PLLC_BUFFER pFrame,
    IN DPLLC_DOS_BUFFER DosBuffers,
    IN WORD UserLength,
    IN WORD BufferSize,
    IN DWORD Flags
    )

/*++

Routine Description:

    Copies a received NT frame into DOS buffers. We have previously calculated
    the DOS buffer requirement and allocated that requirement

    We may copy the entire received frame or only part of it. We can only copy
    partial frames if the frame is NOT an I-Frame

    Notes:  1. the DOS buffer manager returns the orginal DOS 16:16 buffer
            pointers, we must use those original pointers, when the buffers
            are linked to each other.

            2. We do NOT chain frames - DOS DLC cannot handle frames being
            chained, and there is nothing to be gained by us chaining them
            except that we reduce the number of completed READs. However,
            we still have to generate the same number of simulated hardware
            interrupts to the VDM

            3. Unlike DOS buffer pools, NT does not deal in buffers of a
            specific size. Rather, it allocates buffers from a pool based
            on a 'binary buddy' algorithm and the size of the data to be
            returned. Therefore, there is no correspondence between the
            size of DOS buffers (for a station) and the NT buffers which
            were used to receive the data

            4. We only copy the data in this routine - whether it is deferred
            data from some prior local busy state or current data is immaterial
            to this routine. Responsibility for managing current/deferred frames
            is left to the caller

Arguments:

    pFrame      - pointer to received frame in NT buffer(s)
    DosBuffers  - DOS pointer to chain of DOS receive buffers
    UserLength  - the USER_LENGTH value specified in the DOS RECEIVE
    BufferSize  - size of a DOS buffer
    Flags       - various flags:
                    CF_CONTIGUOUS
                        Set if this frame is contiguous

                    CF_BREAK
                        Set if the DOS client requested that the buffers be
                        broken into header, data

                    CF_PARTIAL
                        Set if we are copying a partial frame - ok for non
                        I-Frames

Return Value:

    LLC_STATUS
        LLC_STATUS_SUCCESS
            All data copied from NT buffer to DOS buffer(s)

        LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
            A partial copy was performed. Some data ended up in DOS buffer(s),
            the rest is lost. Cannot be I-Frame!

--*/

{
    //
    // pDosBuffer - pointer to the DOS buffer which is usable in 32-bit mode
    //

    PLLC_DOS_BUFFER pDosBuffer = (PLLC_DOS_BUFFER)DOS_PTR_TO_FLAT(DosBuffers);

    //
    // dataSpace - amount of data space available in the current DOS buffer.
    // Initialize it for common Buffer 1 case
    //

    WORD dataSpace = BufferSize - (WORD)&(((PLLC_BUFFER)0)->Contiguous.pNextFrame);

    PBYTE pDosData;     // pointer to place in DOS buffer where we copy data TO
    PBYTE pNtData;      // corresponding place in NT buffer where we copy data FROM
    WORD headerLength;  // amount of data in headers
    WORD dataLeft;      // amount of data to be copied FROM the NT buffer
    WORD userOffset;    // offset of USER_SPACE
    WORD bufferLeft;    // amount of data left in current NT buffer
    WORD dataToCopy;    // amount of data to copy to DOS buffer
    WORD dataCopied;    // amount of data copied to Buffer 1/Buffer 2
    WORD frameLength;   // length of entire frame

    //
    // bufferOffset - used in generating the correct userOffset
    //

    WORD bufferOffset = LOWORD(DosBuffers);

    IF_DEBUG(DLC_BUFFERS) {
        DPUT5("CopyFrame: pFrame=%x DosBuffers=%x UserLength=%x Flags=%x pDosBuffer=%x\n",
                pFrame, DosBuffers, UserLength, Flags, pDosBuffer);
    }

    //
    // copy the first buffer. If the BREAK option is set then we only copy the
    // header part (ASSUMES NotContiguous)! NB: we KNOW that we can fit at least
    // this amount of data in the DOS buffer. Also: it is safe to use RtlCopyMemory ?
    //

    RtlCopyMemory(&pDosBuffer->Contiguous.cbFrame,
                  &pFrame->Contiguous.cbFrame,
                  (DWORD)&(((PLLC_BUFFER)0)->Contiguous.pNextFrame)
                  - (DWORD)&(((PLLC_BUFFER)0)->Contiguous.cbFrame)
                  );

    //
    // pDosData points to the area in the DOS buffer where the LAN header info
    // or data will go, depending on format
    //

    pDosData = &pDosBuffer->NotContiguous.cbLanHeader;

    //
    // if the CF_CONTIGUOUS flag is not set in the Flags parameter then this is
    // a NotContiguous frame. We must copy the header in 2 parts, because the
    // NT buffer contains the NEXT_FRAME field which the DOS buffer does not
    //

    if (!(Flags & CF_CONTIGUOUS)) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer is NOT CONTIGUOUS\n");
        }

        RtlCopyMemory(pDosData,
                      &pFrame->NotContiguous.cbLanHeader,
                      (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                      - (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader)
                      );

        pDosData += (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                  - (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader);

        dataSpace -= (WORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                   - (WORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader);
        userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->NotContiguous.auchDlcHeader)
                   + sizeof(((PLLC_DOS_BUFFER)0)->NotContiguous.auchDlcHeader);

        //
        // sanity check
        //

        ASSERT(userOffset == 58);

        //
        // amount of data in headers
        //

        headerLength = pFrame->NotContiguous.cbLanHeader
                     + pFrame->NotContiguous.cbDlcHeader;
    } else {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer is CONTIGUOUS\n");
        }

        userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Contiguous.uchAdapterNumber)
                   + sizeof(((PLLC_DOS_BUFFER)0)->Contiguous.uchAdapterNumber);

        //
        // sanity check
        //

        ASSERT(userOffset == 20);

        //
        // no header info in contiguous buffer
        //

        headerLength = 0;
    }

    //
    // if the CF_BREAK flag is set in the Flags parameter then the DOS app
    // requested that the first buffer (presumed NotContiguous) be broken into
    // one buffer containing just the header information and another containing
    // the data. In this case copy no more data to the first buffer
    //

    if (!(Flags & CF_BREAK)) {

        //
        // pDosData points at USER_SPACE - offset 58 for NotContiguous buffer,
        // offset 20 for Contiguous buffer. Bump it by USER_LENGTH (still don't
        // know if we ever expect anything meaningful to be placed at USER_SPACE
        // before we give the buffer to DOS
        //

        pDosData += UserLength;

        //
        // get in dataSpace the amount of space left in the DOS buffer where
        // we are able to copy data. Assume that UserLength doesn't make this
        // go negative (ie LARGE)
        //

        ASSERT(dataSpace >= UserLength);

        dataSpace -= UserLength;
    } else {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer has BREAK\n");
        }

        //
        // the DOS app requested BREAK. Set the count of data in this buffer
        // to 0. Use WRITE_WORD since it may be unaligned. Update the other
        // header fields we can't just copy from the NT buffer
        //

        WRITE_WORD(&pDosBuffer->NotContiguous.cbBuffer, 0);
        WRITE_WORD(&pDosBuffer->NotContiguous.offUserData, userOffset + bufferOffset);
        WRITE_WORD(&pDosBuffer->NotContiguous.cbUserData, UserLength);

        //
        // get the next DOS buffer in the list. There may not be one? (Don't
        // expect such a situation)
        //

        bufferOffset = READ_WORD(&pDosBuffer->pNext);
        pDosBuffer = DOS_PTR_TO_FLAT(pDosBuffer->pNext);
        if (pDosBuffer) {
            userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Next.cbUserData)
                       + sizeof(((PLLC_DOS_BUFFER)0)->Next.cbUserData);

            //
            // sanity check
            //

            ASSERT(userOffset == 12);
            dataSpace = BufferSize - (BUFFER_2_SIZE + UserLength);
            pDosData = (PBYTE)pDosBuffer + BUFFER_2_SIZE + UserLength;
        } else {

            //
            // that was the last buffer. Either there was just header data or
            // this is a partial copy and therefore not an I-Frame
            //

            IF_DEBUG(DLC_BUFFERS) {
                DPUT("CopyFrame: returning early\n");
            }

            return (Flags & CF_PARTIAL)
                ? LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
                : LLC_STATUS_SUCCESS;
        }
    }

    //
    // frameLength is length of entire frame - must appear in Buffer 1 and
    // Buffer 2s
    //

    frameLength = pFrame->Contiguous.cbFrame;

    //
    // dataLeft is the amount of data left to copy from the frame after the
    // headers have been taken care of. For a Contiguous buffer, this is the
    // same as the length of the frame; for a NotContiguous buffer, this is
    // the length of the frame - the combined length of the LAN and DLC
    // headers
    //

    dataLeft = frameLength - headerLength;

    //
    // get pointer to data in NT buffer and the amount of data to copy (from
    // rest of NT frame)
    //

    pNtData = (PBYTE)pFrame
            + pFrame->Contiguous.offUserData
            + pFrame->Contiguous.cbUserData;

    bufferLeft = pFrame->Contiguous.cbBuffer;

    //
    // dataCopied is amount of data copied to current buffer (1 or 2) and goes
    // in cbBuffer field (aka LENGTH_IN_BUFFER)
    //

    dataCopied = 0;

    //
    // we have copied all the data we could to the first buffer. While there
    // are more DOS buffers, copy data from NT buffer
    //

    do {

        //
        // calculate the amount of space available in the current buffer
        //

        if (dataSpace >= bufferLeft) {
            dataToCopy = bufferLeft;
            dataLeft -= dataToCopy;
            dataSpace -= dataToCopy;
            bufferLeft = 0;
        } else {
            dataToCopy = dataSpace;
            bufferLeft -= dataSpace;
            dataLeft -= dataSpace;
            dataSpace = 0;
        }

        //
        // copy the data. This will fill up the current DOS buffer, exhaust the
        // current NT buffer, or both
        //

        if (dataToCopy) {

            IF_DEBUG(DLC_RX_DATA) {
                DPUT3("CopyFrame: Copying %d bytes from %x to %x\n", dataToCopy, pNtData, pDosData);
            }

            RtlCopyMemory(pDosData, pNtData, dataToCopy);

            //
            // dataCopied accumulates until we fill a DOS buffer
            //

            dataCopied += dataToCopy;

            //
            // update to- and from- pointers for next go round loop
            //

            pDosData += dataToCopy;
            pNtData += dataToCopy;
        }

        //
        // we have run out of space in a DOS buffer, or out of data to copy from
        // the NT buffer
        //

        if (dataLeft) {

            //
            // we think there is data left to copy
            //

            if (!bufferLeft) {

                //
                // finished current NT buffer. Get next one
                //

                pFrame = pFrame->pNext;
                if (pFrame) {
                    bufferLeft = pFrame->Next.cbBuffer;
                    pNtData = (PBYTE)pFrame
                            + pFrame->Contiguous.offUserData
                            + pFrame->Contiguous.cbUserData;

                    IF_DEBUG(DLC_RX_DATA) {
                        DPUT3("CopyFrame: new NT buffer @%x pNtData @%x bufferLeft=%d\n",
                                pFrame, pNtData, bufferLeft);
                    }

                } else {

                    //
                    // no more NT buffers. Is this a partial copy?
                    //

                    DPUT("*** ERROR: dataLeft && no more NT buffers ***\n");
                    ASSERT(Flags & CF_PARTIAL);
                    break;
                }
            }
            if (!dataSpace) {

                //
                // update the current DOS buffer header (it doesn't matter that
                // we use Contiguous, NotContiguous, or Next: these fields are
                // present in all 3 buffer types)
                //

                WRITE_WORD(&pDosBuffer->Contiguous.cbFrame, frameLength);
                WRITE_WORD(&pDosBuffer->Contiguous.cbBuffer, dataCopied);
                WRITE_WORD(&pDosBuffer->Contiguous.offUserData, userOffset + bufferOffset);
                WRITE_WORD(&pDosBuffer->Contiguous.cbUserData, UserLength);

                //
                // and get the next one
                //

                bufferOffset = READ_WORD(&pDosBuffer->pNext);
                pDosBuffer = DOS_PTR_TO_FLAT(pDosBuffer->pNext);

                //
                // if we have another DOS buffer, then it is a Next buffer. Get the
                // buffer variables
                //

                if (pDosBuffer) {
                    pDosData = (PBYTE)pDosBuffer + BUFFER_2_SIZE + UserLength;
                    userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Next.cbUserData)
                               + sizeof(((PLLC_DOS_BUFFER)0)->Next.cbUserData);

                    //
                    // sanity check
                    //

                    ASSERT(userOffset == 12);

                    //
                    // get new available space (constant)
                    //

                    dataSpace = BufferSize - (BUFFER_2_SIZE + UserLength);
                    dataCopied = 0;

                    IF_DEBUG(DLC_RX_DATA) {
                        DPUT3("CopyFrame: new DOS buffer @%x pDosData @%x dataSpace=%d\n",
                                pDosBuffer, pDosData, dataSpace);
                    }

                } else {

                    //
                    // no more DOS buffers. Is this a partial copy?
                    //

                    DPUT("*** ERROR: dataLeft && no more DOS buffers ***\n");
                    ASSERT(Flags & CF_PARTIAL);
                    break;
                }
            }
        } else {

            //
            // update the current DOS buffer header
            //

            WRITE_WORD(&pDosBuffer->Contiguous.cbFrame, frameLength);
            WRITE_WORD(&pDosBuffer->Contiguous.cbBuffer, dataCopied);
            WRITE_WORD(&pDosBuffer->Contiguous.offUserData, userOffset + bufferOffset);
            WRITE_WORD(&pDosBuffer->Contiguous.cbUserData, UserLength);
        }

    } while ( dataLeft );

    //
    // if CF_PARTIAL set then we knew we were copying a partial frame before
    // we got here
    //

    return (Flags & CF_PARTIAL)
        ? LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
        : LLC_STATUS_SUCCESS;
}


BOOLEAN
AllBuffersInPool(
    IN DWORD PoolIndex
    )

/*++

Routine Description:

    Returns TRUE if all buffers that a pool has held are currently in the pool.

    Once a buffer has been added to a pool, it cannot be removed, saved by not
    returning it to the pool. Hence this function will always return TRUE if
    the app is well-behaved and all buffers that have been placed in the pool
    are currently in the pool

Arguments:

    PoolIndex   - pool id

Return Value:

    BOOLEAN
        TRUE    - all buffers back
        FALSE   - buffer pool currently contains less than full number of buffers

--*/

{
    BOOLEAN result;
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];

    EnterCriticalSection(&BufferSemaphore);
    result = (pBufferPool->BufferCount == pBufferPool->MaximumBufferCount);
    LeaveCriticalSection(&BufferSemaphore);
    return result;
}