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 } 908