Could you read it before denouncing?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Crazed123

Re:Could you read it before denouncing?

Post by Crazed123 »

<...continued>

Once I've got this figured out, appropriate arguments and functions for each type (along with the partial mapping arguments mentioned earlier) will go into the next revision.

So will read-string and write-string port I/O functions. All those ring switches really do sound bad, despite being progammatically elegant (ie: what Scheme Would Do).

Again, I/O segmentation sounds like a great idea, especially along with the mechanism (to be invented) for telling drivers their hardware paramaters (IRQs, memory ranges, I/O ports, etc). However, I do want to here from other people (we've got 2 votes for now, none against) before adding it.

run() has been renamed switch_to_thread(), and I plan on adding "edi_" to the front of normal EDI routines. EDI class methods should have their names built into the routine names already.

How's fork_thread() as a name for spawn_thread()? I still think that fork() or vfork() implies too much about a process table the kernel may or may not be keeping.
POSIX pthreads aren't too bad, but they aren't as elegant/simple as possible. This is because they're designed to work on a variety of OSs, and therefore need to at least cope with different ways of handling scheduling, processes, threads, etc. For example, for Linux everything is a process (a thread is just a process that shares an address space with another process), while for some versions of *NIX threads are implemented in a user-level library and the kernel has nothing to do with them. The POSIX stuff also has a lot of things I think you can ignore (like thread specific data, signals, etc).
POSIX threads are already adapted for cross-platform use? Nifty and excellent. EDI will employ a subset of POSIX pthread functionality to perform its threading.

Unfortunately, what you said about your device manager really only helps with your kernel. Not that your kernel is insignificant or so bad or anything (DON'T take this personally), but what's really needed is some simple mechanism for the driver to identify the hardware it drives, and be given its hardware parameters.

I'm not sure about what it would be, but I think that some kind of data structure should be used to convey what hardware a driver drives. A set (or array, or whatever) of these could be declared const in the driver with a fixed name. When the driver is loaded (I think it's safe to assume drivers will be loaded in some object format that supports symbols, no?) the kernel can read this symbol, read the data structures there, and use this information to know about the drivers hardware.

Then, when the driver calls init_edi(), it can pass data structures asking for pointers to EDI objects representing its hardware resources. The kernel will pass back pre-initialized (already created and set up with hardware resources, unable to be initialized again) objects to the driver, and the driver will run with these resources. This gives the advantage of the driver not even knowing (or needing to know) its specific hardware parameters.

If the kernel doesn't know what resource to grant, but knows what type of resource, it can grant an uninitialized object that the driver can initialize to its own parameters.

This leaves the kernel with having to figure out hardware parameters for drivers, but there's either a way around that or we can solve that issue.

To your paragraph about message passing (omitted to save space): That's the reason I support this function-calling idea. See below.
I'm thinking that some form of "OS specific" wrapper is going to be unavoidable, and that EDI drivers could just export standard functions that are either linked directly to the kernel (for monolithic kernels) or linked to a generic wrapper or shared library/DLL (for micro-kernels). That way the EDI driver source code itself doesn't need to care whether "port_read_method()" is actually routed through twelve different processes before being done or if it's an inline function, and the kernel or the wrapper could use EDI driver functions like "readSectors(LBA_address, count, void *destination)" or "draw3Dtriangle(point1, point2, point3, texture, opacity)".
I'm thinking like you, that the EDI layer the driver sees may end up being a library rather than the kernel itself. A monolithic kernel with the ability to call functions in the driver directly wouldn't need it, but a microkernel could link an EDI-marshalling layer to the driver. This layer would marshall driver calls to EDI into proper messages, unmarshall kernel/outside-world calls on the driver into proper function calls, run silently (so the driver doesn't know it isn't talking to a function-calling kernel), and be written uniquely for each OS.

Oy is this message long,
Eli
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Could you read it before denouncing?

Post by Brendan »

Hi,
Crazed123 wrote:
For my OS device drivers are normal processes, which means the same 32 bit processes/driver binary could be run on a 64 bit version of the OS. In this case CR3 would be a 64 bit register (the driver would expect a 32 bit register) and the flags used for paging (PAE, PSE) features in CR0 and CR4 might be misleading.
Hence the "intreg" type. On a 64-bit system it would be compiled as an int64_t rather than an int. Is there a preprocessor symbol I can check the definition for to make the declaration clearer?
It can't be determined when the driver is compiled - in a 32 bit platform "intreg" would be 32 bit, so when a 32 bit binary is running under a 64 bit version of the OS some things don't fit anymore. Even if "intreg" is always 64 bit the driver won't know when the upper 32 bits are used. You could probably solve this by providing a way for the device driver to find out what mode the kernel is operating in at run time, but that seems too messy (given that I can't think of a situation where the driver would actually need to know the contents of any of the registers).

