/** Autor komentarzy w języku polskim: Piotr Korzniakow **/
 

/*
 *  linux/fs/devices.c
 *
 * (C) 1993 Matthias Urlichs -- collected common code and tables.
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  Added kerneld support: Jacques Gelinas and Bjorn Ekwall
 */

#include <linux/config.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/ext_fs.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#ifdef CONFIG_KERNELD
#include <linux/kerneld.h>

#include <linux/tty.h>

/* serial module kerneld load support */
struct tty_driver *get_tty_driver(kdev_t device);
#define isa_tty_dev(ma) (ma == TTY_MAJOR || ma == TTYAUX_MAJOR)
#define need_serial(ma,mi) (get_tty_driver(MKDEV(ma,mi)) == NULL)
#endif

struct device_struct {
 const char * name;
 struct file_operations * fops;
};

/** Nadanie wartości początkowej dla tablicy urządzeń znakowych.
    Na początku nie ma żadnych urządzeń. **/
static struct device_struct chrdevs[MAX_CHRDEV] = {
 { NULL, NULL },
};

/** Nadanie wartości początkowej dla tablicy urządzeń blokowych.
    Na początku nie ma żadnych urządzeń.**/
static struct device_struct blkdevs[MAX_BLKDEV] = {
 { NULL, NULL },
};

/** Funkcja wpisująca listę istniejących urządzeń **/
int get_device_list(char * page)
{
 int i;
 int len;

/** Wpisanie nagłówka **/
 len = sprintf(page, "Character devices:\n");
/** Petlą przechodzi tablicę urządzeń znakowych **/
 for (i = 0; i < MAX_CHRDEV ; i++) {
/** Jeśli na danej pozycji w tablicy urządzeń znajduje się jakieś urządzenie to wpisywana jest jego nazwa **/
  if (chrdevs[i].fops) {
   len += sprintf(page+len, "%2d %s\n", i, chrdevs[i].name);
  }
 }

 /** wpisanie nagłówka **/
 len += sprintf(page+len, "\nBlock devices:\n");
/** Pętla przechodzi tablicę urządzeń znakowych **/
 for (i = 0; i < MAX_BLKDEV ; i++) {
/** Jeśli na danej pozycji w tablicy urządzeń znajduje się jakieś urządzenie to wpisywana jest jego nazwa **/
  if (blkdevs[i].fops) {
   len += sprintf(page+len, "%2d %s\n", i, blkdevs[i].name);
  }
 }
/** Zwracana jest długość powstałego napisu **/
 return len;
}

/*
 Return the function table of a device.
 Load the driver if needed.
*/

/**
 Funkcja zwraca tablicę operacji dla danego urządzenia.
 Jeśli trzeba ładowany jest także sterownik urządzenia.
**/
static struct file_operations * get_fops(
 unsigned int major,
 unsigned int minor,
 unsigned int maxdev,
 const char *mangle, /** Napis, z którego powstanie nazwa modułu. **/
 struct device_struct tb[])
{
 struct file_operations *ret = NULL;

 if (major < maxdev){
#ifdef CONFIG_KERNELD
  /*
   * I do get request for device 0. I have no idea why. It happen
   * at shutdown time for one. Without the following test, the
   * kernel will happily trigger a request_module() which will
   * trigger kerneld and modprobe for nothing (since there
   * is no device with major number == 0. And furthermore
   * it locks the reboot process :-(
   *
   * Jacques Gelinas (jacques@solucorp.qc.ca)
   *
   * A. Haritsis <ah@doc.ic.ac.uk>: fix for serial module
   *  though we need the minor here to check if serial dev,
   *  we pass only the normal major char dev to kerneld
   *  as there is no other loadable dev on these majors
   */
/**
Funkcja sprawdza czy podany jako parametr numer główny jest różny od zera.
Jest to konieczne gdyż, z niewiadomych przyczyn, w momencie kończenia działania systemu
ta funkcja jest wołana z parametrem major równym zero. Nie obsłużone spowodowałoby to
zablokowanie procesu "reboot'owania".
**/

/**
Jeśli parametr major jest poprawny (większy od zera) i  nie jest dla takiego urządzenia
określona tablica operacji ładowany jest sterownik tego urządzenia.
**/
  if ((isa_tty_dev(major) && need_serial(major,minor)) ||
      (major != 0 && !tb[major].fops)) {
   char name[20];
   sprintf(name, mangle, major);
   request_module(name);
  }
#endif
  ret = tb[major].fops;
 }
/** Zwracana jest znaleziona tablica operacji dla danego urządzenia. **/
 return ret;
}
 

