]> rtime.felk.cvut.cz Git - can-usb1.git/blob - ulan/host/ul_drv/ul_drv/ul_kdmnt.c
Initializing repo
[can-usb1.git] / ulan / host / ul_drv / ul_drv / ul_kdmnt.c
1 /*******************************************************************
2   uLan Communication - uL_DRV - multiplatform uLan driver
3
4   ul_kdmnt.c    - Windows NT KMD specific support
5
6   (C) Copyright 1996-2004 by Pavel Pisa - project originator
7         http://cmp.felk.cvut.cz/~pisa
8   (C) Copyright 1996-2004 PiKRON Ltd.
9         http://www.pikron.com
10   (C) Copyright 2002-2004 Petr Smolik
11   
12
13   The uLan driver project can be used and distributed 
14   in compliance with any of next licenses
15    - GPL - GNU Public License
16      See file COPYING for details.
17    - LGPL - Lesser GNU Public License
18    - MPL - Mozilla Public License
19    - and other licenses added by project originator
20
21   Code can be modified and re-distributed under any combination
22   of the above listed licenses. If contributor does not agree with
23   some of the licenses, he/she can delete appropriate line.
24   WARNING: if you delete all lines, you are not allowed to
25   distribute code or sources in any form.
26  *******************************************************************/
27
28 //-------------------------------------------------------------------
29 //
30 // Declare forward function references
31 //
32
33 VOID UnloadDriver (IN PDRIVER_OBJECT DriverObject);
34
35 BOOLEAN ReportUsage(IN PDRIVER_OBJECT DriverObject,
36                     IN PDEVICE_OBJECT DeviceObject,
37                     IN INTERFACE_TYPE InterfaceType,
38                     IN ULONG BusNumber,
39                     IN PHYSICAL_ADDRESS PortAddress,
40                     IN LONG PortRange,
41                     IN KIRQL IRQLine,
42                     IN BOOLEAN *ConflictDetected);
43
44 //---------------------------------------------------------------------------
45 // ReportUsage
46 //
47 // Description:
48 //  This routine registers (reports) the I/O and IRQ usage for this driver.
49 //
50 // Arguments:
51 //      DriverObject    - Pointer to the driver object
52 //      DeviceObject    - Pointer to the Device object
53 //      PortAddress     - Address of I/O port used
54 //      ConflictDetected - TRUE if a resource conflict was detected.
55 //
56 // Return Value:
57 //      TRUE    - If a Resource conflict was detected
58 //      FALSE   - If no conflict was detected
59 //
60 BOOLEAN ReportUsage(IN PDRIVER_OBJECT DriverObject,
61                     IN PDEVICE_OBJECT DeviceObject,
62                     IN INTERFACE_TYPE InterfaceType,
63                     IN ULONG BusNumber,
64                     IN PHYSICAL_ADDRESS PortAddress,
65                     IN LONG PortRange,
66                     IN KIRQL IRQLine,
67                     IN BOOLEAN *ConflictDetected)
68 {
69   ULONG sizeOfResourceList;
70   PCM_RESOURCE_LIST resourceList;
71   PCM_FULL_RESOURCE_DESCRIPTOR nextFrd;
72   PCM_PARTIAL_RESOURCE_DESCRIPTOR partial;
73
74   //
75   // The size of the resource list is going to be one full descriptor
76   // which already has one partial descriptor included, plus another
77   // partial descriptor. One partial descriptor will be for the
78   // interrupt, and the other for the port addresses.
79   //
80
81   sizeOfResourceList = sizeof(CM_FULL_RESOURCE_DESCRIPTOR);
82
83   //
84   // The full resource descriptor already contains one
85   // partial.   Make room for one more.
86   //
87   // It will hold the irq "prd", and the port "prd".
88   //      ("prd" = partial resource descriptor)
89   //
90
91   sizeOfResourceList += sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR);
92
93   //
94   // Now we increment the length of the resource list by field offset
95   // of the first frd.   This will give us the length of what preceeds
96   // the first frd in the resource list.
97   //     (frd = full resource descriptor)
98   //
99
100   sizeOfResourceList += FIELD_OFFSET(CM_RESOURCE_LIST, List[0]);
101
102   resourceList = ExAllocatePool(PagedPool, sizeOfResourceList);
103
104   if (!resourceList) {
105     return FALSE;
106   }
107
108   //
109   // Zero out the list
110   //
111
112   RtlZeroMemory(resourceList, sizeOfResourceList);
113
114   resourceList->Count = 1;
115   nextFrd = &resourceList->List[0];
116
117   nextFrd->InterfaceType = InterfaceType;
118   nextFrd->BusNumber = BusNumber;
119
120   //
121   // We are going to report port addresses and interrupt
122   //
123
124   nextFrd->PartialResourceList.Count = 2;
125
126   //
127   // Now fill in the port data.  We don't wish to share
128   // this port range with anyone.
129   //
130   // Note: the port address we pass in is the one we got
131   // back from HalTranslateBusAddress.
132   //
133   // CmResourceShareDriverExclusive
134   // CmResourceShareDeviceExclusive
135   // CmResourceShareShared
136
137   partial = &nextFrd->PartialResourceList.PartialDescriptors[0];
138
139   partial->Type = CmResourceTypePort;
140   partial->ShareDisposition = CmResourceShareDeviceExclusive;
141   partial->Flags = CM_RESOURCE_PORT_IO;
142   partial->u.Port.Start = PortAddress;
143   partial->u.Port.Length = PortRange;
144
145   partial++;
146
147   //
148   // Now fill in the irq stuff.
149   //
150   // Note: for IoReportResourceUsage, the Interrupt.Level and
151   // Interrupt.Vector are bus-specific level and vector, just
152   // as we passed in to HalGetInterruptVector, not the mapped
153   // system vector we got back from HalGetInterruptVector.
154   //
155
156   partial->Type = CmResourceTypeInterrupt;
157   partial->u.Interrupt.Level = IRQLine;
158   partial->u.Interrupt.Vector = IRQLine;
159   partial->ShareDisposition = InterfaceType==Isa? 
160                                  CmResourceShareDeviceExclusive:
161                                  CmResourceShareShared;
162   partial->Flags = InterfaceType==Isa?
163                      CM_RESOURCE_INTERRUPT_LATCHED:
164                      CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE;
165
166   /* Claim resources for Driver */
167   /*IoReportResourceUsage(
168       NULL,
169       DriverObject,
170       resourceList,
171       sizeOfResourceList,
172       NULL,
173       NULL,
174       0,
175       FALSE,
176       ConflictDetected);*/
177
178   /* Claim resources for individual DeviceObject */
179   IoReportResourceUsage(
180       NULL,
181       DriverObject,
182       NULL,
183       0,
184       DeviceObject,
185       resourceList,
186       sizeOfResourceList,
187       FALSE,
188       ConflictDetected);
189
190   //
191   // The above routine sets the BOOLEAN parameter ConflictDetected
192   // to TRUE if a conflict was detected.
193   //
194
195   ExFreePool(resourceList);
196
197   return (*ConflictDetected);
198 }
199
200
201 //-------------------------------------------------------------------
202 // DriverEntry for WinNT style KMD
203 //
204 // Description:
205 //  NT device Driver Entry point
206 //
207 // Arguments:
208 //      DriverObject    - Pointer to this device's driver object
209 //      RegistryPath    - Pointer to the Unicode regsitry path name
210 //
211 // Return Value:
212 //      NTSTATUS
213 //
214 NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
215 {
216     PDEVICE_OBJECT deviceObject = NULL;
217     NTSTATUS status, ioConnectStatus;
218     UNICODE_STRING uniNtNameString;
219     UNICODE_STRING uniWin32NameString;
220     UNICODE_STRING uniRegPath;
221     INTERFACE_TYPE InterfaceType;
222     ULONG BusNumber;
223     PCI_SLOT_NUMBER SlotNumber;
224     KIRQL irql;
225     KIRQL IRQLine;
226     KIRQL IRQLevel;
227     KIRQL OldIrql;
228     KAFFINITY Affinity;
229     LONG TmpLong;
230     LONG PortRange;
231     LONG BaudRate;
232     LONG BaudBase;
233     LONG MyAddr;
234     LONG BufferSize;
235     LONG ScanForPCI;
236     ULONG MappedVector=0, AddressSpace = 1;
237     PULAN_DEVICE_EXTENSION extension;
238     BOOLEAN ResourceConflict;
239     PHYSICAL_ADDRESS InPortAddr, OutPortAddr;
240     int ChipOptions=0;
241
242     ULD_LARGE_INTEGER_0=RtlConvertLongToLargeInteger(0);
243
244     uLan_DbgPrint("uLan v" UL_DRV_VERSION " Enter the driver!\n");
245     uLan_DbgPrint("uLan: " __DATE__ " " __TIME__ "\n");
246
247     //
248     // Create counted string version of our device name.
249     //
250
251     RtlInitUnicodeString(&uniNtNameString, NT_DEVICE_NAME);
252
253     //
254     // Default configuration
255     //
256
257     ScanForPCI=0;
258     InterfaceType = Isa;
259     BusNumber = 0;
260     InPortAddr.LowPart = DEF_PORT_ADDRESS;
261     InPortAddr.HighPart = 0;
262     PortRange = DEF_PORT_RANGE;
263     IRQLine = DEF_IRQ_LINE;
264     BaudRate = DEF_BAUD_RATE;
265     BaudBase = 0;
266     BufferSize=0x8000;
267     MyAddr = DEF_MY_ADDR;
268
269     //
270     // Get the configuration information from the Registry
271     //
272
273     /* RtlInitUnicodeString(&uniRegPath,RegistryPath->Buffer); */
274     /* RtlCopyUnicodeString(&uniRegPath,RegistryPath); */
275
276     status=STATUS_SUCCESS;
277     RtlInitUnicodeString(&uniRegPath, NULL);
278     uniRegPath.MaximumLength = RegistryPath->Length + sizeof(L"\\Parameters")+1;
279     uniRegPath.Buffer = ExAllocatePool(PagedPool, uniRegPath.MaximumLength*sizeof(WCHAR));
280
281     if (!uniRegPath.Buffer) {
282         uLan_DbgPrint("uLan: ExAllocatePool failed for Path in DriverEntry\n");
283         status = STATUS_UNSUCCESSFUL;
284     }
285     else
286     {
287         RtlZeroMemory(uniRegPath.Buffer,uniRegPath.MaximumLength*sizeof(WCHAR));
288         RtlAppendUnicodeStringToString(&uniRegPath, RegistryPath);
289         RtlAppendUnicodeToString(&uniRegPath, L"\\Parameters");
290     }
291
292     if (NT_SUCCESS(status))
293         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"ScanForPCI",&ScanForPCI);
294     if (NT_SUCCESS(status))
295         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Port Address",&InPortAddr.LowPart);
296     if (NT_SUCCESS(status))
297         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Port Range",&PortRange);
298     TmpLong=IRQLine;
299     if (NT_SUCCESS(status))
300         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"IRQ Line",&TmpLong);
301     IRQLine=(KIRQL)TmpLong;
302     IRQLevel=(KIRQL)TmpLong;
303     if (NT_SUCCESS(status))
304         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Baud Rate",&BaudRate);
305     if (NT_SUCCESS(status))
306         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Baud Base",&BaudBase);
307     if (NT_SUCCESS(status))
308         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"My Addr",&MyAddr);
309     if (NT_SUCCESS(status))
310         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Buffer Size",&BufferSize);
311     TmpLong=uld_debug_flg;
312     if (NT_SUCCESS(status))
313         status=ulan_GetRegistryDword(uniRegPath.Buffer,L"Debug",&TmpLong);
314     uld_debug_flg=TmpLong;
315     if(uniRegPath.Buffer)
316         ExFreePool(uniRegPath.Buffer);
317     if (!NT_SUCCESS(status)) {
318         uLan_DbgPrint("uLan: GetConfiguration failed\n");
319         return status;
320     }
321
322    #ifdef UL_WITH_PCI
323     if(ScanForPCI){
324         pci_device_id_t *device_id;
325         uLan_DbgPrint("uLan: Calling ScanForPCICard\n");
326         if(ScanForPCICard(&BusNumber,&SlotNumber,TRUE,&device_id)){
327             /*PCI_COMMON_CONFIG PCIConfig;*/
328             /*HalGetBusData(PCIConfiguration,BusNumber,SlotNumber.u.AsULONG,
329                              PCIConfig,sizeof(PCIConfig));*/
330             uLan_DbgPrint("uLan: ScanForPCICard found slot %02X:%02X.%1X\n",
331                 (int)BusNumber,(int)SlotNumber.u.bits.DeviceNumber,
332                 (int)SlotNumber.u.bits.FunctionNumber);
333             InterfaceType=PCIBus;
334             ChipOptions=device_id->driver_data; /*0x16954000*/
335         }
336     }
337    #endif /*UL_WITH_PCI*/
338
339     //
340     // Create the device object, multi-thread access (FALSE)
341     //
342
343     status = IoCreateDevice(DriverObject, sizeof(ULAN_DEVICE_EXTENSION),
344                             &uniNtNameString, FILE_DEVICE_UNKNOWN, 0,
345                             /* TRUE */ FALSE, &deviceObject);
346
347     if (!NT_SUCCESS (status) ) {
348         uLan_DbgPrint("uLan: IoCreateDevice failed\n");
349         return status;
350     }
351
352     //
353     // Set the FLAGS field
354     //
355
356     deviceObject->Flags |= DO_BUFFERED_IO;
357
358     extension = (PULAN_DEVICE_EXTENSION) deviceObject->DeviceExtension;
359     extension->flag_CHIPOK=0;
360
361     //
362     // Claim PnP busses resources
363     //
364
365    #ifdef UL_WITH_PCI
366     if(InterfaceType!=Isa){
367         PCM_RESOURCE_LIST  AllocatedResources;
368         PCM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptor;
369         int PartialCount, i;
370         InPortAddr.LowPart=IRQLine=0;
371         status = HalAssignSlotResources(RegistryPath,NULL,DriverObject,
372                      deviceObject,InterfaceType,BusNumber,
373                      SlotNumber.u.AsULONG,&AllocatedResources);
374         
375         if(!AllocatedResources->Count)
376             status = STATUS_INSUFFICIENT_RESOURCES;
377         if(NT_SUCCESS(status)){
378             PartialCount=AllocatedResources->List[0].PartialResourceList.Count;
379             PartialDescriptor=AllocatedResources->List[0].PartialResourceList.PartialDescriptors;
380             for(i=0;i<PartialCount;i++){
381                 if((PartialDescriptor[i].Type==CmResourceTypePort)&&
382                   (!InPortAddr.LowPart)){
383                     InPortAddr=PartialDescriptor[i].u.Port.Start;
384                     PortRange=PartialDescriptor[i].u.Port.Length;
385                 }
386                 if((PartialDescriptor[i].Type==CmResourceTypeInterrupt)&&
387                   (!IRQLine)){
388                     IRQLevel=(KIRQL)PartialDescriptor[i].u.Interrupt.Level;
389                     IRQLine=(KIRQL)PartialDescriptor[i].u.Interrupt.Vector;
390                 }
391             }
392         }
393         if(!NT_SUCCESS(status)){
394             uLan_DbgPrint("uLan: HalAssignSlotResources error %X\n",status);
395             ExFreePool(AllocatedResources);
396             IoDeleteDevice(deviceObject);
397             return status;
398         }
399         if(!InPortAddr.LowPart || !IRQLine)
400             status = STATUS_INSUFFICIENT_RESOURCES;
401         uLan_DbgPrint("uLan: Found PCI resources Port=%04X IRQLevel=%02X IRQVector=%02X\n",
402                        InPortAddr.LowPart,IRQLevel,IRQLine);
403         ExFreePool(AllocatedResources);
404     }
405    #endif /*UL_WITH_PCI*/
406
407     //
408     // This call will map our IRQ to a system vector. It will also fill
409     // in the IRQL (the kernel-defined level at which our ISR will run),
410     // and affinity mask (which processors our ISR can run on).
411     //
412     // We need to do this so that when we connect to the interrupt, we
413     // can supply the kernel with this information.
414     //
415
416
417     irql=IRQLevel;
418     MappedVector = HalGetInterruptVector(
419             InterfaceType, // Interface type
420             BusNumber,     // Bus number
421             IRQLevel,      // BusInterruptLevel
422             IRQLine,       // BusInterruptVector
423             &irql,         // IRQ level
424             &Affinity      // Affinity mask
425             );
426     
427
428     //
429     // A little known Windows NT fact,
430     // If MappedVector==0, then HalGetInterruptVector failed.
431     //
432
433     
434     if (MappedVector == 0) {
435         uLan_DbgPrint("uLan: HalGetInterruptVector failed\n");
436         IoDeleteDevice(deviceObject);
437         return (STATUS_INVALID_PARAMETER);
438     }
439
440     //
441     // Save off the Irql
442     //
443
444     extension->Irql = irql;
445
446
447     uLan_DbgPrint("uLan: driver mappedvector=%d irql=%d, dispatch irql=%d\n",
448                    MappedVector,irql,DISPATCH_LEVEL);
449     if(uL_SpinLock_Irql<irql)
450                 uL_SpinLock_Irql=irql;
451     uLan_DbgPrint("uLan: spin lock irql=%d\n",uL_SpinLock_Irql);
452
453     //
454     // Translate the base port address to a system mapped address.
455     // This will be saved in the device extension after IoCreateDevice,
456     // because we use the translated port address to access the ports.
457     //
458
459     if (!HalTranslateBusAddress(InterfaceType, BusNumber, InPortAddr,
460                                 &AddressSpace, &OutPortAddr)) {
461         uLan_DbgPrint("uLan: HalTranslateBusAddress failed\n");
462         return STATUS_SOME_NOT_MAPPED;
463     }
464  
465
466     if ( NT_SUCCESS(status) ) {
467
468         //
469         // Create dispatch points for create/open, close, unload, and ioctl
470         //
471
472         DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;
473         DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchRoutine;
474         DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRoutine;
475         DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchRoutine;
476         DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchRoutine;
477         DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchRoutine;
478         DriverObject->DriverUnload = UnloadDriver;
479
480         //
481         // check if resources (ports and interrupt) are available
482         //
483
484                 
485        #ifdef UL_WITH_PCI
486         if(InterfaceType==Isa)
487        #endif /*UL_WITH_PCI*/
488         {
489             ReportUsage(DriverObject,deviceObject,InterfaceType,BusNumber,
490                         InPortAddr,PortRange,IRQLine,&ResourceConflict);
491
492             if (ResourceConflict) {
493                 uLan_DbgPrint("uLan: Couldn't get resources\n");
494                 IoDeleteDevice(deviceObject);
495                 return STATUS_INSUFFICIENT_RESOURCES;
496             }
497         }
498                 
499
500         //
501         // fill in the device extension
502         //
503         extension = (PULAN_DEVICE_EXTENSION) deviceObject->DeviceExtension;
504         extension->DeviceObject = deviceObject;
505         extension->port = OutPortAddr.LowPart;
506                 
507
508         status=ul_drv_init_ext(extension,OutPortAddr.LowPart,IRQLine,BaudRate,
509                                 BaudBase,ChipOptions,BufferSize,MyAddr);
510
511         if ( !NT_SUCCESS (status) ) {
512             uLan_DbgPrint("uLan: Initialization failed\n");
513             IoDeleteDevice(deviceObject);
514             return status;
515         }
516
517
518         KeInitializeDpc(&extension->bottom_dpc,ulan_bottom_dpc,extension);
519         KeInitializeDpc(&extension->wd_timer_dpc,ulan_wd_dpc,extension);
520         KeInitializeTimer(&extension->wd_timer);
521
522                 //
523         // connect the device driver to the IRQ
524         //
525         ioConnectStatus = IoConnectInterrupt(&extension->InterruptObject,
526                                              uld_irq_handler,   // ServiceRoutine 
527                                              extension,         // ServiceContext
528                                              NULL,              // SpinLock
529                                              MappedVector,      // Vector
530                                              irql,              // Irql 
531                                              irql,              // SynchronizeIrql
532                                              InterfaceType==Isa? Latched:
533                                                LevelSensitive,  // InterruptMode
534                                              InterfaceType==Isa? FALSE:
535                                                TRUE,            // ShareVector 
536                                              Affinity,          // ProcessorEnableMask 
537                                              FALSE);            // FloatingSave 
538
539         if ( !NT_SUCCESS (ioConnectStatus) ) {
540             uLan_DbgPrint("uLan: Couldn't connect interrupt\n");
541             IoDeleteDevice(deviceObject);
542             return ioConnectStatus;
543         }
544
545
546         uLan_DbgPrint("uLan: just about ready!\n");
547
548         //
549         // Create counted string version of our Win32 device name.
550         //
551
552         RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME);
553     
554         //
555         // Create a link from our device name to a name in the Win32 namespace.
556         //
557         
558         /* IoDeleteSymbolicLink (&uniWin32NameString); */
559         status = IoCreateSymbolicLink( &uniWin32NameString, &uniNtNameString );
560
561         if (!NT_SUCCESS(status)) {
562             uLan_DbgPrint("uLan: Couldn't create the symbolic link\n");
563             IoDeleteDevice (DriverObject->DeviceObject);
564         } else {
565
566             //
567             // Setup the Dpc for ISR routine
568             //
569                         /*
570
571             IoInitializeDpcRequest (DriverObject->DeviceObject, uLan_Dpc_Routine);
572
573             //
574             // Initialize the device (enable IRQ's, hit the hardware)
575             //
576
577             Initialize_uLan (extension);
578                     */
579
580             uLan_DbgPrint("uLan: All initialized!\n");
581             
582             #ifdef UL_INT_TST
583                         geninfoblk(extension);
584             printudrvbll(extension);
585                         extension->flag_NACTIV=1;
586                         KeRaiseIrql(DISPATCH_LEVEL,&OldIrql);
587             uld_timeout(extension);
588                         KeLowerIrql(OldIrql);
589             #endif  /* UL_INT_TST */
590
591         }
592
593     } else {
594         uLan_DbgPrint("uLan: Couldn't create the device\n");
595     }
596     return status;
597         
598 }
599
600
601
602 //---------------------------------------------------------------------------
603 // UnloadDriver
604 //
605 // Description:
606 //     Free all the allocated resources, etc.
607 //
608 // Arguments:
609 //     DriverObject - pointer to a driver object
610 // 
611 // Return Value:
612 //      None
613 // 
614 VOID UnloadDriver (IN PDRIVER_OBJECT DriverObject)
615 {
616     WCHAR                  deviceLinkBuffer[]  = DOS_DEVICE_NAME;
617     UNICODE_STRING         deviceLinkUnicodeString;
618     PULAN_DEVICE_EXTENSION extension;
619
620     extension = DriverObject->DeviceObject->DeviceExtension;
621
622     #ifdef UL_INT_TST
623     if(extension->flag_CHIPOK);
624       printudrvbll(extension);
625     #endif  /* UL_INT_TST */
626
627     //
628     // Deactivate uLan 
629     //
630
631     ul_drv_done_ext(extension);
632
633     //
634     // Disconnect IRQ and DPC sources
635     //
636         
637     IoDisconnectInterrupt (extension->InterruptObject);
638       
639     KeCancelTimer(&extension->wd_timer);
640     KeRemoveQueueDpc(&extension->wd_timer_dpc);
641     KeRemoveQueueDpc(&extension->bottom_dpc);
642                 
643     //
644     // Delete the symbolic link
645     //
646         
647
648     RtlInitUnicodeString (&deviceLinkUnicodeString, deviceLinkBuffer);
649
650     IoDeleteSymbolicLink (&deviceLinkUnicodeString);
651
652     //
653     // Delete the device object
654     //
655
656     IoDeleteDevice (DriverObject->DeviceObject);
657
658    #ifdef ENABLE_UL_MEM_CHECK
659     UL_PRINTF("MEM_CHECK: malloc-free=%d\n",
660                  (int)atomic_read(&ul_mem_check_counter));
661    #endif /* ENABLE_UL_MEM_CHECK */
662     uLan_DbgPrint ("uLan: Unloaded\n");
663     return;
664 }
665