Of course now that I've said this, there is one area. For DMA, how does the device driver find the physical addresses, how does it know if they are 32 bit or 64 bit, and (for some OSs) what does it do if the addresses are 64 bit but the device itself isn't capable of transferring data to/from pages above 4 GB (or above 16 MB)?

For my OS, I don't allow anyone to get the physical addresses for general pages. Instead, device drivers must allocate memory using special "allocate page/s for DMA" kernel functions, which are the only way to get any physical addresses to use for DMA or bus mastering. This means that a device driver must use "bounce buffers" and can't transfer data directly into another process's address space (which is better for performance and something that most other OSs would do).
Crazed123 wrote:
I don't know much about other architectures, but the PCI standard (and PCI devices) are cross-platform. It shouldn't be too hard to make portable PCI devices. Some hardware is tricky though (legacy ISA stuff). I'm not sure how other architectures handle I/O port ranges though (e.g. the chipset could memory mapped them into a special memory area or something).

You could try to make EDI work with "similar enough" architectures (without worrying about the radically different architectures)...
Let's put it this way: I have no intention to accomodate IA32-only features such as segmentation, but processor-portability takes lesser priority than creating something people will want to code for on IA-32. Remember how Linux started life for the IA-32 only?
Sure, but you're doing design work, rather than implementing a design. For Linux they mostly just copied the portable design of UNIX, which would've made it much easier to port Linux to other architectures and would've avoided the need to completely redesign non-portable things when it's too late...
Crazed123 wrote:So we've basically got: cacheable memory, "writes-cause-flush" caching, totally uncacheable, and "write-combining" caching. Or is "write-through" a separate type from "writes-cause-flush"?
Forget what I wrote last time - it didn't make any sense :-[

For modern 80x86 there's 6 different caching methods (strong uncacheable, uncacheable, write combining, write through, write back and write protected). Normal RAM is usually write-back for best performance. For ROMs you can't use write through or write back - if write-through or write-back caching is used then writes to the ROM would be stored in the CPUs caches and you'd be able read back your changes as if it was RAM (if the changes are still in the cache).

For the PCI bus, AFAIK the prefetchable/non-prefetchable difference is used to control hardware - I'm not too sure of the specifics (I think it's something to do with combining multiple read requests into a single read request). My thinking is that if a memory area isn't prefetchable then it can't be cachable in the normal sense, and the OS could prevent device drivers from requesting a bad combination of "CPU caching" and "PCI memory type" (i.e. the kernel would return an error instead of allowing the region to be mapped with inappropriate caching).

[Continued]
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Could you read it before denouncing?

Post by Brendan »

[Continued]

For 80x86 PCI device ROMs are easy - the only sane caching methods are "strong uncacheable", "uncacheable" or "write protected", and the device driver shouldn't care which method is used. The "write protected" caching method is better for performance (but it's not supported by Pentium or older CPUs).

The "prefetchable" PCI memory type could use any of the caching methods that the CPU supports - it depends on the type of memory mapped area.

The non-prefetchable memory type could use "strong uncacheable", "uncacheable" or "write combining". The "write through", "write back" and "write-protected" caching methods wouldn't work, as the CPU won't read from the PCI bus if the data is already in the CPU's cache, which means that an intended side-effect could be skipped. For an example, imagine a device where reading from an "interrupt status register" tells the device that you've handled it's IRQ (the read must come from the bus, not the CPU's cache).
Crazed123 wrote:Unfortunately, what you said about your device manager really only helps with your kernel. Not that your kernel is insignificant or so bad or anything (DON'T take this personally), but what's really needed is some simple mechanism for the driver to identify the hardware it drives, and be given its hardware parameters.
I'd say you need 2 different methods - one for when the OS tells the driver which resources it uses, and the other for when the driver tells the OS. For the second method, the device driver would find out for itself (which could involve probing, etc), and you'd want to avoid this if the OS uses the first method.

I'm thinking of something like this:

Code: Select all

EDI_start(EDI_resourceList *resourceList) {
    int status;

    if( resourceList == NULL ) {
        status = doProbing();
        if( status == OK ) {
            status = tell_the_OS_which_resources();
        }
    } else {
        status = scan_resources(resourceList);
        if( status == OK ) {
           status = check_if_device_is_good();
        }
    }
    return status;
}
Crazed123 wrote:I'm not sure about what it would be, but I think that some kind of data structure should be used to convey what hardware a driver drives. A set (or array, or whatever) of these could be declared const in the driver with a fixed name. When the driver is loaded (I think it's safe to assume drivers will be loaded in some object format that supports symbols, no?) the kernel can read this symbol, read the data structures there, and use this information to know about the drivers hardware.
For me, this information comes from PCI configuration space (or some other form of auto-detection). The device driver would check that it was given access to everything it needs and abort with an error if it wasn't.