/*
 Return the function table of a device.
 Load the driver if needed.
*/
/**
 Funkcja zwraca tablicę operacji dla danego urządzenia.
 Jeśli trzeba ładowany jest także sterownik urządzenia.
**/
struct file_operations * get_blkfops(unsigned int major)
{
 return get_fops (major,0,MAX_BLKDEV,"block-major-%d",blkdevs);
}

struct file_operations * get_chrfops(unsigned int major, unsigned int minor)
{
 return get_fops (major,minor,MAX_CHRDEV,"char-major-%d",chrdevs);
}

/**
 Funkcja rejestrująca urządzenie znakowe.
**/
int register_chrdev(unsigned int major,   /** numer główny urządzenia**/
      const char * name,   /** nazwa urządzenia **/
      struct file_operations *fops /** tablica operacji **/
)
{
 if (major == 0) {
/**
Jeśli jako numer główny podano zero to szukany jest największy wolny numer główny
i urządzenie jest rejestrowane z tym właśnie numerem. Do tablicy urządzeń w pole
o odpowiednim indeksie jest wstawiana nazwa urządzenia i wskaźnik do tablicy operacji.
**/
  for (major = MAX_CHRDEV-1; major > 0; major--) {
   if (chrdevs[major].fops == NULL) {
    chrdevs[major].name = name;
    chrdevs[major].fops = fops;
    return major;
   }
  }
/** Jeśli brak wolnego numeru głównego zwracany jest błąd -EBUSY. **/
  return -EBUSY;
 }
/**
Sprawdzenie czy numer główny nie jest większy od maksymalnej ilości
urządzeń znakowych określonej przez stałą MAX_CHRDEV. Jeśli tak to błąd EINVAL.
**/
 if (major >= MAX_CHRDEV)
  return -EINVAL;
/**
Sprawdzeni czy dla urządzenia o podanym numerze głównym nie jest już określona tablica operacji.
Jeśli tak i jest ona różna od określonej przez parametr fops to błąd EBUSY.
W przeciwnym przypadku rejestracja - wpisanie nazwy i wskaźnika do tablicy operacji.
**/
 if (chrdevs[major].fops && chrdevs[major].fops != fops)
  return -EBUSY;
 chrdevs[major].name = name;
 chrdevs[major].fops = fops;
/** Jeśli rejestracja się powiodła zwracane jest zero **/
 return 0;
}

/**
 Funkcja rejestrująca urządzenie blokowe.
**/
int register_blkdev(unsigned int major,  /** numer główny **/
      const char * name,  /** nazwa urządzenia **/
      struct file_operations *fops/** wskaźnik do tablicy operacji **/
)
{
 if (major == 0) {
/**
Jeśli jako numer główny podano zero to szukany jest największy wolny numer główny
i urządzenie jest rejestrowane z tym właśnie numerem. Do tablicy urządzeń w pole
o odpowiednim indeksie jest wstawiana nazwa urządzenia i wskaźnik do tablicy operacji.
**/
  for (major = MAX_BLKDEV-1; major > 0; major--) {
   if (blkdevs[major].fops == NULL) {
    blkdevs[major].name = name;
    blkdevs[major].fops = fops;
    return major;
   }
  }
/** Jeśli brak wolnego numeru głównego zwracany jest błąd -EBUSY. **/
  return -EBUSY;
 }
/**
Sprawdzenie czy numer główny nie jest większy od maksymalnej ilości
urządzeń znakowych określonej przez stałą MAX_BLKDEV. Jeśli tak to błąd EINVAL.
**/
 if (major >= MAX_BLKDEV)
  return -EINVAL;
/**
Sprawdzeni czy dla urządzenia o podanym numerze głównym nie jest już określona tablica operacji.
Jeśli tak i jest ona różna od określonej przez parametr fops to błąd EBUSY.
W przeciwnym przypadku rejestracja - wpisanie nazwy i wskaźnika do tablicy operacji.
**/
 if (blkdevs[major].fops && blkdevs[major].fops != fops)
  return -EBUSY;
 blkdevs[major].name = name;
 blkdevs[major].fops = fops;
/** Jeśli rejestracja się powiodła zwracane jest zero **/
 return 0;
}

/**
Funkcja odrejestrowująca urządzenie znakowe.
**/
int unregister_chrdev(unsigned int major, const char * name)
{
/**
Sprawdzenie czy numer główny nie jest większy od maksymalnej ilości
urządzeń znakowych określonej przez stałą MAX_CHRDEV. Jeśli tak to błąd EINVAL.
**/
 if (major >= MAX_CHRDEV)
  return -EINVAL;
/**
Sprawdzenie czy dla urządzenia o podanym numerze głównym jest określona tablica operacji.
Czyli czy jest ono zarejestrowane. Jeśli nie to błąd EINVAL.
**/
 if (!chrdevs[major].fops)
  return -EINVAL;
/**
Sprawdzenie czy dla urządzenia o podanym numerze głównym nazwa w tablicy urządzeń
jest identyczna z podaną. Jeśli nie to błąd EINVAL.
W przeciwnym przypadku wyrejestrowanie - usunięcie nazwy i wskaźnika do tablicy operacji.
**/
 if (strcmp(chrdevs[major].name, name))
  return -EINVAL;
 chrdevs[major].name = NULL;
 chrdevs[major].fops = NULL;
 return 0;
}

