[SHELL32]
[reactos.git] / reactos / dll / win32 / beepmidi / beepmidi.c
1 /*
2 BeepMidi :: beep.sys MIDI player
3
4 (c) Andrew Greenwood, 2007.
5
6 Released as open-source software. You may copy, re-distribute and modify
7 this software, provided this copyright notice remains intact.
8
9 Please see the included README.TXT for more information
10
11 HISTORY :
12 16th January 2007 Started
13 17th January 2007 Polyphony support and threading added
14 18th January 2007 Made threading optional, added comments
15 */
16
17 /* The timeslice to allocate for all playing notes (in milliseconds) */
18 #define TIMESLICE_SIZE 60
19
20 /*
21 If this is defined, notes are added to the playing list, even if
22 they already exist. As a result, the note will sound twice during
23 each timeslice. Also each note on will require a corresponding note
24 off event.
25 */
26 #define ALLOW_DUPLICATE_NOTES
27
28 /*
29 The maximum number of notes that may be playing at any one time.
30 Higher values result in a messier sound as all the frequencies get
31 mashed together. Do not set this below 2. Recommended = 4
32 */
33 #define POLYPHONY 3
34
35 /*
36 Define CONTINUOUS_NOTES to perform note playback in a separate thread.
37 This was originally the intended behaviour, but after experimentation
38 doesn't sound as good for MIDI files which have a lot going on. If not
39 defined, all playing notes are output in sequence as a new note starts.
40 */
41 #define CONTINUOUS_NOTES
42
43 #define WIN32_NO_STATUS
44 #include <windows.h>
45 #define NTOS_MODE_USER
46 #include <ndk/iofuncs.h>
47 #include <ndk/obfuncs.h>
48 #include <ndk/rtlfuncs.h>
49 #include <stdio.h>
50 #include <ntddbeep.h>
51 #include <math.h>
52
53 #include <mmddk.h>
54 #include <mmsystem.h>
55
56 /*#define DPRINT printf*/
57 #define DPRINT FakePrintf
58
59 /* A few MIDI command categories */
60 #define MIDI_NOTE_OFF 0x80
61 #define MIDI_NOTE_ON 0x90
62 #define MIDI_CONTROL_CHANGE 0xB0
63 #define MIDI_PROGRAM 0xC0
64 #define MIDI_PITCH_BEND 0xE0
65 #define MIDI_SYSTEM 0xFF
66
67 /* Specific commands */
68 #define MIDI_RESET 0xFF
69
70
71 typedef struct _NoteNode
72 {
73 struct _NoteNode* next;
74 struct _NoteNode* previous;
75
76 UCHAR note;
77 UCHAR velocity; /* 0 is note-off */
78 } NoteNode;
79
80 typedef struct _DeviceInfo
81 {
82 HDRVR mme_handle;
83 HANDLE kernel_device;
84
85 DWORD callback;
86 DWORD instance;
87 DWORD flags;
88
89 UCHAR running_status;
90
91 DWORD playing_notes_count;
92 NoteNode* note_list;
93 BOOL refresh_notes;
94
95 HANDLE thread_handle;
96 BOOL terminate_thread;
97 HANDLE thread_termination_complete;
98 } DeviceInfo;
99
100 DeviceInfo* the_device;
101 CRITICAL_SECTION device_lock;
102
103 void
104 FakePrintf(char* str, ...)
105 {
106 /* Just to shut the compiler up */
107 }
108
109
110 /*
111 This is designed to be treated as a thread, however it behaves as a
112 normal function if CONTINUOUS_NOTES is not defined.
113 */
114
115 DWORD WINAPI
116 ProcessPlayingNotes(
117 LPVOID parameter)
118 {
119 DeviceInfo* device_info = (DeviceInfo*) parameter;
120 NTSTATUS status;
121 IO_STATUS_BLOCK io_status_block;
122 DWORD arp_notes;
123
124 DPRINT("Note processing started\n");
125
126 /* We lock the note list only while accessing it */
127
128 #ifdef CONTINUOUS_NOTES
129 while ( ! device_info->terminate_thread )
130 #endif
131 {
132 NoteNode* node;
133
134 /* Number of notes being arpeggiated */
135 arp_notes = 1;
136
137 EnterCriticalSection(&device_lock);
138
139 /* Calculate how much time to allocate to each playing note */
140
141 DPRINT("%d notes active\n", (int) device_info->playing_notes_count);
142
143 node = device_info->note_list;
144
145 while ( ( node != NULL ) && ( arp_notes <= POLYPHONY ) )
146 {
147 BEEP_SET_PARAMETERS beep_data;
148 DWORD actually_playing = 0;
149
150 double frequency = node->note;
151
152 DPRINT("playing..\n");
153
154 frequency = frequency / 12;
155 frequency = pow(2, frequency);
156 frequency = 8.1758 * frequency;
157
158 if (device_info->playing_notes_count > POLYPHONY)
159 actually_playing = POLYPHONY;
160 else
161 actually_playing = device_info->playing_notes_count;
162
163 DPRINT("Frequency %f\n", frequency);
164
165 // TODO
166 beep_data.Frequency = (DWORD) frequency;
167 beep_data.Duration = TIMESLICE_SIZE / actually_playing; /* device_info->playing_notes_count; */
168
169 status = NtDeviceIoControlFile(device_info->kernel_device,
170 NULL,
171 NULL,
172 NULL,
173 &io_status_block,
174 IOCTL_BEEP_SET,
175 &beep_data,
176 sizeof(BEEP_SET_PARAMETERS),
177 NULL,
178 0);
179
180 if ( ! NT_SUCCESS(status) )
181 {
182 DPRINT("ERROR %d\n", (int) GetLastError());
183 }
184
185 SleepEx(beep_data.Duration, TRUE);
186
187 if ( device_info->refresh_notes )
188 {
189 device_info->refresh_notes = FALSE;
190 break;
191 }
192
193 arp_notes ++;
194 node = node->next;
195 }
196
197 LeaveCriticalSection(&device_lock);
198 }
199
200 #ifdef CONTINUOUS_NOTES
201 SetEvent(device_info->thread_termination_complete);
202 #endif
203
204 return 0;
205 }
206
207
208 /*
209 Fills a MIDIOUTCAPS structure with information about our device.
210 */
211
212 MMRESULT
213 GetDeviceCapabilities(
214 MIDIOUTCAPS* caps)
215 {
216 /* These are ignored for now */
217 caps->wMid = 0;
218 caps->wPid = 0;
219
220 caps->vDriverVersion = 0x0100;
221
222 memset(caps->szPname, 0, sizeof(caps->szPname));
223 memcpy(caps->szPname, L"PC speaker\0", strlen("PC speaker\0") * 2);
224
225 caps->wTechnology = MOD_SQSYNTH;
226
227 caps->wVoices = 1; /* We only have one voice */
228 caps->wNotes = POLYPHONY;
229 caps->wChannelMask = 0xFFBF; /* Ignore channel 10 */
230
231 caps->dwSupport = 0;
232
233 return MMSYSERR_NOERROR;
234 }
235
236
237 /*
238 Helper function that just simplifies calling the application making use
239 of us.
240 */
241
242 BOOL
243 CallClient(
244 DeviceInfo* device_info,
245 DWORD_PTR message,
246 DWORD_PTR parameter1,
247 DWORD_PTR parameter2)
248 {
249 DPRINT("Calling client - callback 0x%x mmhandle 0x%x\n", device_info->callback, device_info->mme_handle);
250 return DriverCallback(device_info->callback,
251 HIWORD(device_info->flags),
252 device_info->mme_handle,
253 message,
254 device_info->instance,
255 parameter1,
256 parameter2);
257
258 }
259
260
261 /*
262 Open the kernel-mode device and allocate resources. This opens the
263 BEEP.SYS kernel device.
264 */
265
266 MMRESULT
267 OpenDevice(
268 DeviceInfo** private_data,
269 MIDIOPENDESC* open_desc,
270 DWORD flags)
271 {
272 NTSTATUS status;
273 HANDLE heap;
274 HANDLE kernel_device;
275 UNICODE_STRING beep_device_name;
276 OBJECT_ATTRIBUTES attribs;
277 IO_STATUS_BLOCK status_block;
278
279 /* One at a time.. */
280 if ( the_device )
281 {
282 DPRINT("Already allocated\n");
283 return MMSYSERR_ALLOCATED;
284 }
285
286 /* Make the device name into a unicode string and open it */
287
288 RtlInitUnicodeString(&beep_device_name,
289 L"\\Device\\Beep");
290
291 InitializeObjectAttributes(&attribs,
292 &beep_device_name,
293 0,
294 NULL,
295 NULL);
296
297 status = NtCreateFile(&kernel_device,
298 FILE_READ_DATA | FILE_WRITE_DATA,
299 &attribs,
300 &status_block,
301 NULL,
302 0,
303 FILE_SHARE_READ | FILE_SHARE_WRITE,
304 FILE_OPEN_IF,
305 0,
306 NULL,
307 0);
308
309 if ( ! NT_SUCCESS(status) )
310 {
311 DPRINT("Could not connect to BEEP device - %d\n", (int) GetLastError());
312 return MMSYSERR_ERROR;
313 }
314
315 DPRINT("Opened!\n");
316
317 /* Allocate and initialize the device info */
318
319 heap = GetProcessHeap();
320
321 the_device = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(DeviceInfo));
322
323 if ( ! the_device )
324 {
325 DPRINT("Out of memory\n");
326 return MMSYSERR_NOMEM;
327 }
328
329 /* Initialize */
330 the_device->kernel_device = kernel_device;
331 the_device->playing_notes_count = 0;
332 the_device->note_list = NULL;
333 the_device->thread_handle = 0;
334 the_device->terminate_thread = FALSE;
335 the_device->running_status = 0;
336
337 // TODO
338 the_device->mme_handle = (HDRVR) open_desc->hMidi;
339 the_device->callback = open_desc->dwCallback;
340 the_device->instance = open_desc->dwInstance;
341 the_device->flags = flags;
342
343 /* Store the pointer in the user data */
344 *private_data = the_device;
345
346 /* This is threading-related code */
347 #ifdef CONTINUOUS_NOTES
348 the_device->thread_termination_complete = CreateEvent(NULL, FALSE, FALSE, NULL);
349
350 if ( ! the_device->thread_termination_complete )
351 {
352 DPRINT("CreateEvent failed\n");
353 HeapFree(heap, 0, the_device);
354 return MMSYSERR_NOMEM;
355 }
356
357 the_device->thread_handle = CreateThread(NULL,
358 0,
359 ProcessPlayingNotes,
360 (PVOID) the_device,
361 0,
362 NULL);
363
364 if ( ! the_device->thread_handle )
365 {
366 DPRINT("CreateThread failed\n");
367 CloseHandle(the_device->thread_termination_complete);
368 HeapFree(heap, 0, the_device);
369 return MMSYSERR_NOMEM;
370 }
371 #endif
372
373 /* Now we call the client application to say the device is open */
374 DPRINT("Sending MOM_OPEN\n");
375 DPRINT("Success? %d\n", (int) CallClient(the_device, MOM_OPEN, 0, 0));
376
377 return MMSYSERR_NOERROR;
378 }
379
380
381 /*
382 Close the kernel-mode device.
383 */
384
385 MMRESULT
386 CloseDevice(DeviceInfo* device_info)
387 {
388 HANDLE heap = GetProcessHeap();
389
390 /* If we're working in threaded mode we need to wait for thread to die */
391 #ifdef CONTINUOUS_NOTES
392 the_device->terminate_thread = TRUE;
393
394 WaitForSingleObject(the_device->thread_termination_complete, INFINITE);
395
396 CloseHandle(the_device->thread_termination_complete);
397 #endif
398
399 /* Let the client application know the device is closing */
400 DPRINT("Sending MOM_CLOSE\n");
401 CallClient(device_info, MOM_CLOSE, 0, 0);
402
403 NtClose(device_info->kernel_device);
404
405 /* Free resources */
406 HeapFree(heap, 0, device_info);
407
408 the_device = NULL;
409
410 return MMSYSERR_NOERROR;
411 }
412
413
414 /*
415 Removes a note from the playing notes list. If the note is not playing,
416 we just pretend nothing happened.
417 */
418
419 MMRESULT
420 StopNote(
421 DeviceInfo* device_info,
422 UCHAR note)
423 {
424 HANDLE heap = GetProcessHeap();
425 NoteNode* node;
426 NoteNode* prev_node = NULL;
427
428 DPRINT("StopNote\n");
429
430 EnterCriticalSection(&device_lock);
431
432 node = device_info->note_list;
433
434 while ( node != NULL )
435 {
436 if ( node->note == note )
437 {
438 /* Found the note - just remove the node from the list */
439
440 DPRINT("Stopping note %d\n", (int) node->note);
441
442 if ( prev_node != NULL )
443 prev_node->next = node->next;
444 else
445 device_info->note_list = node->next;
446
447 HeapFree(heap, 0, node);
448
449 device_info->playing_notes_count --;
450
451 DPRINT("Note stopped - now playing %d notes\n", (int) device_info->playing_notes_count);
452
453 LeaveCriticalSection(&device_lock);
454 device_info->refresh_notes = TRUE;
455
456 return MMSYSERR_NOERROR;
457 }
458
459 prev_node = node;
460 node = node->next;
461 }
462
463 LeaveCriticalSection(&device_lock);
464
465 /* Hmm, a good idea? */
466 #ifndef CONTINUOUS_NOTES
467 ProcessPlayingNotes((PVOID) device_info);
468 #endif
469
470 return MMSYSERR_NOERROR;
471 }
472
473
474 /*
475 Adds a note to the playing notes list. If the note is already playing,
476 the definition of ALLOW_DUPLICATE_NOTES determines if an existing note
477 may be duplicated. Otherwise, duplicate notes are ignored.
478 */
479
480 MMRESULT
481 PlayNote(
482 DeviceInfo* device_info,
483 UCHAR note,
484 UCHAR velocity)
485 {
486 HANDLE heap = GetProcessHeap();
487
488 NoteNode* node;
489
490 DPRINT("PlayNote\n");
491
492 if ( velocity == 0 )
493 {
494 DPRINT("Zero velocity\n");
495
496 /* Velocity zero is effectively a "note off" */
497 StopNote(device_info, note);
498 }
499 else
500 {
501 /* Start playing the note */
502 NoteNode* new_node;
503 NoteNode* tail_node = NULL;
504
505 EnterCriticalSection(&device_lock);
506
507 node = device_info->note_list;
508
509 while ( node != NULL )
510 {
511 #ifndef ALLOW_DUPLICATE_NOTES
512 if ( ( node->note == note ) && ( velocity > 0 ) )
513 {
514 /* The note is already playing - do nothing */
515 DPRINT("Duplicate note playback request ignored\n");
516 LeaveCriticalSection(&device_lock);
517 return MMSYSERR_NOERROR;
518 }
519 #endif
520
521 tail_node = node;
522 node = node->next;
523 }
524
525 new_node = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(NoteNode));
526
527 if ( ! new_node )
528 {
529 LeaveCriticalSection(&device_lock);
530 return MMSYSERR_NOMEM;
531 }
532
533 new_node->note = note;
534 new_node->velocity = velocity;
535
536 /*
537 Prepend to the playing notes list. If exceeding polyphony,
538 remove the oldest note (which will be at the tail.)
539 */
540
541 if ( device_info->note_list )
542 device_info->note_list->previous = new_node;
543
544 new_node->next = device_info->note_list;
545 new_node->previous = NULL;
546
547 device_info->note_list = new_node;
548 device_info->playing_notes_count ++;
549
550 /*
551 if ( device_info->playing_notes_count > POLYPHONY )
552 {
553 ASSERT(tail_node);
554
555 DPRINT("Polyphony exceeded\n");
556
557 tail_node->previous->next = NULL;
558
559 HeapFree(heap, 0, tail_node);
560
561 device_info->playing_notes_count --;
562 }
563 */
564
565 LeaveCriticalSection(&device_lock);
566
567 DPRINT("Note started - now playing %d notes\n", (int) device_info->playing_notes_count);
568 device_info->refresh_notes = TRUE;
569 }
570
571 #ifndef CONTINUOUS_NOTES
572 ProcessPlayingNotes((PVOID) device_info);
573 #endif
574
575 return MMSYSERR_NOERROR;
576 }
577
578 /*
579 Decipher a short MIDI message (which is a MIDI message packed into a DWORD.)
580 This will set "running status", but does not take this into account when
581 processing messages (is this necessary?)
582 */
583
584 MMRESULT
585 ProcessShortMidiMessage(
586 DeviceInfo* device_info,
587 DWORD message)
588 {
589 DWORD status;
590
591 DWORD category;
592 DWORD channel;
593 DWORD data1, data2;
594
595 status = message & 0x000000FF;
596
597 /* Deal with running status */
598
599 if ( status < MIDI_NOTE_OFF )
600 {
601 status = device_info->running_status;
602 }
603
604 /* Ensure the status is sane! */
605
606 if ( status < MIDI_NOTE_OFF )
607 {
608 /* It's garbage, ignore it */
609 return MMSYSERR_NOERROR;
610 }
611
612 /* Figure out the message category and channel */
613
614 category = status & 0xF0;
615 channel = status & 0x0F; /* we don't use this */
616
617 data1 = (message & 0x0000FF00) >> 8;
618 data2 = (message & 0x00FF0000) >> 16;
619
620 DPRINT("0x%x, %d, %d\n", (int) status, (int) data1, (int) data2);
621
622 /* Filter drums (which are *usually* on channel 10) */
623 if ( channel == 10 )
624 {
625 return MMSYSERR_NOERROR;
626 }
627
628 /* Pass to the appropriate message handler */
629
630 switch ( category )
631 {
632 case MIDI_NOTE_ON :
633 {
634 PlayNote(device_info, data1, data2);
635 break;
636 }
637
638 case MIDI_NOTE_OFF :
639 {
640 StopNote(device_info, data1);
641 break;
642 }
643 }
644
645 return MMSYSERR_NOERROR;
646 }
647
648
649 #define PACK_MIDI(b1, b2, b3) \
650 ((b3 * 65536) + (b2 * 256) + b1);
651
652
653 /*
654 Processes a "long" MIDI message (ie, a MIDI message contained within a
655 buffer.) This is intended for supporting SysEx data, or blocks of MIDI
656 events. However in our case we're only interested in short MIDI messages,
657 so we scan the buffer, and each time we encounter a valid status byte
658 we start recording it as a new event. Once 3 bytes or a new status is
659 received, the event is passed to the short message handler.
660 */
661
662 MMRESULT
663 ProcessLongMidiMessage(
664 DeviceInfo* device_info,
665 MIDIHDR* header)
666 {
667 unsigned int index = 0;
668 UCHAR* midi_bytes = (UCHAR*) header->lpData;
669
670 unsigned int msg_index = 0;
671 UCHAR msg[3];
672
673 /* Initialize the buffer */
674 msg[0] = msg[1] = msg[2] = 0;
675
676 if ( ! ( header->dwFlags & MHDR_PREPARED ) )
677 {
678 DPRINT("Not prepared!\n");
679 return MIDIERR_UNPREPARED;
680 }
681
682 DPRINT("Processing %d bytes of MIDI\n", (int) header->dwBufferLength);
683
684 while ( index < header->dwBufferLength )
685 {
686 /* New status byte? ( = new event) */
687 if ( midi_bytes[index] & 0x80 )
688 {
689 DWORD short_msg;
690
691 /* Deal with the existing event */
692
693 if ( msg[0] & 0x80 )
694 {
695 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
696
697 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
698 ProcessShortMidiMessage(device_info, short_msg);
699 }
700
701 /* Set new running status and start recording the event */
702 DPRINT("Set new running status\n");
703 device_info->running_status = midi_bytes[index];
704 msg[0] = midi_bytes[index];
705 msg_index = 1;
706 }
707
708 /* Unexpected data byte? ( = re-use previous status) */
709 else if ( msg_index == 0 )
710 {
711 if ( device_info->running_status & 0x80 )
712 {
713 DPRINT("Retrieving running status\n");
714 msg[0] = device_info->running_status;
715 msg[1] = midi_bytes[index];
716 msg_index = 2;
717 }
718 else
719 DPRINT("garbage\n");
720 }
721
722 /* Expected data ( = append to message until buffer full) */
723 else
724 {
725 DPRINT("Next byte...\n");
726 msg[msg_index] = midi_bytes[index];
727 msg_index ++;
728
729 if ( msg_index > 2 )
730 {
731 DWORD short_msg;
732
733 short_msg = PACK_MIDI(msg[0], msg[1], msg[2]);
734
735 DPRINT("Complete msg is 0x%x %d %d\n", (int) msg[0], (int) msg[1], (int) msg[2]);
736 ProcessShortMidiMessage(device_info, short_msg);
737
738 /* Reinit */
739 msg_index = 0;
740 msg[0] = msg[1] = msg[2] = 0;
741 }
742 }
743
744 index ++;
745 }
746
747 /*
748 We're meant to clear MHDR_DONE and set MHDR_INQUEUE but since we
749 deal with everything here and now we might as well just say so.
750 */
751 header->dwFlags |= MHDR_DONE;
752 header->dwFlags &= ~ MHDR_INQUEUE;
753
754 DPRINT("Success? %d\n", CallClient(the_device, MOM_DONE, (DWORD_PTR) header, 0));
755
756 return MMSYSERR_NOERROR;
757 }
758
759
760 /*
761 Exported function that receives messages from WINMM (the MME API.)
762 */
763
764 MMRESULT
765 FAR PASCAL
766 modMessage(
767 UINT device_id,
768 UINT message,
769 DWORD_PTR private_data,
770 DWORD_PTR parameter1,
771 DWORD_PTR parameter2)
772 {
773 switch ( message )
774 {
775 case MODM_GETNUMDEVS :
776 {
777 /* Only one internal PC speaker device (and even that's too much) */
778 DPRINT("MODM_GETNUMDEVS\n");
779 return 1;
780 }
781
782 case MODM_GETDEVCAPS :
783 {
784 DPRINT("MODM_GETDEVCAPS\n");
785 return GetDeviceCapabilities((MIDIOUTCAPS*) parameter1);
786 }
787
788 case MODM_OPEN :
789 {
790 DPRINT("MODM_OPEN\n");
791
792 return OpenDevice((DeviceInfo**) private_data,
793 (MIDIOPENDESC*) parameter1,
794 parameter2);
795 }
796
797 case MODM_CLOSE :
798 {
799 DPRINT("MODM_CLOSE\n");
800 return CloseDevice((DeviceInfo*) private_data);
801 }
802
803 case MODM_DATA :
804 {
805 return ProcessShortMidiMessage((DeviceInfo*) private_data, parameter1);
806 }
807
808 case MODM_PREPARE :
809 {
810 /* We don't bother with this */
811 MIDIHDR* hdr = (MIDIHDR*) parameter1;
812 hdr->dwFlags |= MHDR_PREPARED;
813 return MMSYSERR_NOERROR;
814 }
815
816 case MODM_UNPREPARE :
817 {
818 MIDIHDR* hdr = (MIDIHDR*) parameter1;
819 hdr->dwFlags &= ~MHDR_PREPARED;
820 return MMSYSERR_NOERROR;
821 }
822
823 case MODM_LONGDATA :
824 {
825 DPRINT("LONGDATA\n");
826 return ProcessLongMidiMessage((DeviceInfo*) private_data, (MIDIHDR*) parameter1);
827 }
828
829 case MODM_RESET :
830 {
831 /* TODO */
832 break;
833 }
834 }
835
836 DPRINT("Not supported %d\n", message);
837
838 return MMSYSERR_NOTSUPPORTED;
839 }
840
841
842 /*
843 Driver entrypoint.
844 */
845
846 LONG
847 FAR PASCAL
848 DriverProc(
849 DWORD driver_id,
850 HDRVR driver_handle,
851 UINT message,
852 LONG parameter1,
853 LONG parameter2)
854 {
855 switch ( message )
856 {
857 case DRV_LOAD :
858 DPRINT("DRV_LOAD\n");
859 the_device = NULL;
860 return 1L;
861
862 case DRV_FREE :
863 DPRINT("DRV_FREE\n");
864 return 1L;
865
866 case DRV_OPEN :
867 DPRINT("DRV_OPEN\n");
868 InitializeCriticalSection(&device_lock);
869 return 1L;
870
871 case DRV_CLOSE :
872 DPRINT("DRV_CLOSE\n");
873 return 1L;
874
875 case DRV_ENABLE :
876 DPRINT("DRV_ENABLE\n");
877 return 1L;
878
879 case DRV_DISABLE :
880 DPRINT("DRV_DISABLE\n");
881 return 1L;
882
883 /*
884 We don't provide configuration capabilities. This used to be
885 for things like I/O port, IRQ, DMA settings, etc.
886 */
887
888 case DRV_QUERYCONFIGURE :
889 DPRINT("DRV_QUERYCONFIGURE\n");
890 return 0L;
891
892 case DRV_CONFIGURE :
893 DPRINT("DRV_CONFIGURE\n");
894 return 0L;
895
896 case DRV_INSTALL :
897 DPRINT("DRV_INSTALL\n");
898 return DRVCNF_RESTART;
899 };
900
901 DPRINT("???\n");
902
903 return DefDriverProc(driver_id,
904 driver_handle,
905 message,
906 parameter1,
907 parameter2);
908 }