[Continued]
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Could you read it before denouncing?

Post by Brendan »

[Continued]
Crazed123 wrote:I'm thinking like you, that the EDI layer the driver sees may end up being a library rather than the kernel itself. A monolithic kernel with the ability to call functions in the driver directly wouldn't need it, but a microkernel could link an EDI-marshalling layer to the driver. This layer would marshall driver calls to EDI into proper messages, unmarshall kernel/outside-world calls on the driver into proper function calls, run silently (so the driver doesn't know it isn't talking to a function-calling kernel), and be written uniquely for each OS.
That's makes more sense to me - micro-kernel writers would only need one EDI layer that is linked to the "EDI source code" and re-used by all EDI drivers...

There's a few more things..

You might want a standard way to get text out of the driver (e.g. "fprintf(stderr, "Ooops, I think the device just exploded!\n");" but more generic). Something like "EDI_info_string(<something> *string)" perhaps, with some rules on what sort of string it is (ASCIIZ, UTF-8, wide characters, etc). The standard "int8 edi_string_t" type is good, but I'm not sure if 31 characters is long enough for this purpose. There should also be some warning about re-entrancy for this - for example, for some systems doing "EDI_info_string("Hello")" and then "EDI_info_string(" world.\n")" is a bad idea because the system log is shared and it could end up looking like "HelloAnother device driver put this string here. world.".

Also, you should consider power management - standard ways to put the device into sleep states and wake it up again...

Lastly, everything so far is generic. Sooner or later you'll need to create seperate specifications for specific device types - an "EDI storage device driver specification", an "EDI video driver specification", an "EDI USB controller driver specification", etc. How would you feel about writing a specification for a specific device? BTW even though you've already got an example video/console driver, I'd avoid video (too complicated for the first device type). I was thinking of keyboard, mouse, joystick or serial port...


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
paulbarker

Re:Could you read it before denouncing?

Post by paulbarker »

BTW even though you've already got an example video/console driver, I'd avoid video (too complicated for the first device type). I was thinking of keyboard, mouse, joystick or serial port...
Whenever I'm looking at example drivers, I think the best one to write would be a Bochs debugging port driver. You simply write one char at a time to port 0xe9, with no reading allowed.

This means that all the example code is to do with what you are trying to show (the interface) rather than implementation code for a particular device.
mystran

Re:Could you read it before denouncing?

Post by mystran »

paulbarker wrote: Whenever I'm looking at example drivers, I think the best one to write would be a Bochs debugging port driver. You simply write one char at a time to port 0xe9, with no reading allowed.
I don't think writing something like a serial port driver is much worse. It's better as example too, because you do initialization (to set parameters) and handle interrupts (if you want).

You should still be able write a serial port driver in about one screen of C code, but it has many basic things almost any driver will need:

- configurable IO address and IRQ
- initialization
- settings that someone might want to change on the fly
- interrupt driven input and output with limited device buffering
- duplex operation

It's not too complicated, while it still allows one to show important functionality. You can even have two serial ports using the same IRQ, if you want to try and get IRQ sharing to work.

But the best thing: you don't need almost any of the features, so it makes a great example for a minitutorial, because you can start with just non-configurable init, and polling output only driver. You can then show how to extend it with rest of the normal stuff.
Crazed123

Re:Could you read it before denouncing?

Post by Crazed123 »

mystran: I'll do the serial port driver.

Bran: I know that specs for driver types have to be written.

Yeah, some kind of edi_info_out() routine needs writing, and it can't use edi_string_t. edi_string_t was designed for passing data structures around without worrying about the allocation of the string memory.
For me, this information comes from PCI configuration space (or some other form of auto-detection). The device driver would check that it was given access to everything it needs and abort with an error if it wasn't.
Only teensy issue is drivers like the keyboard, mouse, serial port, etc. that don't get auto-detected. Though I'm sure they can probe, right?
It can't be determined when the driver is compiled - in a 32 bit platform "intreg" would be 32 bit, so when a 32 bit binary is running under a 64 bit version of the OS some things don't fit anymore. Even if "intreg" is always 64 bit the driver won't know when the upper 32 bits are used. You could probably solve this by providing a way for the device driver to find out what mode the kernel is operating in at run time, but that seems too messy (given that I can't think of a situation where the driver would actually need to know the contents of any of the registers).
OK, so intreg and edi_get_register() are out, possibly to come back as extensions. I especially agree that making the driver find out what mode the kernel's running in at runtime, thus having to redefine int_reg at runtime, is far too messy.
Of course now that I've said this, there is one area. For DMA, how does the device driver find the physical addresses, how does it know if they are 32 bit or 64 bit, and (for some OSs) what does it do if the addresses are 64 bit but the device itself isn't capable of transferring data to/from pages above 4 GB (or above 16 MB)?

