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