simeonz wrote:
For example file_read() calls fat32_read(), fat32_read_cluster() calls partition_read() and partition_read() calls ata_read() that needs device number also.
I propose that you don't work with device numbers at all. Those are intended for userland and can be supported as such by lookup tables. Each kernel object in the storage stack should directly reference the objects underneath it (the device object for the enclosing storage space) and store any additional parameters that describe its layout.
If the underlying storage happens to be some partition, it will behave just like an unpartitioned device as far as the filesystem is concerned. The partition object will keep a field referencing the enclosing disk device and its first and last sectors. But the partition device and the disk device are essentially the same abstraction - contiguous sector storage. The device references and other parameters are established when the relevant kernel objects are created - when the file system is mounted (automatically or manually), when the partitions are enumerated, when the storage interface enumerates the attached devices, etc. If some storage device happens to be a partition - fine, it will reference the enclosing storage. If the storage device is a RAM disk, then it will keep pointer to a memory allocation. For a file-backed storage device (loop device), a reference to a file kernel object will be kept.
Similarly, the file system gets description of some kind of storage when it mounts, whose nature depends on your OS design and the file system (could be network path, could be local device file, could be even storage interface path.) Then, when mounting, this device description is converted to the appropriate kernel object pointer - storage device object for local file system, tcp connection object (like socket) for network file system, pointer to buffer in RAM for tmpfs like file system, etc.
Where are those references and parameters stored? Depends on your OS design choices - either private (i.e. driver-implementation specific) structure or public (i.e. driver-class standardized) structure. For things like tcp connections or RAM buffer pointers, a private field would be the norm, as those are rather specific and also generally uninteresting for most system facilities, but the driver itself. Note that you don't need to pass two pointers around - either structure embedding or pointer to private structure in the public structure will do the trick.
Also, you should use reference counting to guarantee proper lifetime management of your kernel objects. For example, even if the disk is disconnected, the kernel object must remain alive to provide the file system and partition drivers with interface that denies their requests and makes sure that they are informed of the disk's new status.