For my OS, I don't allow anyone to get the physical addresses for general pages. Instead, device drivers must allocate memory using special "allocate page/s for DMA" kernel functions, which are the only way to get any physical addresses to use for DMA or bus mastering. This means that a device driver must use "bounce buffers" and can't transfer data directly into another process's address space (which is better for performance and something that most other OSs would do).
I do believe I designed EDI-STREAM-DMA for this very purpose. A driver writes to its DMA stream, and the EDI-layer sends the data to the DMA hardware *safely*.
Sure, but you're doing design work, rather than implementing a design. For Linux they mostly just copied the portable design of UNIX, which would've made it much easier to port Linux to other architectures and would've avoided the need to completely redesign non-portable things when it's too late...
With intreg gone, what's so unportable? Well, I/O ports yes, but drivers could put #ifdefs for architecture around those and compile memory mappings (the usual alternative) on a platform that doesn't support them.

I'm just saying about the portability: I'd rather have a clean stadard for IA-32 (and possibly/probably IA-64 and AMD-64) only than have a PITA like UDI that runs everywhere. Once you get to that kind of bloat, it's better just to do something crazy-cool (crazy-cool, adj: Crazy, but cool. Like your awesome schizophrenic friend.) like write a special interpreted language for device drivers. Portability is a factor, but not where it might impede the thing actually working.

That means at this point that portability is a priority.

I have to go now, but everything else I pretty much agree with. I'll be designing the resource discovery and probing stuff.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Could you read it before denouncing?

Post by Brendan »

Hi,
paulbarker wrote:
BTW even though you've already got an example video/console driver, I'd avoid video (too complicated for the first device type). I was thinking of keyboard, mouse, joystick or serial port...
Whenever I'm looking at example drivers, I think the best one to write would be a Bochs debugging port driver. You simply write one char at a time to port 0xe9, with no reading allowed.
I'm not sure if Bochs port 0xE9 counts as a device driver - normally it's only used for debugging where it's often easier to do it inline.

Also, I'm talking of a specification rather than example code. For a mouse it might go (very roughly) like this:

[tt]EDI Mouse Driver Specification

Foreword: This specification is part of a series of specifications and should be read in conjunction with the "EDI Common Requirements Specification".


Function: ED_status sendMousePacket(s32 x, s32 y, s32 x_wheel, s32 y_wheel, u16 buttons, u16 buttonChange)

This required function is used by the device driver to send mouse information to the OS. The values for x, y, x_wheel and y_wheel represent the amount of virtual movement in any direction. These values are "normalized" such that they are always within the range of 0 to 0xFFFFFFFF, where 0 corresponds to no movement and (positive or negative) 0x7FFFFFFF corresponds to enough movement to move the mouse pointer from one edge of the screeen to the other. If the mouse doesn't have any scroll wheels, then x_wheel and y_wheel must always be set to zero.

The buttons value contains a flag corresponding to the state of each possible button (up to 16 buttons) where a 0 reprents "not pressed" and 1 represents "pressed". The buttonChange value also contains 16 flags one (for each possible button), where a 1 indicates that the state of the corresponding button has changed since the last mouse packet was sent. The bit positions for each possible button are detailed in Appendix A for common mouse types. For unsupported buttons a 0 should always be returned for the corresponding bit position in both the buttons and buttonChange variables.


Function: ED_status getMouseFeatures(bool x_wheel_present, bool y_wheel_present, u8 totalButtons)

This required function is used by the OS to find out which features the mouse supports. It is intended for use by a generic "mouse configuration" utility.


Function: ED_status setMouseConfiguration(mouse_configuration *configuration)

This required function is used by the OS to set the acceleration curve of the mouse for each direction of movement. The format for the "mouse_configuration" structure is detailed in Appendix A. This function is intended for use by a generic "mouse configuration" utility.


Function: ED_status getMouseStatistics(mouse_statistics *statistics)

