Viel zu viele Kommentare???



  • Den habe ich gerade gefunden. Ist das nicht ein bisschen übertrieben mit den

    Kommentaren?

    /***************************************************************************
                          BMC PIO-II 24 I/O card driver
            ------------------------------------------------------------
        Begin                : Wed Dec 04 2002
        copyright            : (C) 2002 by Uwe Thieme
        email                : uthieme@imn.htwk-leipzig.de
        version              : 0.01
    
    ***************************************************************************/
    
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/ioport.h>
    #include <linux/kernel.h>
    #include <linux/errno.h>
    #include <linux/devfs_fs_kernel.h>
    #include <linux/compatmac.h>
    #include <linux/bmc.h>
    #include <asm/io.h>
    
    /* port IO flags
       - das Steuerbyte wird auf den Kontrollport geschrieben,
         um Funktion und Richtung der Ports festzulegen
       - Modus 0 Basic In/Out: die Ports A bis C sind einfache gelatchte Ein-/Ausgabeports
                               bit 7 bis 5 des Steuerports sind auf 100 (765.....) zu setzen
                               bit 4 bis 0 legen die Richtung der Ports fest
       - Modus 1 Strobe In/Out:Port C wird zum Strobe Steuerport, an den externe Signale
                               zur Steuerung von Port A und B angelegt und abgefragt werden
                               können.
    */
    /* - Modus 0 Basic In/Out: */
    #define BMC_PORT_ALL_OUT   0x80
    #define BMC_PORT_ALL_IN    0x9B
    /* bit 0 bis 4 des Steuerwortes zum setzen der Portrichtung */
    #define BMC_BASIC_AIN      0x10
    #define BMC_BASIC_BIN      0x02
    #define BMC_BASIC_CIN      0x09
    #define BMC_BASIC_CHIN     0x08
    #define BMC_BASIC_CLIN     0x01
    
    /* - Modus 1 Strobe In/Out: */
    #define BMC_STRB_AIN       0xb0
    #define BMC_STRB_AOUT      0xc0
    #define BMC_STRB_BIN       0x86
    #define BMC_STRB_BOUT      0x84
    
    /* portinfo Defines für Port-Typen */
    #define BMC_T_IO           0x0
    #define BMC_T_SPLIT_IO     0x1
    #define BMC_T_STRB         0x2
    #define BMC_T_STRBCTL      0x3
    #define BMC_T_CTL          0x4
    
    /* portinfo Defines für Datenrichtung der Ports */
    #define BMC_DIR_NONE       0x0
    #define BMC_DIR_IN         0x1
    #define BMC_DIR_OUT        0x2
    
    /* major number und baseaddress für BMC PIO-II 24 */
    static int majornr=231;
    static int baseaddr=0x0;
    
    /* struct mit Eigenschaften und Infos zu den Ports */
    struct bmc_portinfo{
       int           portaddr;   /* Portadresse */
       int           fdcount;    /* Anzahl der offenen Filedeskriptoren */
       unsigned char type;       /* Porttyp (I/O, Strobe ctrl, ctrl) */
       unsigned char direction;  /* Richtung des I/O Ports */
       unsigned char lastout;    /* leltztes geschriebenes Byte */
    };
    
    /* für jeden Port (A bis C und CTL) wird eine statische Struktur angelegt */
    static struct bmc_portinfo bmc_portinfo[4];
    /* Prozessgruppe des des momentanen Eigentümers der Karte */
    static int bmc_owner = 0;
    /* Feld mit den Flags zur Richtungssteuerung der Ports für Zugriff über minor number */
    static int bmc_dir_flags[] = {
                                  BMC_BASIC_AIN,
                                  BMC_BASIC_BIN,
                                  BMC_BASIC_CIN,
                                 };
    
    /*****************************************************************
    * Auswerten der Kernel/insmod Parameter
    * Setzen von Modul-Werten
    ******************************************************************/
    #if defined(MODULE)
    EXPORT_NO_SYMBOLS;
    MODULE_AUTHOR("Uwe Thieme");
    MODULE_DESCRIPTION("BMC PIO24II driver");
    MODULE_SUPPORTED_DEVICE("bmc");
    MODULE_PARM(baseaddr, "i");
    MODULE_PARM_DESC(baseaddr, "I/O card base address");
    MODULE_LICENSE("GPL");
    #else
    static int __init bmc_setup(char *str)
    {
       baseaddr=(int)simple_strtol(str, NULL, 0);
       return(1);
    }
    __setup("bmc=",bmc_setup);
    #endif
    
    /*****************************************************************
    * aktiviert die Porteinstellungen und korrigiert gleichzeitig die Werte der OUT-Ports
    * Bemerkungen
    *   bei jedem Einschreiben eines Wertes in den Steuerport mit gesetztem MODE-SET-FLAG
    *   (Bit 7 des Steuerwortes = 1) werden alle Ports wieder mit 0 initialisiert. Die
    *   anliegenden Pegel der OUT-Ports gehen also verloren. Um diese wiederherzustellen,
    *   werden die letzten, an die Ports geschriebenen Werte nochmals geschrieben (lastout),
    *   wenn der Port als OUT-Port gekennzeichnet ist.
    *   durch diese Verfahrensweise kommt es möglicherweise zu kurzzeitigen 0-Spitzen an den
    *   OUT-Ports (oszillographieren), möglicherweise aber auch nicht, da ein Lesezugriff auf
    *   einen Port direkt nach dem Schreiben auf den Kontrollport noch einen korrekten Wert
    *   liefert. Die Pegel stehen also noch kurzzeitig zur Verfügung.
    * Parameter:
    *   keine
    * Rückgabewert:
    *   keiner
    ******************************************************************/
    void bmc_setportio(void)
    {
       /* Aktivieren der Porteinstellungen */
       outb_p(bmc_portinfo[3].lastout, bmc_portinfo[3].portaddr);
    
       /* Korrigieren der OUT-Port Pegel für Port A, B und C */
       if(bmc_portinfo[0].fdcount && bmc_portinfo[0].direction == BMC_DIR_OUT)
       {
          outb_p(bmc_portinfo[0].lastout, bmc_portinfo[0].portaddr);
       }
       if(bmc_portinfo[1].fdcount && bmc_portinfo[1].direction == BMC_DIR_OUT)
       {
          outb_p(bmc_portinfo[1].lastout, bmc_portinfo[1].portaddr);
       }
       if(bmc_portinfo[2].fdcount)
       {
          switch(bmc_portinfo[2].type)
          {
             case BMC_T_IO: if(bmc_portinfo[2].direction == BMC_DIR_OUT)
                            {
                               outb_p(bmc_portinfo[2].lastout, bmc_portinfo[2].portaddr);
                            }
                            break;
             case BMC_T_SPLIT_IO:
                            if(bmc_portinfo[2].direction & BMC_HIGH_WR || bmc_portinfo[2].direction & BMC_LOW_WR)
                            {
                               outb_p(bmc_portinfo[2].lastout, bmc_portinfo[2].portaddr);
                            }
                            break;
          }
       }
    }
    
    /*****************************************************************
    * setzt Defaultwerte für die Ports A und B beim Wechsel von BASIC_IO
    * zu Strobe-IO und umgekehrt
    * Parameter:
    *   type .. Modus, in den gewechselt werden soll
    * Rückgabewert:
    *   keiner
    ******************************************************************/
    void bmc_setdefaults(unsigned char type)
    {
       int i;
       for(i=0;i<2;i++)
       {
          /* Setzen von Defaultwerten für Port A und B als Strobe-IO */
          bmc_portinfo[i].type     =type;
          bmc_portinfo[i].direction=BMC_DIR_IN;
          bmc_portinfo[i].lastout  =0x0;
          /* Ports rücksetzen */
          outb_p(0x0, bmc_portinfo[i].portaddr);
       }
       /* Steuerwort für neuen Modus festlegen */
       bmc_portinfo[3].lastout = (type == BMC_T_IO?BMC_PORT_ALL_IN:BMC_STRB_AIN | BMC_STRB_BIN);
    }
    
    /*****************************************************************
    * Öffnen des angeforderten Ports
    * Funktionsweise
    *   die Ports des Gerätes können nur von Prozessen mit derselben
    *   Prozessgruppe geöffnet werden. bmc_owner wird dazu auf die
    *   Prozessgruppen-ID des öffnenden Tasks gesetzt und bei jedem weiteren
    *   öffnen überprüft.
    *   Sind alle Ports wieder geschlossen, so wird das Gerät durch
    *   bmc_owner=0 wieder freigegeben
    * Parameter:
    *   *inode .. Zeiger auf die zugehörige inode struktur
    *   *file  .. Zeiger auf die zugehörige file struktur
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 0
    ******************************************************************/
    static int bmc_open(struct inode *inode, struct file *file)
    {
       unsigned int minor;
       struct task_struct *calling_task;
    
       minor = MINOR(inode->i_rdev);
       calling_task = get_current();
    
       if(minor > 2 || minor < 0) return(-ENXIO);
       /* Wenn Karte schon von einem anderen Prozess belegt, dann BUSSY */
       if(bmc_owner && bmc_owner != calling_task->pgrp) return(-EBUSY);
       /* kein weiteres open() von Port C zulassen, wenn Port C Strobecontrol ist */
       if(minor == 2 && bmc_portinfo[2].type == BMC_T_STRBCTL) return(-EBUSY);
    
       if(!bmc_owner)
       {
          bmc_owner = calling_task->pgrp;
          printk(KERN_DEBUG "bmc: RESERVIERUNG durch task %d\n", calling_task->pid);
       }
       /* setze Defaultwerte, wenn dies das erste open() auf den Port ist */
       if((bmc_portinfo[minor].fdcount = file->f_count.counter)==1)
       {
          /* wenn Port C Strobecontrol Port ist, dann A/B im Strobe-IO öffnen, sonst BASIC_IO */
          bmc_portinfo[minor].type     = (minor < 2 && bmc_portinfo[2].type == BMC_T_STRBCTL?
                                          BMC_T_STRB:BMC_T_IO);
          bmc_portinfo[minor].direction= BMC_DIR_IN;
          bmc_portinfo[minor].lastout  = 0x0;
       }
       return(0);
    }
    
    /*****************************************************************
    * Aufräumarbeiten nach dem letzten close() des Ports
    * Parameter:
    *    *inode .. Zeiger auf die zugehörige inode struktur
    *    *file  .. Zeiger auf die zugehörige file struktur
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 0
    ******************************************************************/
    static int bmc_release(struct inode *inode, struct file *file)
    {
       unsigned int minor;
       struct task_struct *calling_task;
    
       calling_task = get_current();
       minor = MINOR(inode->i_rdev);
       /* Ermitteln der Anzahl offener Filedeskriptoren */
       bmc_portinfo[minor].fdcount = file->f_count.counter;
       /* zurücksetzen der Portwerte auf default, wenn der letzte fd geschlossen wurde */
       if(!bmc_portinfo[minor].fdcount)
       {
          /* Rücksetzen des Ports im Steuerwort */
          if(bmc_portinfo[minor].type == BMC_T_STRB)
             bmc_portinfo[3].lastout &= (minor?0xf9:0x8f);
          else
             bmc_portinfo[3].lastout |= bmc_dir_flags[minor];
          /* setzen von Defaultwerten */
          bmc_portinfo[minor].type     = BMC_T_IO;
          bmc_portinfo[minor].direction= BMC_DIR_IN;
          bmc_portinfo[minor].lastout  = 0x0;
          outb_p(0x0, bmc_portinfo[minor].portaddr);
          /* Werte aktivieren */
          bmc_setportio();
       }
    
       /* wenn der letzte Port geschlossen wurde, dann Gerät wieder freigeben */
       if(!(bmc_portinfo[0].fdcount || bmc_portinfo[1].fdcount || bmc_portinfo[2].fdcount))
       {
          bmc_portinfo[3].lastout = BMC_PORT_ALL_IN;
          bmc_setportio();
          bmc_owner=0;
          printk(KERN_DEBUG "bmc: FREIGABE durch task %d\n", calling_task->pid);
       }
    
       return(0);
    }
    
    /*****************************************************************
    * read() - lesen eines Bytes vom entsprechenden Port in Abhängigkeit
    *          des Porttypes
    * Parameter:
    *    *file  .. Zeiger auf die zugehörige file struktur
    *    *buf   .. Puffer des Userbereiches
    *    nbytes .. Anzahl der zu lesenden Bytes
    *    *ppos  .. weis nich
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 1 (gelesene Bytes)
    ******************************************************************/
    static ssize_t bmc_read(struct file *file, char *buf, size_t nbytes, loff_t *ppos)
    {
       unsigned char byte;
       unsigned int  minor;
    
       if(nbytes <= 0) return(0L);
    
       /* ermitteln der minor Nummer */
       minor = MINOR(file->f_dentry->d_inode->i_rdev);
       if(minor<0 || minor > 2) return(-EINVAL);
    
       /* Leseaktionen in Abhängigkeit des gesetzten Porttyps (nur relevant für Port C) */
       switch(bmc_portinfo[minor].type)
       {
          case BMC_T_IO: /* standard I/O Port */
                         /* überprüfen der Portrichtung und gegebenenfalls korrigieren */
                         if(bmc_portinfo[minor].direction != BMC_DIR_IN)
                         {
                            bmc_portinfo[minor].direction=BMC_DIR_IN;
                            bmc_portinfo[3].lastout |= bmc_dir_flags[minor];
                            bmc_setportio();
                         }
                         byte=inb_p(bmc_portinfo[minor].portaddr);
                         if(copy_to_user(buf, &byte, 1)) return(-EFAULT);
                         break;
          case BMC_T_SPLIT_IO: /* Port ist in High/Low geteilt - Task bekommt nur den Wert eines IN Nibles */
                         /* wenn kein Nible auf IN, dann Fehler */
                         if(!(bmc_portinfo[minor].direction & BMC_HIGH_RD ||
                              bmc_portinfo[minor].direction & BMC_LOW_RD )) return(-EINVAL);
                         byte=inb_p(bmc_portinfo[minor].portaddr);
                         /* Ausblenden des High Anteils, wenn nicht gefordert */
                         if(! bmc_portinfo[minor].direction & BMC_HIGH_RD)
                            byte &= 0x0f;
                         /* Ausblenden des Low Anteils, wenn nicht gefordert */
                         if(! bmc_portinfo[minor].direction & BMC_LOW_RD)
                            byte &= 0xf0;
                         if(copy_to_user(buf, &byte, 1)) return(-EFAULT);
                         break;
          case BMC_T_STRB: /* I/O Port für Strobecontrol, lesen immer erlaubt, aber kein Umschalten zwischen I/O */
                         byte=inb_p(bmc_portinfo[minor].portaddr);
                         if(copy_to_user(buf, &byte, 1)) return(-EFAULT);
                         break;
          case BMC_T_STRBCTL:  /* Port dient als Strobe Port für Strobe I/O, I/O deaktiviert */
                         return(-EINVAL);
                         break;
          default: return(-EINVAL);
       }
    
       return(1L);
    }
    
    /*****************************************************************
    * write() - schreiben eines Bytes an den entsprechenden Port in Abhängigkeit
    *           des Porttypes
    * Parameter:
    *    *file  .. Zeiger auf die zugehörige file struktur
    *    *buf   .. Puffer des Userbereiches
    *    nbytes .. Anzahl der zu schreibenden Bytes
    *    *ppos  .. weis nich
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 1 (geschriebene Bytes)
    ******************************************************************/
    static ssize_t bmc_write(struct file *file, const char *buf, size_t nbytes, loff_t *ppos)
    {
       unsigned char byte;
       unsigned int  minor;
    
       if(nbytes <= 0) return(0L);
       /* ermitteln der minor Nummer und lesen des Bytes */
       minor = MINOR(file->f_dentry->d_inode->i_rdev);
       if(minor<0 || minor > 2) return(-EINVAL);
    
       if(copy_from_user(&byte, buf, 1))
          return(-EFAULT);
    
       /* Leseaktionen in Abhängigkeit des gesetzten Porttyps (nur relevant für Port C) */
       switch(bmc_portinfo[minor].type)
       {
          case BMC_T_IO: /* standard I/O Port */
                         /* überprüfen der Portrichtung und gegebenenfalls korrigieren */
                         if(bmc_portinfo[minor].direction != BMC_DIR_OUT)
                         {
                            bmc_portinfo[minor].direction=BMC_DIR_OUT;
                            bmc_portinfo[3].lastout &= ~bmc_dir_flags[minor];
                            bmc_setportio();
                         }
                         outb_p(byte, bmc_portinfo[minor].portaddr);
                         bmc_portinfo[minor].lastout=byte;
                         break;
          case BMC_T_SPLIT_IO: /* Port ist in High/Low geteilt - es wird nur der High/Low Teil geschrieben */
                         /* wenn kein Nible auf OUT, dann Fehler */
                         if(!(bmc_portinfo[minor].direction & BMC_HIGH_WR ||
                              bmc_portinfo[minor].direction & BMC_LOW_WR )) return(-EINVAL);
                         /* Ausblenden des High Anteils, wenn nicht gefordert */
                         if(! bmc_portinfo[minor].direction & BMC_HIGH_WR)
                         {
                            byte &= 0x0f;
                         }
                         /* Ausblenden des Low Anteils, wenn nicht gefordert */
                         if(! bmc_portinfo[minor].direction & BMC_LOW_WR)
                         {
                            byte &= 0xf0;
                         }
                         /* Schreiben des Bytes und Abspeichern in portinfo */
                         outb_p(byte, bmc_portinfo[minor].portaddr);
                         bmc_portinfo[minor].lastout=byte;
                         break;
          case BMC_T_STRB: /* I/O Port für Strobecontrol, Schreiben nur, wenn Port auf OUT */
                         if(bmc_portinfo[minor].direction != BMC_DIR_OUT) return(-EINVAL);
                         outb_p(byte, bmc_portinfo[minor].portaddr);
                         bmc_portinfo[minor].lastout=byte;
                         break;
          case BMC_T_STRBCTL:  /* Port dient als Strobe Port für Strobe I/O, I/O deaktiviert */
                         return(-EINVAL);
                         break;
          default: return(-EINVAL);
       }
    
       return(1L);
    }
    
    /*****************************************************************
    * ioctl() - Ändern und Abfragen von Geräteeinstellungen
    * Parameter:
    *    *inode .. Zeiger auf die zugehörige inode struktur
    *    *file  .. Zeiger auf die zugehörige file struktur
    *    cmd    .. ioctl Kommando
    *    arg    .. Argument des Kommandos
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 0
    ******************************************************************/
    static int bmc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
    {
       unsigned int  minor;
       unsigned char byte=0;
    
       /* ermitteln der minor Nummer */
       minor = MINOR(inode->i_rdev);
       /* ioctl für Port A und B nur im Strobe-IO Modus */
       if(minor < 2)
       {
          if(bmc_portinfo[minor].type == BMC_T_STRB)
          {
             switch(cmd)
             {
                case BMCIOCSTRBDIRIN:
                         bmc_portinfo[minor].direction=BMC_DIR_IN;
                         bmc_portinfo[minor].lastout  =0x0;
                         outb_p(0x0, bmc_portinfo[minor].portaddr);
                         /* Aktivieren der Einstellungen */
                         if(minor==0)
                         {
                            bmc_portinfo[3].lastout &= 0x8f;
                            bmc_portinfo[3].lastout |= BMC_STRB_AIN;
                         }
                         else
                         {
                            bmc_portinfo[3].lastout &= 0xf9;
                            bmc_portinfo[3].lastout |= BMC_STRB_BIN;
                         }
                         bmc_setportio();
                         break;
                case BMCIOCSTRBDIROUT:
                         bmc_portinfo[minor].direction=BMC_DIR_OUT;
                         bmc_portinfo[minor].lastout  =0x0;
                         outb_p(0x0, bmc_portinfo[minor].portaddr);
                         /* Aktivieren der Einstellungen */
                         if(minor==0)
                         {
                            bmc_portinfo[3].lastout &= 0x8f;
                            bmc_portinfo[3].lastout |= BMC_STRB_AOUT;
                         }
                         else
                         {
                            bmc_portinfo[3].lastout &= 0xf9;
                            bmc_portinfo[3].lastout |= BMC_STRB_BOUT;
                         }
                         bmc_setportio();
                         break;
                case BMCIOCGETIBF: /* gibt IBF zurück */
                      /* wenn Port auf OUT, dann Fehler */
                      if(bmc_portinfo[minor].direction == BMC_DIR_OUT) return(-EINVAL);
                      /* auslesen des Bits (maskiert und an Position 0 verschoben) */
                      byte = inb_p(bmc_portinfo[2].portaddr);
                      byte = (minor?(0x02 & byte) >> 1:(0x20 & byte) >> 5);
                      if(put_user(byte, (unsigned char*)arg))
                         return(-EFAULT);
                      break;
                case BMCIOCGETOBF: /* gibt OBF zurück */
                      /* wenn Port auf IN, dann Fehler */
                      if(bmc_portinfo[minor].direction == BMC_DIR_IN) return(-EINVAL);
                      /* auslesen des Bits (maskiert und an Position 0 verschoben) */
                      byte = inb_p(bmc_portinfo[2].portaddr);
                      byte = (minor?(0x02 & byte) >> 1:(0x80 & byte) >> 7);
                      if(put_user(byte, (unsigned char*)arg))
                         return(-EFAULT);
                      break;
                default: return(-EINVAL);
                         break;
             }
             return(0);
          }
          else return(-EBADF);
       }
    
       /* ioctl für Port C */
       switch(cmd)
       {
          case BMCIOCSETTYPE: /* Setzen des Porttyps */
                switch(arg)
                {
                   case BMC_PORT_IO: /* Port wird Standard I/O Port */
                         /* wenn Port C Strobecontrol war, dann A und B rücksetzen */
                         if(bmc_portinfo[2].type == BMC_T_STRBCTL) bmc_setdefaults(BMC_T_IO);
                         /* Setzen von Defaultwerten */
                         bmc_portinfo[2].type     =BMC_T_IO;
                         bmc_portinfo[2].direction=BMC_DIR_IN;
                         bmc_portinfo[2].lastout  =0x0;
                         outb_p(0x0, bmc_portinfo[2].portaddr);
                         /* Aktivieren des Port C als IN-Port */
                         bmc_portinfo[3].lastout |= BMC_BASIC_CIN;
                         bmc_setportio();
                         break;
                   case BMC_PORT_SPLIT_IO: /* Port wird Split-I/O Port */
                         /* wenn Port C Strobecontrol war, dann A und B rücksetzen */
                         if(bmc_portinfo[2].type == BMC_T_STRBCTL) bmc_setdefaults(BMC_T_IO);
                         /* Setzen von Defaultwerten */
                         bmc_portinfo[2].type     =BMC_T_SPLIT_IO;
                         bmc_portinfo[2].direction=BMC_HIGH_RD | BMC_LOW_RD;
                         bmc_portinfo[2].lastout  =0x0;
                         outb_p(0x0, bmc_portinfo[2].portaddr);
                         /* Aktivieren des Port C als splitted IN-Port */
                         bmc_portinfo[3].lastout |= BMC_BASIC_CIN;
                         bmc_setportio();
                         break;
                   case BMC_PORT_STRBCTL: /* Port wird zum Strobecontrol Port für Strobe I/O */
                         /* Setzen von Defaultwerten für Port C als Strobecontrol */
                         bmc_portinfo[2].type     =BMC_T_STRBCTL;
                         bmc_portinfo[2].direction=BMC_DIR_NONE;
                         bmc_portinfo[2].lastout  =0x0;
                         /* Port rücksetzen */
                         outb_p(0x0, bmc_portinfo[2].portaddr);
                         /* Setzen von Defaultwerten für Port A und B als Strobe-IO */
                         bmc_setdefaults(BMC_T_STRB);
                         /* Aktivieren der Einstellungen */
                         bmc_setportio();
                         break;
                   default: return(-EINVAL);
                }
                break;
          case BMCIOCSETDIR: /* Setzen der Portrichtung von H bzw L */
                /* Setzen wird nur zugelassen, wenn Port C vom Typ BMC_T_SPLIT_IO ist */
                if(bmc_portinfo[2].type != BMC_T_SPLIT_IO) return(-ENOTTY);
                switch(arg)
                {
                   /* bmc_portinfo[Port C}.direction bekommt als Flags die momentan
                      gesetzte Richtung von H und L als Bitmaske */
                   case BMC_HIGH_RD:
                                     bmc_portinfo[3].lastout   |=  BMC_BASIC_CHIN;
                                     bmc_portinfo[2].direction |=  BMC_HIGH_RD;
                                     bmc_portinfo[2].direction &= ~BMC_HIGH_WR;
                                     bmc_portinfo[2].lastout   &= 0x0f;
                                     break;
                   case BMC_HIGH_WR: bmc_portinfo[3].lastout   &= ~BMC_BASIC_CHIN;
                                     bmc_portinfo[2].direction |=  BMC_HIGH_WR;
                                     bmc_portinfo[2].direction &= ~BMC_HIGH_RD;
                                     break;
                   case BMC_LOW_RD :
                                     bmc_portinfo[3].lastout   |=  BMC_BASIC_CLIN;
                                     bmc_portinfo[2].direction |=  BMC_LOW_RD;
                                     bmc_portinfo[2].direction &= ~BMC_LOW_WR;
                                     bmc_portinfo[2].lastout   &= 0xf0;
                                     break;
                   case BMC_LOW_WR : bmc_portinfo[3].lastout   &= ~BMC_BASIC_CLIN;
                                     bmc_portinfo[2].direction |=  BMC_LOW_WR;
                                     bmc_portinfo[2].direction &= ~BMC_LOW_RD;
                                     break;
                   default: return(-EINVAL);
                }
                /* Aktivieren der Einstellungen */
                bmc_setportio();
                break;
          default: return(-EINVAL);
       }
       return(0);
    }
    
    /*****************************************************************
    * Struktur mit den Funktionen der deklarierten Systemrufe
    ******************************************************************/
    static struct file_operations bmc_fops = {
       open:     bmc_open,
       release:  bmc_release,
       read:     bmc_read,
       write:    bmc_write,
       ioctl:    bmc_ioctl
    };
    
    /*****************************************************************
    * Modulfunktion: Initialisieren der Karte beim Laden des Moduls
    * Parameter:
    *   keine
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 0
    ******************************************************************/
    int __init bmc_init(void)
    {
       int i;
       struct resource *resource;
    
       /* überprüfen der übergebenen Basisadresse */
       if(baseaddr < 0x210 || baseaddr > 0x330)
       {
          printk(KERN_ERR "BMC ERROR: invalid base address %#3x, driver not initialized\n", baseaddr);
          return(-EIO);
       }
    
       /* Überprüfen der Verfügbarkeit der angeforderten Port Adressen */
       if(check_region(baseaddr, 4))
       {
          printk(KERN_ERR "BMC ERROR: unable to initialize driver, port address already in use: %#3x\n", baseaddr);
          return(-EIO);
       }
       /* sperren der Port Adressen */
       resource=request_region(baseaddr, 4, "bmc");
    
       /* Registrieren des Treibers */
       if(register_chrdev(majornr, "bmc", &bmc_fops))
       {
          printk(KERN_ERR "BMC ERROR: unable to get major number %d\n", majornr);
          release_region(baseaddr, 4);
          return(-EIO);
       }
    
       /* Eintragen der Porteigenschaften und -infos in die portinfo Struktur */
       for(i=0;i<3;i++)
       {
          bmc_portinfo[i].portaddr = baseaddr+i;
          bmc_portinfo[i].fdcount  = 0;
          bmc_portinfo[i].type     = BMC_T_IO;
          bmc_portinfo[i].direction= BMC_DIR_IN;
          bmc_portinfo[i].lastout  = 0;
       }
       /* spezielle Eintragungen für ctl Port */
       bmc_portinfo[3].portaddr  = baseaddr+3;
       bmc_portinfo[3].fdcount   = 0;
       bmc_portinfo[3].type      = BMC_T_CTL;
       bmc_portinfo[3].direction = BMC_DIR_OUT;
       bmc_portinfo[3].lastout   = BMC_PORT_ALL_IN;
    
       /* Initialzustand der Ports: IN-Ports */
       outb_p(BMC_PORT_ALL_IN, bmc_portinfo[3].portaddr);
       printk(KERN_INFO "bmc: BMC PIO-II 24 card address from %#3lx to %#3lx\n", resource->start, resource->end);
    
       return(0);
    }
    
    module_init(bmc_init);
    
    #if defined(MODULE)
    /*****************************************************************
    * Modulfunktion: Aufräumarbeiten beim entladen des Moduls
    * Parameter:
    *   keine
    * Rückgabewert:
    *   Fehlercode bei Fehler, sonst 0
    ******************************************************************/
    static void __exit bmc_term_module(void)
    {
       /* Exitzustand der Ports: IN-Ports */
       outb_p(BMC_PORT_ALL_IN, bmc_portinfo[3].portaddr);
       /* Unregistrieren des Treibers */
       unregister_chrdev(majornr, "bmc");
       /* freigeben der Port Adressen */
       release_region(baseaddr, 4);
       printk(KERN_INFO "bmc: BMC PIO-II 24 card module unloaded\n");
    }
    
    module_exit(bmc_term_module);
    #endif
    


  • Ich finds ok.

    Bis auf diese Auflistung und Erlärung der Parameter/Rückgabewerte. "Parameter: keine" braucht kein Mensch und oft ist der Name ohnehin Eindeutig (bzw. sollte er sein). Im Zweifelsfall kann man's immer noch hinschreiben.



  • Ich find die Kommentierung nahezu perfekt. Man kann den Code sehr schön und schnell lesen und verstehen.



  • Mir gefällt es auch. Viel Code - viele Comments.

    Doch frage ich mich, warum Du

    /* */
    

    nimmst, anstatt (wenns nur die eine Zeile betrifft)

    //
    


  • Achja: Mein Grund für die Frage: Wegen dem vielen Kommentieren sparrst du mit

    //
    

    mehr Zeit.



  • Original erstellt von <Henrie>:
    **Mir gefällt es auch. Viel Code - viele Comments.

    Doch frage ich mich, warum Du

    /* */
    

    nimmst, anstatt (wenns nur die eine Zeile betrifft)

    //
    

    **

    Da es Zeiten gab wo "//" in C nicht erlaubt war.



  • "//" gehört auch nicht in den C-Standard, würd ich mal sagen. Das kam erst mit C++.

    Ich finde die Kommentierung ziemlich gut. Parameter: keine ist vielleicht ersichtlich, aber trotzdem sinnvoll. (ich gestehe, dass ich nicht so ausführlich bin)

    Bei mir hier auf arbeit gibt es PERL-Scripte, die Kommentare auswerten und damit dann Informationen über einzelne Programme / Funktionen liefern. Eigentlich ganz sinnvoll das Ganze. Man bekommt schneller nen Überblick.
    Außerdem geht's ja ums Prinzip. Wenn Du sagst, Du musst es hier nicht machen, dann fehlts irgendwann dort auch mal und schlußendlich lässt man's ganz sein, weil so ne einzelne Kommentarzeile irgendwo auch nichts mehr bringt..

    cYa
    DjR



  • Original erstellt von DocJunioR:
    **"//" gehört auch nicht in den C-Standard, würd ich mal sagen. Das kam erst mit C++.
    **

    ist aber trotzdem in C99 drin :p



  • Ich finde das auch ok.

    Gerade bei solchen Hardwareansteuerungen hat man aus Softwaresicht ja das Problem, daß man die Gegenseite nicht kennt. Ein

    bmc_portinfo[i].type =type;
    bmc_portinfo[i].direction=BMC_DIR_IN;
    bmc_portinfo[i].lastout =0x0;

    ohne die Info "Strobe-Impuls" ist völlig nichtssagend, weil man nicht überblickt warum der Port auf diese Art und Weise beschrieben wird.

    Bei hardwarenahen Programmieren muß jedes Detail kommentiert sein, weil man die ganzen Abläufe im Programm beschreiben muß (z.B. wie "danach beginnt sich der Motor zu drehen", wenn man Bit 7 setzt).



  • Jo, sind ganz nett viele Kommentare. Ich finde es nur komisch, dass die sich teilweise so ähnlich sind und in einer Methode, die irgendwas schreiben soll plötzlich genau das gleiche steht, wie in einer Methode, die etwas lesen soll. Sieht irgendwie nach Copy & Paste aus. Vielleicht solltest du auch darauf achten, dass das, was in den Kommentaren steht, richtig ist. Naja, vielleicht ist das ja auch richtig so, das mußt du wissen. Ich finde es nur seltsam, wenn in der Methode bmc_write plötzlich von einer "Leseaktionen in Abhängigkeit des gesetzten Porttyps" die Rede ist.

    [ Dieser Beitrag wurde am 04.07.2003 um 09:16 Uhr von Gregor editiert. ]



  • Ihr wollt mir doch nicht weiß machen, dass ihr den Code gelesen und verstanden habt?! 😮 😮 😮



  • Original erstellt von <Stift>:
    **Den habe ich gerade gefunden. Ist das nicht ein bisschen übertrieben mit den
    Kommentaren?
    **

    Ist doch super Dokumentiert.
    Ich hab' schon Teile gesehen, da war noch viel
    mehr Kommentar drin - ohne dass es zu viel war!

    Inhaltlich sollte das aber überarbeitet werden! Anpassen nach Copy/Paste 😃



  • Ich habe mir schon vor Jahren angewöhnt, jede Methode am Kopf ausführlich und (für mich) genormt zu dokumentieren. Ich habe mal eine Stringklasse geschrieben und die immer wieder erweitert. Wenn ich hierzu keine Dokumentation gemacht hätte (im Source und als seperates Word-Dokument), würd ich ständig mit der Nase im alten Source hängen und kucken, was da tatsächlich passiert.

    Hier mal eine Kostprobe einer etwas komplexeren Methode:

    UBYTE __fastcall TStringMachine::CompEx(STRP strpFirst, STRP strpSecond, BOOL blCaseSensitive, ULONG lFSize, ULONG lSSize, ULONG lFPos, ULONG lSPos)
    {
    /***[FUNKTION 0032h]*********************************************************
    ANMERKUNG -------------------------------------------------------------------
    Stark erweiterte Version von Comp: vergleicht zwei Strings miteinander, wobei
    es sich bei jedem String um einen STRP, STRZP oder SHANDLE handeln kann. Auch
    Teilstrings können miteinander verglichen werden. Die Stringgrößen können
    gleich oder unterschiedlich sein. Als zusätzli-ches Feature kann angegeben
    werden, ob Groß/Kleinschreibung berücksichtigt werden soll oder nicht.
    
    AUFRUF ----------------------------------------------------------------------
    STRP strpFirst {1 bis X + [CPT_SHANDLE]}
    STRP strpSecond   {1 bis X + [CPT_SHANDLE]}
    
    Zeiger auf den ersten String, der mit dem zweiten String verglichen werden
    soll. Handelt es sich bei dem String um einen SHANDLE, so ist das Flag
    CPT_SHANDLE zu setzen (über Makro SM_STRING(x) möglich). Handelt es sich um
    einen STRP, so muß die Größe des Strings zwingend in lFSize bzw. lSSize
    angegeben werden.
    HINWEIS: die Stringtypen von strpFirst und strpSecond dürfen voneinander
    abweichen!
    
    BOOL blCaseSensitive {FALSE, TRUE}           [STANDARD: FALSE]
    
    Wird FALSE (Standardwert) angegeben, wird keine Unterscheidung bei Groß/
    Kleinbuchstaben vorgenommen. Dabei werden auch die deutschen Umlaute (ä, ö,
    ü) korrekt umgesetzt!
    Wird hingegen FALSE angegeben, so wird jedes Zeichen mit seinem Zeichencode
    (Grundlage bildet dabei der ANSI-Zeichencode) bewertet.
    
    ULONG lFSize   {0 bis SM_MAXSIZE}                      [STANDARD: NULL]
    ULONG lSSize   {0 bis SM_MAXSIZE}                      [STANDARD: NULL]
    
    Hier kann die Größe des Strings in strpFirst bzw. strpSecond in Bytes
    angegeben werden. Handelt es sich bei strpFirst bzw. strpSecond um einen
    STRP, muß die Größe zwingend angegeben werden, andernfalls ist die Angabe
    optional.
    Wird NULL (Standardwert) angegeben, bestimmt die Methode die Stringgröße
    selbst (bei STRZP bis zu einem abschließenden Nullbyte; bei SHANDLE bis zum
    Stringende).  Bei einem SHANDLE wird lFSize bzw. lSSize maximiert!
    
    ULONG lFPos {0 bis X}                               [STANDARD: NULL]
    ULONG lSPos {0 bis X}                               [STANDARD: NULL]
    
    Diese Parameter geben an, ab welchem Offset mit dem Vergleich begonnen werden
    soll. Der Wert wird auf die Adresse in strpFirst bzw. strpSecond aufaddiert.
    Ein Wert von NULL besagt, daß beim ersten Zeichen begonnen werden soll, ein
    Wert von 1 bezieht sich auf die zweite Zeichenposition und so weiter.
    Der Offset macht hauptsächlich bei SHANDLEs Sinn, weil damit (ggf. in
    Verbindung mit lFSize bzw. lSSize) Teilstrings definiert werden können, ohne
    diese vorher extrahieren zu müssen.
    
    RÜCKGABE --------------------------------------------------------------------
    UBYTE {Fehler: NULL, Erfolg: siehe nachfolgende Auflistung}
    
    Konnten beide Strings miteinander verglichen werden, wird das Testergebnis
    mitgeteilt. Wurden unzulässige Parameter angegeben, wird lediglich NULL
    zurückgeliefert.
    Das Ergebnis ist stets als Vergleich von strpFirst mit strpSecond zu sehen.
    Sind alle Zeichen von strpFirst in strpSecond enthalten, wird SMC_EQUAL
    zurückgeliefert. Ist mindestens ein Zeichen unterschiedlich, wird SMC_UNEQUAL
    zurückgegeben. Sind alle Zeichen von strpFirst in strpSecond enthalten, aber
    sind beide Strings unterschiedlich groß, wird neben SMC_EQUAL auch
    SMC_GREATER (strpFirst > strpSecond) oder SMC_LESS (strpFirst < strpSecond)
    zurückgegeben. Sind beide String unterschiedlich, wird SMC_UNEQUAL
    zurückgegeben und die Aussage, ob der String in strpFirst bei einer
    Sortierung größer (SMC_GREATER) oder kleiner (SMC_LESS) ist. Beispiele:
    
    strpFirst   = "Test"    strpSecond  = "Testzweck"
    Ergebnis: SMC_EQUAL|SMC_LESS, weil strpFirst kleiner als strpSecond ist, aber
    alle Zeichen von strpFirst in strpSecond vorkommen.
    
    strpFirst   = "Testzweck"  strpSecond  = "Test"
    Ergebnis: SMC_UNEQUAL|SMC_GREATER, weil strpFirst größer als strpSecond ist
    und nicht alle Zeichen von strpFirst in strpSecond vorkommen.
    
    strpFirst   = "Test"    strpSecond  = "Test"
    Ergebnis: SMC_EQUAL, weil alle Zeichen von strpFirst in strpSecond vorkommen
    und beide Strings exakt gleich groß sind.
    
    strpFirst   = "ABCD"    strpSecond  = "FGD"
    Ergebnis: SMC_UNEQUAL|SMC_LESS, weil strpFirst nicht in strpSecond vorkommt
    und bei einer Sortierung vor strpSecond angesiedelt ist.
    ****************************************************************************/
    

    Die Formatierug sieht hier vermutlich etwas blöd aus, ist aber auch auf 76 Zeichen/Zeile ausgelegt.

    Es kostet zwar eine gewisse Disziplin, aber es rentiert sich, wenn man die Sachen später wiederverwenden will und das ist ja heutzutage nicht unwichtig.

    Wenn ich neue Klassen bilde, schreib ich meist ein Word-Dokumentationsfile dazu und dokumentiere erstmal alle öffentlichen Methoden und Eigenschaften. Dann erst wieder codiert. Interne Methoden dokumentiere ich allerdings nur im Source.
    Beruflich würde ich natürlich alles komplett dokumentieren, aber für privat reichts sicherlich aus 😃

    Außerdem denkt man durch diese Vorgehensweise erstmal ein bissel nach und codiert erst dann, so daß man sich viele Neuprogrammierungen sparen kann.

    Kann ich also nur empfehlen. Die Doku oben find ich übrigens nach dem ersten Augenschein auch nicht schlecht, wenn man sicher auch noch ausführlicher sein könnte....



  • Original erstellt von JFK:
    Hier mal eine Kostprobe einer etwas komplexeren Methode

    Das kann ja wohl unmöglich dein Ernst sein...:)



  • wieso nicht?



  • Der ist besonders gut:

    /* Wird FALSE (Standardwert) angegeben, wird keine Unterscheidung bei Groß/
    Kleinbuchstaben vorgenommen. Dabei werden auch die deutschen Umlaute (ä, ö,
    ü) korrekt umgesetzt!
    Wird hingegen FALSE angegeben, so wird jedes Zeichen mit seinem Zeichencode
    (Grundlage bildet dabei der ANSI-Zeichencode) bewertet. */
    

    Warum das nicht dein Ernst sein kann? Aus mehreren Gründen:
    1. Eine so ausführliche Dokumentation gehört nicht in den Code. Im Code soll knapp und präzise beschrieben sein, wie man die Funktion aufruft oder was sie macht. Einen Parameter wie CaseSensitive zu dokumentieren, ist nicht wirklich sinnvoll. Der ist selbsterklärend. Der Hinweis mit den Umlauten gehört in die externe Doku.
    2. Wenn der Rückgabewert so klar und einfach über Flags definiert ist, reicht es die möglichen Flags kurz aufzulisten. Normalerweise sollte der Name schon aussagekräftig genug sein, sodass man gar nicht mehr in die Doku schauen muss.
    3. Beispiele gehören nicht in Kommentare. Schon gar nicht so viele.

    btw:
    Wie war das nochmal mit dem Präfix?



  • Also da muß ich wiedersprechen. Wer bitteschön sagt mir, daß ich im Source zu einer Methode keine 10, 20 oder 100 Zeilen Kommentar reinschreiben kann? Wie ich bereits schrieb, ist die Funktionsbeschreibung aus der zugehörigen Dokumentation übernommen (natürlich nur die Texte, die Darstellung in der Doku sieht ein bissel anders aus).

    Außerdem hab ich mir ein Tool geschrieben, welches in der Lage ist, die Kommentare zu einer Methode auf Knopfdruck anzuzeigen. Das kann die Dokumentation oftmals ersetzen, wenn bekannt ist, wie die Klasse zu bedienen ist bzw. wie die Schnittstelle zu bedienen ist.

    Hat man mehrere Klassen und für jede eine Dokumentation (am Besten dann gleich ausgedruckt), ist man nämlich nur noch am rumblättern und wird irgendwann von einer Leitz-Ordner-Lawine erschlagen.

    Zum CaseSensitive: wer sagt Dir denn, daß es "normal" ist, die deutschen Umlaute zu berücksichtigen? Wenn ich alles, was ich als normal empfinde, annehmen würde, dann hätte ich einige Probleme.

    Was die Beispiele anbelangt, so gilt das oben gesagte. Manchmal sind 3 Beispiele besser als gar keines.

    Das ist übrigens nur ein Beispiel einer Methode gewesen. Andere Methoden können durchaus kürzer sein.

    Abgesehen davon ist das halt meine Methode, meinen Source zu dokumentieren und es war ja nur ein Vorschlag, wie ich das handhabe. Ich bin überzeugt, daß das die wenigsten so machen, aber das is nich mein Problem, weil ich deren Code nicht in meinen Programmen habe.... 🕶

    Noch ein Letztes zu dem Präfix: an so einer Diskussion beteilige ich mich nicht (mehr). Jeder sollte seinen Stil haben und ihn beibehalten, wenn er sich für ihn als nützlich herausgestellt hat. Arbeiten mehrere Leute zusammen, bietet es sich natürlich an, sich auf eine Vorgehensweise zu einigen. Allerdings halte ich am Präfix fest, weil ich das extrem sinnvoll finde und mich längst daran gewöhnt habe. Dadurch komme ich auch gar nich erst auf die Idee, einem strpPtr einen iValue zuzuweisen.



  • Ich würde auch so lange Doku´s in den Code schreiben. Obwohl die Parameter / Funktionsnamen für sich sprechen sollten.

    Mit Doxygen eine Docu draus machen, und passt!

    Meine Header docs sehen so aus:

    //! Returns how much files matching the mask
        /*! Files returned in sOutFile
            You need to free the struct with 
            IPacker::FreeFileStruct( SPackFile **sPackFile )
        */
        virtual unsigned int  FindFiles( SPackFile **sOutFile, const char *_chMask )                        = 0;
    
        //! Free´s the file struct - allocated in FindFiles
        virtual void FreeFileStruct( SPackFile **sPackFile )                                                = 0;
    
        //! Returns the compression level
        /*! 1 is low compression and the best speed
        9 is the best
        0 is no compression */
        virtual int  GetCompressionLevel()                                                  = 0;
    

    Und im Source:

    bool CPacker::CompressRam( char **_chCompressed, int &_iCompresedSize, char *_chData, int _iSize )
    {
        PUSH_TREE( "CompressRam", "CPacker" );
    
        // Allocate memory for the compressed data
        *_chCompressed  = new char[ _iSize * 2 + 100 ];// char[ _iSize + 100 ];
        _iCompresedSize = _iSize + 100;
    
        // Compress
        if( PrintError( compress2( (unsigned char*)*_chCompressed, (unsigned long*)&_iCompresedSize, (const Bytef*)_chData, _iSize, iCompressionLevel ) ) )
        {
            delete [] *_chCompressed;
            POP_TREE();
            return false;
        }
    
        POP_TREE();
        // Done successful
        return true;
    }
    

    Ist jetzt nur kleines Beispiel, aber wer versuch nach 1 Jahr den Code wieder zu lesen, dankt es sich sebst, wenn er viel Docu geschrieben hat.

    Lieber zuviel Docu als zu wenig! Wenn man ohne sie auskommt, dann ists auch gut. Man muß sie ja nicht lesen



  • @ SnorreDev: Ich dachte, man soll keine Unterstriche bei Variablennamen voranstellen!? Habe ich da etwas falsch verstanden?

    Da ist eine typische von mir geschriebene und dokumentierte Javaklasse 😃 :
    [java]
    package myMath.function.distribution;

    import myMath.function.*;

    public class Gaussian extends Distribution
    {
    private Function gaussian;

    public Gaussian (int dimensions, float standardDeviation)
    {
    Function normalizer = new ConstFunction ((float)(standardDeviation*
    standardDeviation*2.0*Math.PI));
    normalizer = new PowerFunction (normalizer,
    new ConstFunction (-0.5f*(float)dimensions));
    Function [] tempFunctions = new Function [dimensions];
    for (int i = 0 ; i < dimensions ; ++i)
    {
    tempFunctions[i] = new PowerFunction (new Variable(i),
    new ConstFunction(2.0f));
    }
    Function exponent = new AddFunction (tempFunctions);
    exponent = new MultiplyFunction (new ConstFunction (-1.0f),exponent);
    exponent = new DivideFunction (exponent,
    new ConstFunction (2.0f*standardDeviation*standardDeviation));
    gaussian = new MultiplyFunction (normalizer,new ExpFunction(exponent));
    }

    public float getValue(float[] variables)
    {
    return gaussian.getValue(variables);
    }

    protected Function getDerivative(int variable)
    {
    return gaussian.derive(variable);
    }

    public Function simplify()
    {
    return gaussian.simplify();
    }

    public String toString ()
    {
    return gaussian.toString();
    }
    }[/code]
    ...ich finde, die Doku reicht vollkommen aus. Ich sehe keine Stelle, wo mehr Doku mehr Lesbarkeit bringt, die jemand benötigt, der die Klasse benutzt.

    BTW: Was mir beim ersten Code noch auffällt:

    Mir ist nicht ganz klar, ob der Schreiber davon ausgeht, ob der, der den Code liest Deutsch spricht, oder ob er Englisch spricht.

    Wenn er Deutsch sprechen soll: Warum sind die Variablennamen auf Englisch?
    Wenn er Englisch sprechen soll: Warum ist die Doku auf Deutsch?

    [ Dieser Beitrag wurde am 05.07.2003 um 07:46 Uhr von Gregor editiert. ]



  • also für mich ist das das krasse Gegenbeispiel zu meinem. Um zu wissen, was die Methode leistet, muß ich mir erstmal den Code ankucken. Bei den reinen Zugriffsmethoden mag das ja noch angehen.

    Aber selbst da tut eine kurze Info Not, WAS denn da eigentlich zurückkommt und welche Wertbereiche da möglich sind.

    Wenn ich 50 Klassen verwenden muß und hab keinerlei Anhaltspunkte, was das passiert, dann bin ich als Programmierer aber wirklich arm dran, wenn ich mich durch alle Methoden durchwühlen muß, bis ich die richtige Gefunden hab.

    Meiner Meinung nach extrem kurzsichtig gedacht.... oder Du bist so ein Genie, daß du schneller den Code im Kopf in eine Programmdoku umsetzt, also 4 Sätze zu lesen.... Ist aber eher zu bezweifeln.


Anmelden zum Antworten