
   Programming the PC speaker in assembly under Linux

   Do  sometimes  think  about  how would it be if your programs had not
   only visual effects, but also some sound?
   Programming  sound  cards  (especially  the modern ones) can be quite
   troublesome.  Fortunately,  the good old PC-speaker is a device which
   you  can  program quite easily. This is what I'm going to prove here.
   First some theory.
   Linux  is  an  operating  system which works fully in protected mode.
   This is why we can't use the interesting (42h, 43h, 61h) input/output
   ports directly without root privileges.
   Fortunately,  the  system function sys_ioctl (number 54) will help us
   in bringing the PC-speaker alive. This function expects the following
   parameters:
     * EBX  =  descriptor  for  an  file  (we'll use /dev/console or the
       standard output stream - STDOUT) open for writing
     * ECX = KIOCSOUND = 0x4B2F (see: /usr/include/linux/kd.h)
     * EDX =
          + 0 if we want to turn off the sound
          + 1234DDh / frequency, if we want sound with that frequency

   But  this  is  not  all. We'd like our sound to last for a while. For
   that  we'll  use  the system function sys_nanosleep (number 162). Its
   syntax is simple:
     * EBX  = address of a timespec structure, which looks like this (in
       FASM syntax):
        struc   timespec
         {
                .tv_sec         rd 1
                .tv_nsec        rd 1
         }
       containing number of seconds and nanoseconds to wait for.
     * ECX  =  address  of  a  timespec  structure,  which  will get the
       function result.

   As you can see, the algorithm of our program is simple:
    1. Open /dev/console for writing. In case of failure use STDOUT
    2. Maybe  call sys_ioctl with EDX=0 to turn off the currently played
       sound
    3. As  many  times  as  it  will  be needed, call sys_ioctl with the
       correct values in EDX and call sys_nanosleep
    4. Call sys_ioctl with EDX=0 to turn off the sound
    5. If we opened /dev/console, close it

   Example  program  follows  (using  my  library  isn't necessary - the
   comments tell what to change):
   (skip the code)
; A program making sounds from the PC-speaker using sys_ioctl
; Author: Bogdan D.
; Contact: bogdandr (at) op (dot) pl
;
; assemble:
;   fasm spkr.asm spkr

format ELF executable
entry _start

segment readable executable

include "bibl/incl/fasm/fasm_system.inc"

KIOCSOUND       = 0x4B2F

_start:
        mov     eax, sys_open   ; sys_open = 5
        mov     ebx, konsola
        mov     ecx, O_WRONLY   ; O_WRONLY = 1
        mov     edx, 777o
        int     80h

        cmp     eax, 0          ; was there an error (EAX < 0) ?
        jg      .otw_ok

        mov     eax, 1          ;if we didn't open the console, use STDOUT (1)
.otw_ok:
        mov     ebx, eax        ; EBX = file handle

        mov     eax, sys_ioctl  ; sys_ioctl = 54
        mov     ecx, KIOCSOUND
        xor     edx, edx        ; turining off the current sound
        int     80h

        mov     eax, sys_ioctl
        mov     edx, 2711       ; 2711 = 1234DDh/440. 440 Hz is the 'A' note
        int     80h

        mov     cx, 0fh
        mov     dx, 4240h       ; 0F4240h is 1 decimal million in hexadecimal
        call    pauza

        mov     eax, sys_ioctl
        mov     ecx, KIOCSOUND
        xor     edx, edx        ; turining off the sound
        int     80h

        cmp     ebx, 2          ; check if using /dev/console or STDOUT
        jbe     .koniec

        mov     eax, sys_close  ; sys_close = 6
        int     80h             ; close the open console file

.koniec:
        mov     eax, 1
        xor     ebx, ebx
        int     80h

pauza:                          ;procedure pausing for CX:DX miliseconds
        push    ebx
        push    ecx
        push    edx

        mov     ax, cx
        shl     eax, 16
        mov     ebx, 1000000
        mov     ax, dx                  ; EAX = CX:DX
        xor     edx, edx
        div     ebx                     ; divide CX:DX by 1 million
        mov     [t1.tv_sec], eax        ; EAX = number of seconds

        mov     ebx, 1000
        mov     eax, edx                ;EAX = number of microseconds left
        mul     ebx
        mov     [t1.tv_nsec], eax       ; EAX = number of nanoseconds

        mov     eax, sys_nanosleep      ; function number 162
        mov     ebx, t1
        mov     ecx, t2
        int     80h

        pop     edx
        pop     ecx
        pop     ebx

        ret

segment readable writeable

konsola         db      "/dev/console", 0

struc   timespec
 {
        .tv_sec         rd 1
        .tv_nsec        rd 1
 }

t1 timespec
t2 timespec

   I  hope to have given enough information for you to start programming
   the  speaker  on your own. If the program isn't working, you may need
   to  compile pc-speaker support into your kernel (or insert the needed
   modules). Sometimes root privileges are necessary.
   Have fun!

   On-line contents (Alt+2)
   Helpers for people with disabilities (Alt+0)