int unregister_blkdev(unsigned int major, const char * name)
{
/**
Sprawdzenie czy numer główny nie jest większy od maksymalnej ilości
urządzeń znakowych określonej przez stałą MAX_BLKDEV. Jeśli tak to błąd EINVAL.
**/
 if (major >= MAX_BLKDEV)
  return -EINVAL;
/**
Sprawdzenie czy dla urządzenia o podanym numerze głównym jest określona tablica operacji.
Czyli czy jest ono zarejestrowane. Jeśli nie to błąd EINVAL.
**/
 if (!blkdevs[major].fops)
  return -EINVAL;
/**
Sprawdzenie czy dla urządzenia o podanym numerze głównym nazwa w tablicy urządzeń
jest identyczna z podaną. Jeśli nie to błąd EINVAL.
W przeciwnym przypadku wyrejestrowanie - usunięcie nazwy i wskaźnika do tablicy operacji.
**/
 if (strcmp(blkdevs[major].name, name))
  return -EINVAL;
 blkdevs[major].name = NULL;
 blkdevs[major].fops = NULL;
 return 0;
}

/*
 * This routine checks whether a removable media has been changed,
 * and invalidates all buffer-cache-entries in that case. This
 * is a relatively slow routine, so we have to try to minimize using
 * it. Thus it is called only upon a 'mount' or 'open'. This
 * is the best way of combining speed and utility, I think.
 * People changing diskettes in the middle of an operation deserve
 * to loose :-)
 */

/**
Funkcja sprawdza czy nastąpiła zamiana wymienialnego urządzenia
(w praktyce chodzi tu o wymienialne dyski). Jeśli tak to bufory takiego
urządzenia są oznaczane jako nieaktualne. Jest to stosunkowo czasochłonna
funkcja i należy minimalizować liczbę jej wywołań. Z tego powodu jest ona
wywoływana tylko przy wykonywaniu operacji 'open' i 'mount'.
**/
int check_disk_change(kdev_t dev)
{
 int i;
 struct file_operations * fops;

 i = MAJOR(dev);
/**
Jeśli numer główny danego urządzenia jest większy od stałej MAX_BLKDEV
(maksymalna ilość urządzeń blokowych) lub dla urządzenia o danym numerze
głównym nie jest określona tablica operacji to zwracane jest zero.
**/
 if (i >= MAX_BLKDEV || (fops = blkdevs[i].fops) == NULL)
  return 0;
/**
Jeśli dla danego urządzenia nie jest określona funkcja sprawdzająca
zmianę tego urządzenia to zwracane jest zero.
**/
 if (fops->check_media_change == NULL)
  return 0;
/**
Jeśli nie nastąpiła zmiana urządzenia to zwracane jest zero.
**/
 if (!fops->check_media_change(dev))
  return 0;

/** W tym miejscu wiadomo, że nastąpiła zmiana urządzenia. **/
 printk(KERN_DEBUG "VFS: Disk change detected on device %s\n",
  kdevname(dev));
 for (i=0 ; i<NR_SUPER ; i++)
  if (super_blocks[i].s_dev == dev)
   put_super(super_blocks[i].s_dev);
/** deaktualizacja i-węzłów **/
 invalidate_inodes(dev);
/** deaktualizacja buforów **/
 invalidate_buffers(dev);

/**
Jeśli dla danego urządzenia jest określona funkcja uaktualniająca to zostaje wykonana.
**/
 if (fops->revalidate)
  fops->revalidate(dev);
/**
Jeśli nastąpiła zmiana urządzenia i wszystko się powiodło zwracana jest wartość jeden.
**/
 return 1;
}

/*
 * Called every time a block special file is opened
 */

/**
Funkcja wywoływana przy każdym otwarciu pliku specjalnego dla urządzenia blokowego.
**/
int blkdev_open(struct inode * inode, struct file * filp)
{
 int ret = -ENODEV;
/**
Znalezienie tablicy operacji dla danego urządzenia (przez numer główny).
**/
 filp->f_op = get_blkfops(MAJOR(inode->i_rdev));
/**
Jeśli tablica operacji jest określona i jest w niej określona
operacja 'open' to jest ona wykonywana.
**/

 if (filp->f_op != NULL){
  ret = 0;
  if (filp->f_op->open != NULL)
   ret = filp->f_op->open(inode,filp);
 }
/**
Zwracane wartości:
 jeśli nie jest określona tablica operacji błąd ENODEV (-ENODEV)
 jeśli nie jest określona operacja 'open' zero
 w przeciwnym przypadku wynik operacji open
**/
 return ret;
}