This optional function is used by the OS to obtain mouse statistics. The format for the "mouse_statistics" structure is detailed in Appendix A (but includes how many times the mouse has been moved, how many times each button was pressed, the total distance the mouse has travelled, etc). The mouse driver is required to save these statistics when it is shut down (either creating a new file or replacing a pre-existing file) and load these statistics when it starts up, so that the statistics are persistant (i.e. so that the statistics measure everything since the mouse was installed rather than everything since boot).

This function is intended for utilities that predict hardware failures (similar to the SMART system used in hard drives) - for example, every time the mouse travels 5000 meters an email might be sent to maintenance saying that the mouse is due for service, and if the mouse has travelled 1000000 meters or a button has been pressed more than 100000 times an email might be sent to maintenance saying the mouse is due for replacement. This function may also be used for studying user interface design efficiency.[/tt]

Eventually, EDI will need one of these for each device type...


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:Could you read it before denouncing?

Post by Brendan »

Hi,
Crazed123 wrote:
For me, this information comes from PCI configuration space (or some other form of auto-detection). The device driver would check that it was given access to everything it needs and abort with an error if it wasn't.
Only teensy issue is drivers like the keyboard, mouse, serial port, etc. that don't get auto-detected. Though I'm sure they can probe, right?
For me, everything is auto-detected. For PCI and USB this is easy. For ISA/legacy devices there's the "Plug & Play BIOS Specification", several hardware level Plug & Play specifications, ACPI, etc. If all of these methods fail then the Device Manager will probe for common devices (PS/2, serial, parallel, floppy). If the device still isn't found the information will need to come from a file that describes legacy devices and their resources - the system administrator would need to create this file (or download it from somewhere and check it for security violations, etc).

In general, a device driver is never started unless the OS knows that it's device is definately present, and a device driver is never given access to resources (I/O ports, etc) that it doesn't need. Basically, device drivers are untrusted like normal applications (and the device manager is trusted). This means people can (hopefully) download device drivers from anywhere they like without worrying about viruses, trojans, etc (the OS is meant to protect against these things).

For one example of the problems I'm trying to prevent, see: http://www.securityfocus.com/columnists/402
Crazed123 wrote:
For my OS, I don't allow anyone to get the physical addresses for general pages. Instead, device drivers must allocate memory using special "allocate page/s for DMA" kernel functions, which are the only way to get any physical addresses to use for DMA or bus mastering. This means that a device driver must use "bounce buffers" and can't transfer data directly into another process's address space (which is better for performance and something that most other OSs would do).
I do believe I designed EDI-STREAM-DMA for this very purpose. A driver writes to its DMA stream, and the EDI-layer sends the data to the DMA hardware *safely*.
EDI-STREAM-DMA looks like it was designed for ISA DMA controllers only.

For PCI bus mastering, monolithic OSs usually transfer data directly to/from application's address spaces to avoid copying data around (when possible). For example, an application might say "send 1234 bytes from the address 0x7654321" and the device driver would convert the address into physical address/es and tell the PCI device to transfer the data directly.

For my OS (where hardware isn't allowed to transfer data directly to/from other process's address spaces), your "init_dma_stream()" still isn't suitable for PCI bus mastering. Instead you'd need something like:

int32 alloc_DMA_buffer(void **physicalAddressOfPages, void *linearAddressForPages, unsigned int32 buffer_pages, unsigned int32 flags, unsigned int32 type);

Where "flags" is used to determine if pages must be below 16 MB or below 4 GB, and "type" is used to determine how the pages should be mapped (the cache control flags in page table entries). Please note that the "type" here has nothing to do with memory mapped I/O (it's for performance only). For e.g. a buffer that is only ever read once by the device driver (after it was written to by the device/hardware) could be set to "caching disabled" to improve the efficiency of the CPU's caches.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Crazed123

Re:Could you read it before denouncing?

Post by Crazed123 »

Processing everything rather slowly today, due to lack of sleep and time spent with father. Here's something, though:

Code: Select all

typedef struct {
 edi_string_t name,device_class,bus;
 int16_t vendor_id,device_id;
 int32_t field_validity_flags;
} edi_driver_identifier_t;
One of those gets declared for each driver, and its value is read when the driver is loaded. Also:

Code: Select all

int32_t driver_init(int32_t num_resources,edi_object_metadata_t *resources);
Will allow the kernel/EDI-layer to pass initialized hardware resource objects to the driver. An argument will be added to init_edi() denoting whether or not these are acceptable by the driver. I think, if they are not, that the driver will simply ask for and create objects as it previously did, with the kernel checking each initialization and creation call for security purposes if it wishes to.

OK, time to go spend more time with the family.
Eli
Post Reply