/**
Funkcja wywoływana przy każdym zwolnieniu i-węzłą dla urządzenia blokowego.
**/
void blkdev_release(struct inode * inode)
{
/**
Znalezienie tablicy operacji dla danego urządzenia (przez numer główny).
**/
 struct file_operations *fops = get_blkfops(MAJOR(inode->i_rdev));
/**
Jeśli tablica operacji jest określona i jest w niej określona
operacja 'release' to jest ona wykonywana.
**/
 if (fops && fops->release)
  fops->release(inode,NULL);
}
 

/*
 * Dummy default file-operations: the only thing this does
 * is contain the open that then fills in the correct operations
 * depending on the special file...
 */

/**
Definicja domyślnych operacji plikowych. Zawiera tylko operację
'open', która po swym wykonaniu uzupełni ją poprawnymi operacjami.
**/
struct file_operations def_blk_fops = {
 NULL,  /* lseek */
 NULL,  /* read */
 NULL,  /* write */
 NULL,  /* readdir */
 NULL,  /* select */
 NULL,  /* ioctl */
 NULL,  /* mmap */
 blkdev_open, /* open */
 NULL,  /* release */
};

struct inode_operations blkdev_inode_operations = {
 &def_blk_fops,  /* default file operations */
 NULL,   /* create */
 NULL,   /* lookup */
 NULL,   /* link */
 NULL,   /* unlink */
 NULL,   /* symlink */
 NULL,   /* mkdir */
 NULL,   /* rmdir */
 NULL,   /* mknod */
 NULL,   /* rename */
 NULL,   /* readlink */
 NULL,   /* follow_link */
 NULL,   /* readpage */
 NULL,   /* writepage */
 NULL,   /* bmap */
 NULL,   /* truncate */
 NULL   /* permission */
};

/*
 * Called every time a character special file is opened
 */

/**
Funkcja wywoływana przy każdym otwarciu pliku specjalnego dla urządzenia znakowego.
**/
int chrdev_open(struct inode * inode, struct file * filp)
{
 int ret = -ENODEV;
/**
Znalezienie tablicy operacji dla danego urządzenia (przez numer główny).
**/

 filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
/**
Jeśli tablica operacji jest określona i jest w niej określona
operacja 'open' to jest ona wykonywana.
**/
 if (filp->f_op != NULL){
  ret = 0;
  if (filp->f_op->open != NULL)
   ret = filp->f_op->open(inode,filp);
 }
/**
Zwracane wartości:
 jeśli nie jest określona tablica operacji błąd ENODEV (-ENODEV)
 jeśli nie jest określona operacja 'open' zero
 w przeciwnym przypadku wynik operacji open
**/
 return ret;
}

/*
 * Dummy default file-operations: the only thing this does
 * is contain the open that then fills in the correct operations
 * depending on the special file...
 */
/**
Definicja domyślnych operacji plikowych. Zawiera tylko operację
'open', która po swym wykonaniu uzupełni ją poprawnymi operacjami.
**/
struct file_operations def_chr_fops = {
 NULL,  /* lseek */
 NULL,  /* read */
 NULL,  /* write */
 NULL,  /* readdir */
 NULL,  /* select */
 NULL,  /* ioctl */
 NULL,  /* mmap */
 chrdev_open, /* open */
 NULL,  /* release */
};

struct inode_operations chrdev_inode_operations = {
 &def_chr_fops,  /* default file operations */
 NULL,   /* create */
 NULL,   /* lookup */
 NULL,   /* link */
 NULL,   /* unlink */
 NULL,   /* symlink */
 NULL,   /* mkdir */
 NULL,   /* rmdir */
 NULL,   /* mknod */
 NULL,   /* rename */
 NULL,   /* readlink */
 NULL,   /* follow_link */
 NULL,   /* readpage */
 NULL,   /* writepage */
 NULL,   /* bmap */
 NULL,   /* truncate */
 NULL   /* permission */
};

/*
 * Print device name (in decimal, hexadecimal or symbolic) -
 * at present hexadecimal only.
 */
/**
/**
Funkcja wypisująca nazwę urządzenia podanego jako parametr.
Zwraca wskaźnik do danej statycznej !
**/
char * kdevname(kdev_t dev)
{
 static char buffer[32];
 sprintf(buffer, "%02x:%02x", MAJOR(dev), MINOR(dev));
 return buffer;
}