From 7a37e94da34a830b8ddb02febc8f27d53f001e82 Mon Sep 17 00:00:00 2001 From: Mathieu Maret Date: Fri, 13 Jul 2018 17:13:10 +0200 Subject: [PATCH] Initial import --- INSTALL | 156 ++ LICENSE | 340 ++++ Makefile | 100 + README | 89 + VERSION | 14 + bootstrap/multiboot.S | 85 + bootstrap/multiboot.h | 135 ++ drivers/bochs.c | 123 ++ drivers/bochs.h | 55 + drivers/console.c | 42 + drivers/console.h | 37 + drivers/devices.h | 63 + drivers/fs_fat.c | 3212 +++++++++++++++++++++++++++++++++ drivers/fs_fat.h | 36 + drivers/fs_virtfs.c | 1054 +++++++++++ drivers/fs_virtfs.h | 40 + drivers/ide.c | 785 ++++++++ drivers/ide.h | 32 + drivers/kbd.c | 118 ++ drivers/kbd.h | 37 + drivers/kbdmapfr.c | 282 +++ drivers/mem.c | 489 +++++ drivers/mem.h | 36 + drivers/part.c | 216 +++ drivers/part.h | 34 + drivers/serial.c | 292 +++ drivers/serial.h | 40 + drivers/tty.c | 318 ++++ drivers/tty.h | 39 + drivers/x86_videomem.c | 270 +++ drivers/x86_videomem.h | 108 ++ drivers/zero.c | 452 +++++ drivers/zero.h | 46 + extra/Makefile | 46 + extra/README | 85 + extra/bootsect.S | 438 +++++ extra/dot.mkvars | 31 + extra/grub.img.gz | Bin 0 -> 67535 bytes extra/hd20M.img.gz | Bin 0 -> 21877 bytes extra/mtoolsrc | 2 + extra/patch-qemu-port-e9.diff | 212 +++ extra/patch-qemu-pty.diff | 42 + extra/sos_bsect.lds | 64 + extra/termslave.c | 130 ++ hwcore/bitmap.c | 177 ++ hwcore/bitmap.h | 79 + hwcore/cpu_context.c | 1008 +++++++++++ hwcore/cpu_context.h | 433 +++++ hwcore/cpu_context_switch.S | 159 ++ hwcore/exception.c | 183 ++ hwcore/exception.h | 83 + hwcore/exception_wrappers.S | 271 +++ hwcore/gdt.c | 203 +++ hwcore/gdt.h | 52 + hwcore/i8254.c | 79 + hwcore/i8254.h | 35 + hwcore/i8259.c | 79 + hwcore/i8259.h | 40 + hwcore/idt.c | 159 ++ hwcore/idt.h | 84 + hwcore/ioports.h | 84 + hwcore/irq.c | 100 + hwcore/irq.h | 97 + hwcore/irq_wrappers.S | 305 ++++ hwcore/mm_context.c | 321 ++++ hwcore/mm_context.h | 113 ++ hwcore/paging.c | 1056 +++++++++++ hwcore/paging.h | 252 +++ hwcore/segment.h | 73 + hwcore/swintr.c | 50 + hwcore/swintr.h | 43 + hwcore/swintr_wrappers.S | 116 ++ sos/assert.c | 47 + sos/assert.h | 45 + sos/binfmt_elf32.c | 462 +++++ sos/binfmt_elf32.h | 42 + sos/blkcache.c | 500 +++++ sos/blkcache.h | 97 + sos/blkdev.c | 1197 ++++++++++++ sos/blkdev.h | 225 +++ sos/calcload.c | 396 ++++ sos/calcload.h | 117 ++ sos/chardev.c | 463 +++++ sos/chardev.h | 155 ++ sos/errno.h | 58 + sos/fs.c | 2263 +++++++++++++++++++++++ sos/fs.h | 1140 ++++++++++++ sos/fs_nscache.c | 527 ++++++ sos/fs_nscache.h | 284 +++ sos/fs_pagecache.c | 642 +++++++ sos/fs_pagecache.h | 214 +++ sos/hash.c | 271 +++ sos/hash.h | 153 ++ sos/klibc.c | 398 ++++ sos/klibc.h | 111 ++ sos/kmalloc.c | 114 ++ sos/kmalloc.h | 63 + sos/kmem_slab.c | 814 +++++++++ sos/kmem_slab.h | 206 +++ sos/kmem_vmm.c | 607 +++++++ sos/kmem_vmm.h | 113 ++ sos/ksynch.c | 256 +++ sos/ksynch.h | 178 ++ sos/kwaitq.c | 321 ++++ sos/kwaitq.h | 216 +++ sos/list.h | 194 ++ sos/macros.h | 47 + sos/main.c | 640 +++++++ sos/mouse_sim.c | 804 +++++++++ sos/physmem.c | 387 ++++ sos/physmem.h | 205 +++ sos/process.c | 597 ++++++ sos/process.h | 238 +++ sos/sched.c | 338 ++++ sos/sched.h | 158 ++ sos/syscall.c | 1482 +++++++++++++++ sos/syscall.h | 121 ++ sos/thread.c | 899 +++++++++ sos/thread.h | 427 +++++ sos/time.c | 355 ++++ sos/time.h | 222 +++ sos/types.h | 72 + sos/uaccess.c | 413 +++++ sos/uaccess.h | 173 ++ sos/umem_vmm.c | 1757 ++++++++++++++++++ sos/umem_vmm.h | 616 +++++++ support/build_image.sh | 220 +++ support/sos.lds | 113 ++ userland/Makefile | 100 + userland/banner.c | 150 ++ userland/blktest.c | 208 +++ userland/chartest.c | 248 +++ userland/crt.c | 663 +++++++ userland/crt.h | 233 +++ userland/crt_asm.S | 36 + userland/debug.c | 39 + userland/debug.h | 34 + userland/envtest.c | 16 + userland/fstest.c | 847 +++++++++ userland/fstest_utils.c | 149 ++ userland/fstest_utils.h | 41 + userland/fstestfat.c | 768 ++++++++ userland/init.c | 100 + userland/ldscript.lds | 67 + userland/libc.c | 808 +++++++++ userland/libc.h | 287 +++ userland/myprog1.c | 37 + userland/myprog10.c | 82 + userland/myprog11.c | 109 ++ userland/myprog12.c | 104 ++ userland/myprog13.c | 87 + userland/myprog14.c | 70 + userland/myprog2.c | 35 + userland/myprog3.c | 46 + userland/myprog4.c | 46 + userland/myprog5.c | 36 + userland/myprog6.c | 39 + userland/myprog7.c | 87 + userland/myprog8.c | 66 + userland/myprog9.c | 82 + userland/shell.c | 1001 ++++++++++ userland/stdarg.c | 244 +++ userland/stdarg.h | 50 + userland/string.c | 239 +++ userland/string.h | 77 + userland/types.h | 49 + 166 files changed, 46512 insertions(+) create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 VERSION create mode 100644 bootstrap/multiboot.S create mode 100644 bootstrap/multiboot.h create mode 100644 drivers/bochs.c create mode 100644 drivers/bochs.h create mode 100644 drivers/console.c create mode 100644 drivers/console.h create mode 100644 drivers/devices.h create mode 100644 drivers/fs_fat.c create mode 100644 drivers/fs_fat.h create mode 100644 drivers/fs_virtfs.c create mode 100644 drivers/fs_virtfs.h create mode 100644 drivers/ide.c create mode 100644 drivers/ide.h create mode 100644 drivers/kbd.c create mode 100644 drivers/kbd.h create mode 100644 drivers/kbdmapfr.c create mode 100644 drivers/mem.c create mode 100644 drivers/mem.h create mode 100644 drivers/part.c create mode 100644 drivers/part.h create mode 100644 drivers/serial.c create mode 100644 drivers/serial.h create mode 100644 drivers/tty.c create mode 100644 drivers/tty.h create mode 100644 drivers/x86_videomem.c create mode 100644 drivers/x86_videomem.h create mode 100644 drivers/zero.c create mode 100644 drivers/zero.h create mode 100644 extra/Makefile create mode 100644 extra/README create mode 100644 extra/bootsect.S create mode 100644 extra/dot.mkvars create mode 100644 extra/grub.img.gz create mode 100644 extra/hd20M.img.gz create mode 100644 extra/mtoolsrc create mode 100644 extra/patch-qemu-port-e9.diff create mode 100644 extra/patch-qemu-pty.diff create mode 100644 extra/sos_bsect.lds create mode 100644 extra/termslave.c create mode 100644 hwcore/bitmap.c create mode 100644 hwcore/bitmap.h create mode 100644 hwcore/cpu_context.c create mode 100644 hwcore/cpu_context.h create mode 100644 hwcore/cpu_context_switch.S create mode 100644 hwcore/exception.c create mode 100644 hwcore/exception.h create mode 100644 hwcore/exception_wrappers.S create mode 100644 hwcore/gdt.c create mode 100644 hwcore/gdt.h create mode 100644 hwcore/i8254.c create mode 100644 hwcore/i8254.h create mode 100644 hwcore/i8259.c create mode 100644 hwcore/i8259.h create mode 100644 hwcore/idt.c create mode 100644 hwcore/idt.h create mode 100644 hwcore/ioports.h create mode 100644 hwcore/irq.c create mode 100644 hwcore/irq.h create mode 100644 hwcore/irq_wrappers.S create mode 100644 hwcore/mm_context.c create mode 100644 hwcore/mm_context.h create mode 100644 hwcore/paging.c create mode 100644 hwcore/paging.h create mode 100644 hwcore/segment.h create mode 100644 hwcore/swintr.c create mode 100644 hwcore/swintr.h create mode 100644 hwcore/swintr_wrappers.S create mode 100644 sos/assert.c create mode 100644 sos/assert.h create mode 100644 sos/binfmt_elf32.c create mode 100644 sos/binfmt_elf32.h create mode 100644 sos/blkcache.c create mode 100644 sos/blkcache.h create mode 100644 sos/blkdev.c create mode 100644 sos/blkdev.h create mode 100644 sos/calcload.c create mode 100644 sos/calcload.h create mode 100644 sos/chardev.c create mode 100644 sos/chardev.h create mode 100644 sos/errno.h create mode 100644 sos/fs.c create mode 100644 sos/fs.h create mode 100644 sos/fs_nscache.c create mode 100644 sos/fs_nscache.h create mode 100644 sos/fs_pagecache.c create mode 100644 sos/fs_pagecache.h create mode 100644 sos/hash.c create mode 100644 sos/hash.h create mode 100644 sos/klibc.c create mode 100644 sos/klibc.h create mode 100644 sos/kmalloc.c create mode 100644 sos/kmalloc.h create mode 100644 sos/kmem_slab.c create mode 100644 sos/kmem_slab.h create mode 100644 sos/kmem_vmm.c create mode 100644 sos/kmem_vmm.h create mode 100644 sos/ksynch.c create mode 100644 sos/ksynch.h create mode 100644 sos/kwaitq.c create mode 100644 sos/kwaitq.h create mode 100644 sos/list.h create mode 100644 sos/macros.h create mode 100644 sos/main.c create mode 100644 sos/mouse_sim.c create mode 100644 sos/physmem.c create mode 100644 sos/physmem.h create mode 100644 sos/process.c create mode 100644 sos/process.h create mode 100644 sos/sched.c create mode 100644 sos/sched.h create mode 100644 sos/syscall.c create mode 100644 sos/syscall.h create mode 100644 sos/thread.c create mode 100644 sos/thread.h create mode 100644 sos/time.c create mode 100644 sos/time.h create mode 100644 sos/types.h create mode 100644 sos/uaccess.c create mode 100644 sos/uaccess.h create mode 100644 sos/umem_vmm.c create mode 100644 sos/umem_vmm.h create mode 100755 support/build_image.sh create mode 100644 support/sos.lds create mode 100644 userland/Makefile create mode 100644 userland/banner.c create mode 100644 userland/blktest.c create mode 100644 userland/chartest.c create mode 100644 userland/crt.c create mode 100644 userland/crt.h create mode 100644 userland/crt_asm.S create mode 100644 userland/debug.c create mode 100644 userland/debug.h create mode 100644 userland/envtest.c create mode 100644 userland/fstest.c create mode 100644 userland/fstest_utils.c create mode 100644 userland/fstest_utils.h create mode 100644 userland/fstestfat.c create mode 100644 userland/init.c create mode 100644 userland/ldscript.lds create mode 100644 userland/libc.c create mode 100644 userland/libc.h create mode 100644 userland/myprog1.c create mode 100644 userland/myprog10.c create mode 100644 userland/myprog11.c create mode 100644 userland/myprog12.c create mode 100644 userland/myprog13.c create mode 100644 userland/myprog14.c create mode 100644 userland/myprog2.c create mode 100644 userland/myprog3.c create mode 100644 userland/myprog4.c create mode 100644 userland/myprog5.c create mode 100644 userland/myprog6.c create mode 100644 userland/myprog7.c create mode 100644 userland/myprog8.c create mode 100644 userland/myprog9.c create mode 100644 userland/shell.c create mode 100644 userland/stdarg.c create mode 100644 userland/stdarg.h create mode 100644 userland/string.c create mode 100644 userland/string.h create mode 100644 userland/types.h diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..b74810b --- /dev/null +++ b/INSTALL @@ -0,0 +1,156 @@ + + SOS: A Simple Operating System + + Compilation/Installation/Test instructions + + +Compilation +=========== + +IMPORTANT +--------- + +Don't forget to run 'make clean' before 'make' after you have modified +any source or header file(s). + + +On a x86 host where grub is correctly installed +----------------------------------------------- + +Simply run 'make' + + +On a non-x86 host (without grub of course !) +-------------------------------------------- + +See extra/README + + +On an x86 host without Grub, or with a buggy Grub +------------------------------------------------- + +See extra/README + +How do I know I have a buggy grub installation ? Answer: in the qemu +PC emulator, Grub hangs while loading the kernel + + +Installation +============ + +Nothing special to do besides compiling + + +Test the SOS Kernel +=================== + +On a x86 real machine with Grub installed +----------------------------------------- + + 1st method + => Boot the sos.elf file (append 'kernel=sos.elf' in the + menu.lst or type it on Grub's command line) from a hard disk, a + floppy, or from the network + + 2nd method + => Copy the file 'fd.img' to a floppy and boot from it + + +On a x86 real machine without Grub installed +-------------------------------------------- + + 1st method + => see extra/README to compile with the grub floppy image we provide, + copy the file 'fd.img' to a floppy, and boot from it + + 2nd method + => see extra/README to compile with the boot sector we provide, + copy the file 'extra/sos_bsect.img' to a floppy, and boot from + it + + +Inside a PC emulator (x86 and non-x86 hosts) +-------------------------------------------- + +Tested on both the bochs emulator (x86/linux, sparc/solaris and +ppc/linux hosts, 'apt-get install bochs-x vgabios' on debian +testing/unstable), and the qemu system emulator (with libsdl +installed: 'apt-get install libsdl1.2-dev' on debian +testing/unstable). + + + 1/ Grub is installed on the host (x86 hosts only) + - - - - - - - - - - - - - - - - - - - - - - - - - + + bochs: boot from the file 'fd.img'. Example of a ~/.bochsrc: + floppya: 1_44=/home/d2/sos/fd.img, status=inserted + romimage: file=/usr/share/bochs/BIOS-bochs-latest, address=0xf0000 + vgaromimage: /usr/share/vgabios/vgabios.bin + megs:63 # 63 Mo de RAM + + qemu: run 'qemu -fda fd.img' + If grub hangs while loading the kernel, please go to method 2/ + + + 2/ Grub is not installed (all hosts) + - - - - - - - - - - - - - - - - - - + + See extra/README to generate a floppy image with the Grub floppy + image we provide, and: + + bochs: boot from the file 'fd.img' + + qemu: run 'qemu -fda fd.img' + + + 3/ Bonus: boot with the bootsector we provide (all hosts) + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + See extra/README to generate a floppy image with the boot sector we + provide, and: + + bochs: boot from the file 'extra/sos_bsect.img' + + qemu: run 'qemu -fda extra/sos_qemu.img' + + NOTE: This technique assumes that INT 15H is supported by the + machine's BIOS. This should be OK for the vast majority of targets + (bochs, qemu, recent machines), but we do not guarantee it. In case + of doubt, please use Grub. + + +NOTE : recommended versions of the tools +---------------------------------------- + +Release development platform: + + - OS : Linux 2.6.14.3 x86_64 + - gcc : i586-pc-elf-gcc-4.0.2 (GCC) 4.0.2 + - GNU binutils : GNU ld version 2.16 + - GNU make : GNU Make 3.80 + +Usual development platforms known to work: + + + amd64 (x86_64)/debian sarge: + - OS : Linux 2.6.14.3 x86_64 + - gcc : i586-pc-elf-gcc-4.0.2 (GCC) 4.0.2 + - GNU binutils : GNU ld version 2.16 + + + x86/debian sarge: + - OS : Linux 2.6.11.7-d2-1 i686 + - gcc : gcc (GCC) 3.3.6 (Debian 1:3.3.6-5) + - GNU binutils : GNU ld version 2.15 + + + ppc/debian sarge: + - OS : Linux 2.6.10-powerpc ppc + - gcc : gcc (GCC) 3.2.2 + - GNU binutils : GNU ld version 2.13.2 + + + x86/windows with cygwin (http://sos.enix.org/SOSFaq#TOC_0_2_1): + - OS : MS Windows XP Pro SP2 / Cygwin + - gcc : gcc 3.4.4 + - GNU binutils : GNU ld version 2.16 + - GNU make : GNU Make 3.80 + +-- +David Decotigny diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..60549be --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0826fc3 --- /dev/null +++ b/Makefile @@ -0,0 +1,100 @@ +## Copyright (C) 2004,2005 The SOS Team +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation; either version 2 +## of the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +## USA. + +CC=gcc +LD=ld +CP=cp +STRIP=strip +CFLAGS = -Wall -nostdinc -ffreestanding -DKERNEL_SOS -g +LIBGCC = $(shell $(CC) -print-libgcc-file-name) # To benefit from FP/64bits artihm. +LDFLAGS = --warn-common -nostdlib +OBJECTS = bootstrap/multiboot.o \ + hwcore/idt.o hwcore/gdt.o \ + hwcore/swintr.o hwcore/swintr_wrappers.o \ + hwcore/exception.o hwcore/exception_wrappers.o \ + hwcore/irq.o hwcore/irq_wrappers.o hwcore/i8259.o \ + hwcore/paging.o hwcore/i8254.o \ + hwcore/cpu_context.o hwcore/cpu_context_switch.o \ + hwcore/mm_context.o hwcore/bitmap.o \ + sos/kmem_vmm.o sos/kmem_slab.o sos/kmalloc.o \ + sos/physmem.o sos/klibc.o \ + sos/thread.o sos/kwaitq.o \ + sos/time.o sos/sched.o sos/ksynch.o \ + sos/process.o sos/syscall.o \ + sos/assert.o sos/main.o sos/mouse_sim.o \ + sos/uaccess.o sos/calcload.o \ + sos/umem_vmm.o sos/binfmt_elf32.o \ + drivers/x86_videomem.o drivers/bochs.o \ + drivers/zero.o drivers/mem.o \ + drivers/ide.o drivers/kbd.o drivers/kbdmapfr.o \ + drivers/serial.o drivers/tty.o drivers/part.o \ + drivers/console.o \ + sos/hash.o sos/fs.o sos/fs_nscache.o \ + drivers/fs_virtfs.o drivers/fs_fat.o \ + sos/chardev.o sos/blkdev.o \ + sos/blkcache.o sos/fs_pagecache.o \ + userland/userprogs.kimg + +KERNEL_OBJ = sos.elf +KERNEL_LOAD = sos.gz +MULTIBOOT_IMAGE = fd.img +PWD := $(shell pwd | sed 's/"/\\\"/g;s/\$$/\\\$$/g') + +# Main target +all: $(MULTIBOOT_IMAGE) + +$(MULTIBOOT_IMAGE): $(KERNEL_LOAD) + ./support/build_image.sh $@ $< + +$(KERNEL_LOAD): $(KERNEL_OBJ) + $(CP) $< $<.strip && $(STRIP) -sx -R .comment $<.strip + gzip < $<.strip > $@ + +$(KERNEL_OBJ): $(OBJECTS) ./support/sos.lds + $(LD) $(LDFLAGS) -T ./support/sos.lds -o $@ $(OBJECTS) $(LIBGCC) + -nm -C $@ | cut -d ' ' -f 1,3 > sos.map + size $@ + +-include .mkvars + +# Create the userland programs to include in the kernel image +userland/userprogs.kimg: FORCE + $(MAKE) -C userland + +# Create objects from C source code +%.o: %.c + $(CC) "-I$(PWD)" -c "$<" $(CFLAGS) -o "$@" + +# Create objects from assembler (.S) source code +%.o: %.S + $(CC) "-I$(PWD)" -c "$<" $(CFLAGS) -DASM_SOURCE=1 -o "$@" + +FORCE: + @ + +# Clean directory +clean: + $(RM) fd*.img *.o mtoolsrc *~ menu.txt *.elf *.bin *.strip *.map + $(RM) *.log *.out bochs* sos.gz + $(RM) bootstrap/*.o bootstrap/*~ + $(RM) drivers/*.o drivers/*~ + $(RM) hwcore/*.o hwcore/*~ + $(RM) sos/*.o sos/*~ + $(RM) support/*~ + $(RM) extra/*~ + $(MAKE) -C extra clean + $(MAKE) -C userland clean diff --git a/README b/README new file mode 100644 index 0000000..e480048 --- /dev/null +++ b/README @@ -0,0 +1,89 @@ + + SOS: A Simple Operating System + + +This is SOS, a Simple Operating System for i386-family +processors. This is as simple as possible to show a way to program a +basic Operating System on real common hardware (PC). The code should +be easily readable and understandable thanks to frequent comments, and +references to external documentation. We chose to implement the basic +features of an OS, thus making design decisions targetting towards +simplicity of understanding, covering most of the OS classical +concepts, but not aiming at proposing yet another full-fledged +competitive OS (Linux is quite good at it). However, for those who +would like to propose some enhancements, we are open to any code +suggestions (patches only, please). And yes, there might be bugs in +the code, so please send us any bug report, and/or patches ! + +The OS comes as a set of articles (in french) to be published in the +journal "Linux Magazine France". Each month, the part of the code +related to the current article's theme is released (see VERSION file), +and the resulting OS can be successfully compiled and run, by booting +it from a floppy on a real machine (tested AMD k7, Cyrix and Intel P4 +pentiums), or through an x86 emulator (bochs or qemu). The resulting +OS is available as a multiboot compliant ELF kernel (sos.elf) and as a +floppy image (fd.img). It provides a very very very basic demo whose +aim is to understand how everything works, not to animate sprites on +the screen with 5:1 dolby sound. + +The initial technical features and lack-of-features of the OS are: + - monolithic kernel, fully interruptible, non-preemptible (big kernel + lock), target machines = i386 PC or better + - compiles on any host where the gcc/binutils toolchain (target + i586-gnu) is available. Can be tested on real i486/pentium + hardware, or on any host that can run an i486/pentium PC emulator + (bochs or qemu) + - kernel loaded by grub or by a sample bootsector + - clear separation of physical memory and virtual memory concepts, + even inside the kernel: no identity-mapping of the physical memory + inside the kernel (allows to move virtual mappings of kernel pages + at run-time, eg to free ISA DMA pages, and to avercome the 4G RAM + barrier) + - slab-type kernel memory allocation + - no swap, no reverse mapping + - VERY simple drivers: keyboard, x86 video memory, IDE disks + - logical devices: partitions, FAT/ext2 filesystem, Unix-style + mountpoints, hard/soft links + - basic network stack (ARP/IP/UDP) + - user-level features: ELF loader (no shared libraries), processes, + user threads (kernel-level scheduling only), mmap API, basic VFS + +To understand where to look at for what, here is a brief description: + - Makefile: the (ONLY) makefile of the OS. Targets are basically + 'all' and 'clean' + - bootstrap/ directory: code to load the kernel. Both the stuff + needed for a multiboot-compliant loader (eg grub) AND a bootsector + are provided. + - sos/ directory: the entry routine for the kernel (main.c), various + systemwide header files, a set of common useful C routines + ("nano-klibc"), and kernel subsystems (kernel memory management, + etc...) + - hwcore/ directory: Low-level CPU- and kernel-related routines + (interrupt/exception management, translation tables and segment + registers, ...) + - drivers/ directory: basic kernel drivers for various (non CPU) + devices (keyboard, x86 video memory, bochs 0xe9 port, ...). Used + mainly for debugging + - support/ directory: scripts and configuration files to build the + floppy images + - extra/ directory: a set of configuration files to be customized for + non-x86 host installations (yes, we primarily develop SOS on a ppc, for + the x86 target of course), or for grub-less installations. See + README file in this directory. + +The code is licensed under the terms of the GNU GPL version 2 (see +LICENSE file). + +Enjoy ! + + David Decotigny, Thomas Petazzoni, the Kos team + http://sos.enix.org/ + http://david.decotigny.free.fr/ + http://kos.enix.org/~thomas/ + http://kos.enix.org/ + + +-- +David Decotigny + +PS: Made with a Mac. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6a83b5c --- /dev/null +++ b/VERSION @@ -0,0 +1,14 @@ +SOS -- Simple OS +Copyright (C) 2003,2004,2005 The SOS Team (David Decotigny & Thomas Petazzoni) + +Version "Article 9 (2nd part)" -- User interactions with the devices drivers + through the VFS: block devices, block cache, + page cache and IDE driver with PC-style + partitions support + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + See the LICENSE file included in the distribution. diff --git a/bootstrap/multiboot.S b/bootstrap/multiboot.S new file mode 100644 index 0000000..546d947 --- /dev/null +++ b/bootstrap/multiboot.S @@ -0,0 +1,85 @@ +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + + +/* The operating system is booted by Grub, so we almost have nothing + to do to boot it. We only have to conform to the Multiboot + standard, as defined by the Grub documentation */ + +#define ASM 1 +/* The multiboot.h header contains a lot of multiboot standard + definitions */ +#include "multiboot.h" + + /* The multiboot header itself. It must come first. */ +.section ".multiboot" + /* Multiboot header must be aligned on a 4-byte boundary */ + .align 4 +multiboot_header: + /* magic= */ .long MULTIBOOT_HEADER_MAGIC + /* flags= */ .long MULTIBOOT_HEADER_FLAGS + /* checksum= */ .long -(MULTIBOOT_HEADER_MAGIC \ + +MULTIBOOT_HEADER_FLAGS) + /* header_addr= */ .long multiboot_header + /* load_addr= */ .long __b_load + /* load_end_addr=*/ .long __e_load + /* bss_end_addr= */ .long __e_kernel + /* entry_addr= */ .long multiboot_entry + +/* Here is the beginning of the code of our operating system */ +.text + +.globl start, _start +start: +_start: +multiboot_entry: + /* Set up a stack */ + movl $(stack + MULTIBOOT_STACK_SIZE), %ebp + movl %ebp, %esp + + /* Set EFLAGS to 0 */ + pushl $0 + /* pop stack into the EFLAGS register */ + popf + + /* Push the magic and the address on the stack, so that they + will be the parameters of the cmain function */ + pushl %ebx + pushl %eax + + /* Call the cmain function (os.c) */ + call EXT_C(sos_main) + + /* Should never get there */ +loop: + hlt + jmp loop + +/* Here is the stack */ +.section ".init_stack", "aw", @nobits +.size stack, MULTIBOOT_STACK_SIZE +stack: + .space MULTIBOOT_STACK_SIZE + +/* Some data characterizing the stack addresses */ +.data + .globl bootstrap_stack_bottom +bootstrap_stack_bottom: .long stack + + .globl bootstrap_stack_size +bootstrap_stack_size: .long MULTIBOOT_STACK_SIZE diff --git a/bootstrap/multiboot.h b/bootstrap/multiboot.h new file mode 100644 index 0000000..85d51f7 --- /dev/null +++ b/bootstrap/multiboot.h @@ -0,0 +1,135 @@ +#ifndef __MULTIBOOT_H__ +#define __MULTIBOOT_H__ + +/* multiboot.h - the header for Multiboot */ +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Macros. */ + +/* The magic number for the Multiboot header. */ +#define MULTIBOOT_HEADER_MAGIC 0x1BADB002 + +/* The flags for the Multiboot header. */ +#define MULTIBOOT_HEADER_FLAGS 0x00010003 + +/* The magic number passed by a Multiboot-compliant boot loader. */ +#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002 + +/* The size of our stack (16KB). */ +#define MULTIBOOT_STACK_SIZE 0x4000 + +#define MULTIBOOT_CMDLINE 4 +#define MULTIBOOT_MODS 8 + +/* C symbol format. HAVE_ASM_USCORE is defined by configure. */ +#ifdef HAVE_ASM_USCORE +# define EXT_C(sym) _ ## sym +#else +# define EXT_C(sym) sym +#endif + +#ifndef ASM +/* Do not include here in the assembler sources. */ + +#include + + +/* The address of the stack of the bootstrap thread */ +extern sos_vaddr_t bootstrap_stack_bottom; +extern sos_size_t bootstrap_stack_size; + + +/* Types. */ + +/* The Multiboot header. */ +typedef struct multiboot_header +{ + unsigned long magic; + unsigned long flags; + unsigned long checksum; + unsigned long header_addr; + unsigned long load_addr; + unsigned long load_end_addr; + unsigned long bss_end_addr; + unsigned long entry_addr; +} multiboot_header_t; + +/* The symbol table for a.out. */ +typedef struct aout_symbol_table +{ + unsigned long tabsize; + unsigned long strsize; + unsigned long addr; + unsigned long reserved; +} aout_symbol_table_t; + +/* The section header table for ELF. */ +typedef struct elf_section_header_table +{ + unsigned long num; + unsigned long size; + unsigned long addr; + unsigned long shndx; +} elf_section_header_table_t; + +/* The Multiboot information. */ +typedef struct multiboot_info +{ + unsigned long flags; + unsigned long mem_lower; + unsigned long mem_upper; + unsigned long boot_device; + unsigned long cmdline; + unsigned long mods_count; + unsigned long mods_addr; + union + { + aout_symbol_table_t aout_sym; + elf_section_header_table_t elf_sec; + } u; + unsigned long mmap_length; + unsigned long mmap_addr; + unsigned long drives_length; + unsigned long drives_addr; +} multiboot_info_t; + +/* The module structure. */ +typedef struct module +{ + unsigned long mod_start; + unsigned long mod_end; + unsigned long string; + unsigned long reserved; +} module_t; + +/* The memory map. Be careful that the offset 0 is base_addr_low + but no size. */ +typedef struct memory_map +{ + unsigned long size; + unsigned long base_addr_low; + unsigned long base_addr_high; + unsigned long length_low; + unsigned long length_high; + unsigned long type; +} memory_map_t; + +void dump_multiboot_info(multiboot_info_t *mbi); + +#endif /* ! ASM */ + +#endif /* __MULTIBOOT_H__ */ diff --git a/drivers/bochs.c b/drivers/bochs.c new file mode 100644 index 0000000..c1f92c7 --- /dev/null +++ b/drivers/bochs.c @@ -0,0 +1,123 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include + +#include "bochs.h" + +/* This is a special hack that is only useful when running the + operating system under the Bochs emulator. */ +#define SOS_BOCHS_IOPORT 0xe9 + +sos_ret_t sos_bochs_subsystem_setup(void) +{ + return SOS_OK; +} + +#define _putchar(chr) \ + outb((chr), SOS_BOCHS_IOPORT) + +sos_ret_t sos_bochs_putchar(char c) +{ + _putchar(c); + + return SOS_OK; +} + +sos_ret_t sos_bochs_putstring(const char* str) +{ + for ( ; str && (*str != '\0') ; str++) + _putchar(*str); + + return SOS_OK; +} + +sos_ret_t sos_bochs_puthex(unsigned val, int nbytes) +{ + unsigned c; + +#define BOCHS_PRTHEX(q) \ + ({ unsigned char r; if ((q) >= 10) r='a'+(q)-10; \ + else r='0'+(q); _putchar(r); }) + + switch (nbytes) + { + case 4: + c = (val >> 24) & 0xff; + BOCHS_PRTHEX((c >> 4)&0xf); + BOCHS_PRTHEX(c&0xf); + case 3: + c = (val >> 16) & 0xff; + BOCHS_PRTHEX((c >> 4)&0xf); + BOCHS_PRTHEX(c&0xf); + case 2: + c = (val >> 8) & 0xff; + BOCHS_PRTHEX((c >> 4)&0xf); + BOCHS_PRTHEX(c&0xf); + case 1: + c = val & 0xff; + BOCHS_PRTHEX((c >> 4)&0xf); + BOCHS_PRTHEX(c&0xf); + } + + return SOS_OK; +} + + +sos_ret_t sos_bochs_hexdump(const void* addr, int nbytes) +{ + int offs; + for (offs = 0 ; offs < nbytes ; offs++) + { + const unsigned char *c; + + if ((offs % 16) == 0) + { + sos_bochs_putstring("0x"); + sos_bochs_puthex(offs, 4); + } + + if ((offs % 8) == 0) + sos_bochs_putstring(" "); + + c = (const unsigned char*)(addr + offs); + sos_bochs_puthex(*c, 1); + sos_bochs_putstring(" "); + + if (((offs + 1) % 16) == 0) + sos_bochs_putstring("\n"); + } + + if (offs % 16) + sos_bochs_putstring("\n"); + + return SOS_OK; +} + + +sos_ret_t sos_bochs_printf(const char *format, /* args */...) +{ + char buff[256]; + va_list ap; + + va_start(ap, format); + vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + return sos_bochs_putstring(buff); +} diff --git a/drivers/bochs.h b/drivers/bochs.h new file mode 100644 index 0000000..72bd011 --- /dev/null +++ b/drivers/bochs.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_BOCHS_H_ +#define _SOS_BOCHS_H_ + +/** + * @file bochs.h + * + * If you compiled Bochs with the --enable-e9-hack, then any character + * printed to the 0xE9 I/O port is printed to the xterm that is + * running Bochs. This may appear to be a detail, but in fact, this + * functionnality is *VERY* precious for debugging purposes. This + * """driver""" handles this feature. + */ + +#include +#include + +sos_ret_t sos_bochs_subsystem_setup(void); + +sos_ret_t sos_bochs_putchar(char c); + +sos_ret_t sos_bochs_putstring(const char* str); + +/** Print the least signficant 32 (nbytes == 4), 24 (nbytes == 3), 16 + (nbytes == 2) or 8 (nbytes == 1) bits of val in hexadecimal. */ +sos_ret_t sos_bochs_puthex(unsigned val, int nbytes); + +/** hexdump-style pretty printing */ +sos_ret_t sos_bochs_hexdump(const void* addr, int nbytes); + +/** + * Print the formatted string. Very restricted version of printf(3): + * 1/ can print max 255 chars, 2/ supports only %d/%i, %c, %s, %x + * without any support for flag charachters (eg %08x). + */ +sos_ret_t sos_bochs_printf(const char *format, /* args */...) + __attribute__ ((format (printf, 1, 2))); + +#endif diff --git a/drivers/console.c b/drivers/console.c new file mode 100644 index 0000000..392d23c --- /dev/null +++ b/drivers/console.c @@ -0,0 +1,42 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "console.h" + +sos_ret_t +sos_console_subsystem_setup (void) +{ + sos_ret_t ret; + struct tty_device *tty; + + ret = sos_screen_init(); + if (SOS_OK != ret) + return ret; + + ret = tty_create (SOS_CHARDEV_CONSOLE_MINOR, + sos_screen_putchar, & tty); + if (SOS_OK != ret) + return ret; + + return sos_kbd_subsystem_setup (tty); +} diff --git a/drivers/console.h b/drivers/console.h new file mode 100644 index 0000000..4e1132d --- /dev/null +++ b/drivers/console.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DRV_CONSOLE_H_ +#define _SOS_DRV_CONSOLE_H_ + +/** + * @file console.h + * + * Simple tty device binding together the physical keyboard (@see + * kbd.h) and the text-mode screen (@see x86_videomem.h) + */ + +#include + + +/** + * Create a new TTY device (minor CHARDEV_CONSOLE_MINOR) that controls + * the keyboard/screen + */ +sos_ret_t sos_console_subsystem_setup (void); + +#endif /* _SOS_DRV_CONSOLE_H_ */ diff --git a/drivers/devices.h b/drivers/devices.h new file mode 100644 index 0000000..e5df53d --- /dev/null +++ b/drivers/devices.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2005 David Decotigny, Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DEVICES_H_ +#define _SOS_DEVICES_H_ + +/** + *@file devices.h + * + * This file contains definitions concerning device drivers that must + * be shared between userspace applications and the kernel. They are + * mostly major numbers, IOCTL commands and parameters. + * + * This file must be built so that it can be safely included both from + * user applications and the kernel. + */ + +/* For /dev/zero & /dev/null devices */ +#define SOS_CHARDEV_ZERO_MAJOR 1 +#define SOS_CHARDEV_ZERO_MINOR 0 +#define SOS_CHARDEV_NULL_MINOR 1 + +/* For memory char devices (/dev/mem and /dev/kmem) */ +#define SOS_CHARDEV_MEM_MAJOR 2 +#define SOS_CHARDEV_KMEM_MINOR 0 /* /dev/kmem */ +#define SOS_CHARDEV_PHYSMEM_MINOR 1 /* /dev/mem */ + +/* TTY major and minor*/ +#define SOS_CHARDEV_TTY_MAJOR 3 +#define SOS_CHARDEV_CONSOLE_MINOR 0 +#define SOS_CHARDEV_SERIAL_MINOR 1 + +/* TTY IOCTL commands */ +#define SOS_IOCTL_TTY_SETPARAM 1 +#define SOS_IOCTL_TTY_RESETPARAM 2 + +/* TTY IOCTL command parameters for SOS_IOCTL_TTY_SETPARAM and + SOS_IOCTL_TTY_RESETPARAM */ +#define SOS_IOCTLPARAM_TTY_CANON 1 +#define SOS_IOCTLPARAM_TTY_ECHO 2 + +/** IDE major */ +#define SOS_BLOCKDEV_IDE_MAJOR 1 + +/** Generic hardrive IOCTL: flush caches to disk */ +#define SOS_IOCTL_BLOCKDEV_SYNC 1 + + +#endif /* _SOS_DEVICE_H_ */ diff --git a/drivers/fs_fat.c b/drivers/fs_fat.c new file mode 100644 index 0000000..1020925 --- /dev/null +++ b/drivers/fs_fat.c @@ -0,0 +1,3212 @@ +/* Copyright (C) 2007 Anthoine Bourgeois + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * This drivers implements the FAT file system. + * + * The FAT file system is integrated in the VFS and allow on SOS the + * capability of recording datas on a disk. + * All VFS operations (mount, umount, read, write, seek, stat, mmap...) are + * implemented in this file. + * + * This drivers haves few limitations: + * - FAT12 not supported + * - no time stamp on nodes + * - no long directory entries + * - the 'rename' VFS operation is not supported + * Others features should be implemented in this file. + * + * The current drivers is implemented from the offical specification: + * Microsoft Extensible Firmware Initiative FAT32 File System Specification. + * The document can be found at: + * http://www.csn.ul.ie/~mel/projects/vm/ + */ + +/** + * Note: + * The FAT word have two significations. + * + * First, FAT is the name of the file system for File Allocation Table. + * + * Second, FAT is a structure. A cluster table at the begining of the + * partition. The FAT table maps the data region of the partition by cluster + * number. + * + * It will be specify explicitly if the understanding can be ambigus. + */ + +#include "fs_fat.h" + +/* ******************************************************** + * FAT structures definitions {{{ + */ + +/** FAT Type */ +#define FAT12 12 +#define FAT16 16 +#define FAT32 32 + +/** + * Converts a FAT type to a string + */ +static const char * +sos_fat_type_str (unsigned int type) +{ + switch (type) + { + case FAT12: + return "FAT12"; + case FAT16: + return "FAT16"; + case FAT32: + return "FAT32"; + default: + return "Unknown"; + } +} + +/** FAT cluster limit + * + * The maximum number of a FAT type. + * These constant are helpful to determine the FAT type. + * + * section "FAT Type Determination" in the offical spec. + */ +#define FAT12_CLUSTER_LIMIT 4085 +#define FAT16_CLUSTER_LIMIT 65525 + +/** FAT EOC (End Of Cluster) marker + * + * In this context the FAT refers to the cluster table. + * When the cluster contains a end of file then the EOC marker is set in the + * mapping of this cluster in the FAT. + */ +#define FAT16_EOC 0xFFF8 +#define FAT32_EOC 0x0FFFFFF8 + +/** FAT cluster mask + * + * In this context the FAT refers to the cluster table. + * This mask is used for the cluster number in the FAT. The mask prevent + * number overflow. We can see that the FAT16 number is a 16 bits unsigned integer + * and the FAT32 number is a 28 bits unsigned integer. The 4 higher + * significant bits are reserved. + */ +#define FAT16_CLUSTER_MASK 0xFFFF +#define FAT32_CLUSTER_MASK 0x0FFFFFFF + +/** FAT cluster entry size + * + * In this context the FAT refers to the cluster table. + * The size is in bytes. + */ +#define FAT16_CLUSTER_ENTRY_SIZE 2 +#define FAT32_CLUSTER_ENTRY_SIZE 4 + +/** FAT cluster value for the first valid cluster + * + * The first valid data cluster have number 2. + * It's specify in the section "FAT Data Structure" in the official spec. + */ +#define FAT_FIRST_VALID_CLUSTER 2 + +/** FAT cluster entry value for a free cluster + * + * In this context the FAT refers to the cluster table. + * This marker specify that the cluster entry (and the cluster) is free and + * the cluster is available for use. + */ +#define FAT_FREE_CLUSTER_ENTRY_VALUE 0 + +/** The FAT16 specific BPB + * + * BPB is the BIOS Prameter Block. The BPB is located in the first sector of + * the partition also named Boot Sector. sos_fat16_BPB and sos_fat32_BPB are + * specific parts of the FAT types, FAT16 and FAT32, and are include in the + * sos_fat_boot_sector structure above. The Boot Sector is the + * entry point of the FAT file system. It gives informations on the file + * system. + * + * FAT FS sturcture: + * __________________________________________ + * |BS/BPB|FAT struct| data clusters | <--- a FAT partition. + * ------------------------------------------ + */ +struct sos_fat16_BPB { + sos_ui8_t BS_DrvNum; /* Int 0x13 driver number. */ + /* Under real mode operating systems such as MS-DOS, calling INT 0x13 + * would jump into the computer's BIOS code for Low Level Disk Services, + * which will carry out sector-based disk read or write for the program. */ + sos_ui8_t BS_Reserved1; /* Reserved (used by Windows NT). */ + sos_ui8_t BS_BootSig; /* Extended boot signature (0x29). */ + sos_ui32_t BS_VolID; /* Volume serial number. */ + char BS_VolLab[11]; /* Volume label. */ + char BS_FilSysType[8]; /* Always set to the string "FATxx " */ +} __attribute__((packed)); + +/** The FAT32 specific BPB */ +struct sos_fat32_BPB { + sos_ui32_t BPB_FATSz32; /* This field is the FAT32 32-bit count of sectors occupied by ONE FAT */ + sos_ui16_t BPB_ExtFlags; /* Flags */ + sos_ui16_t BPB_FSVer; /* High byte is major revision number. Low byte is minor revision number. */ + sos_ui32_t BPB_RootClus; /* This is set to the cluster number of the first cluster of the root directory. */ + sos_ui16_t BPB_FSInfo; /* Sector number of FSINFO structure in the reserved area of the FAT32 volume. */ + sos_ui16_t BPB_BkBootSec; /* If non-zero, indicates the sector number in the reserved area of */ + /* the volume of a copy of the boot record. */ + char BPB_Reserved[12]; /* Reserved for future expansion. */ + + struct sos_fat16_BPB fat16_BPB; +} __attribute__ ((packed)); + +/** The FAT first sector structure */ +struct sos_fat_boot_sector { + /* BS (Boot Sector) */ + sos_ui8_t BS_jmpbBoot[3]; /* jmpBoot[0] = 0xEB, jmpBoot[1] = 0x??, jmpBoot[2] = 0x90 */ + /* or jmpBoot[0] = 0xE9, jmpBoot[1] = 0x??, jmpBoot[2] = 0x?? */ + char BS_OEMName[8]; /* MSWIN4.1 */ + + /* BPB (BIOS Parameter Block) */ + sos_ui16_t BPB_BytsPerSec; /* Count of bytes per sector: 512, 1024, 2048 or 4096 */ + sos_ui8_t BPB_SecPerClus; /* Number of sectors per allocation unit: 1, 2, 4, 8, 16, 32, 64 or 128 */ + sos_ui16_t BPB_RsvdSecCnt; /* Number of reserved sectors in the Reserved region of the volume */ + /* starting at the first sector of the volume. */ + sos_ui8_t BPB_NumFATs; /* The count of FAT data structures on the volume. */ + sos_ui16_t BPB_RootEntCnt; /* For FAT32 volumes, this field must be set to 0. */ + sos_ui16_t BPB_TotSec16; /* For FAT32 volumes, this field must be 0. */ + sos_ui8_t BPB_Media; /* Media type: 0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE or 0xFF */ + sos_ui16_t BPB_FATSz16; /* On FAT32 volumes this field must be 0. */ + sos_ui16_t BPB_SecPerTrk; /* Sectors per track for interrupt 0x13. */ + sos_ui16_t BPB_NumHeads; /* Number of heads for interrupt 0x13. */ + sos_ui32_t BPB_HiddSec; /* Count of hidden sectors preceding the partition that contains this FAT volume. */ + sos_ui32_t BPB_TotSec32; /* This field is the new 32-bit total count of sectors on the volume. */ + + /* BPB specific */ + union { + struct sos_fat16_BPB fat16_BPB; + struct sos_fat32_BPB fat32_BPB; + } BPB_specific; +} __attribute__ ((packed)); + +/** Fat Directory entry structure + * + * There are only 2 nodes types in FAT: file and directory. + * A FAT directory is nothing but a "file" composed of a linear list of + * 32-byte directory entry structures. + */ +struct sos_fat_directory_entry { +#define IS_FAT_VALID_CHAR(c) ((c) > 0x20 && (c) != 0x22 && ((c) < 0x2A || (c) > 0x2C) && /*(c) != 0x2E &&*/ (c) != 0x2F && ((c) < 0x3A || (c) > 0x3F) && ((c) < 0x5B || (c) > 0x5D) && (c) != 0x7C) + char DIR_Name[11]; /* Short name */ + + /* File attributes */ +#define ATTR_READ_ONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLUME_ID 0x08 +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID) + sos_ui8_t DIR_Attr; /* File attributes */ + sos_ui8_t DIR_NTRes; /* Reserved for use by Windows NT */ + sos_ui8_t DIR_CrtTimeTenth; /* Millisecond stamp at file creation time */ + sos_ui16_t DIR_CrtTime; /* Time file was created. */ + sos_ui16_t DIR_CrtDate; /* Date file was created. */ + sos_ui16_t DIR_LstAccDate; /* Last access date */ + sos_ui16_t DIR_FstClusHI; /* High word of this entry's first cluster number. */ + sos_ui16_t DIR_WrtTime; /* Time of last write. */ + sos_ui16_t DIR_WrtDate; /* Date of last write. */ + sos_ui16_t DIR_FstClusLO; /* Low word of this entry's first cluster number. */ + sos_ui32_t DIR_FileSize; /* 32-bit DWORD holding this file's size in bytes. */ +} __attribute__ ((packed)); + +/** Fat Info strcuture + * + * This structure is FAT32 specific. It's currently unused. + * + * This structure contains generals informations on the FAT file system. The + * main information is the current state of the FAT cluster table. + */ +struct sos_fat_info { + sos_ui32_t FSI_LeadSig; /* Value 0x41615252 */ + char FSI_Reserved1[480]; /* This field is currently reserved for future expansion. */ + sos_ui32_t FSI_StrucSig; /* Value 0x61417272 */ + sos_ui32_t FSI_Free_Count; /* Contains the last known free cluster count on the volume */ + sos_ui32_t FSI_Nxt_Free; /* It indicates the cluster number at which the driver + should start looking for free clusters */ + char FSI_Reserved2[12]; /* This field is currently reserved for future expansion. */ + sos_ui32_t FSI_TrailSig; /* Value 0xAA550000 */ +} __attribute__ ((packed)); + +/** All structures before are given by the official spec. + * ====================================================== + * Now starts internals SOS structures. + */ + +/** Fat data structure + * + * This structure is filled during the mount operation. These fields are + * computed from BS/BPB like describe in the official specification. The + * structure is fundamental for partition use in many VFS operations. + */ +struct sos_fat_data_structure { + sos_ui32_t RootDirSectors; /* Number of sectors in root directory */ + sos_ui32_t FATSz; /* FAT size */ + sos_ui32_t FirstDataSector; /* First data sector */ + sos_ui32_t TotSec; /* Total number of sector in the partition */ + sos_ui32_t DataSec; /* Number of data sector in the partition */ + sos_ui32_t CountOfClusters; /* Number of cluster in the partition */ + sos_ui32_t FirstRootDirSecNum; /* First sector of the root directory */ + + sos_ui32_t FAT_Type; /* FAT type */ + sos_ui32_t EndOfCluster; /* End of cluster marker */ + + struct sos_fat_boot_sector BootSector; +}; + +/** Fat mmap page list structure */ +struct sos_fat_mmap_page_list { + struct sos_fat_mmap_page_list *next; /* Next page of the file */ + + sos_luoffset_t offset; /* Offset in the file. Must be a multiple of PAGE_SIZE */ + sos_vaddr_t mmap_page; /* The page fill of datas of the file */ +}; + +/** Fat file informations */ +struct sos_fat_file { + struct sos_fat_directory_entry dir_entry; + + /* Needed variables to map the file */ + struct sos_umem_vmm_mapped_resource mapres; + sos_count_t num_mappings; + struct sos_fat_mmap_page_list *list; /* The list is ordered by offset */ +}; + +/** The description of the "FAT FS" */ +static struct sos_fs_manager_type fat_type; +/* }}} */ + +/* ******************************************************** + * Helper functions {{{ + */ + +/** Helper macro to get the first cluster of the FS node (subdir / file) as + * referenced by a directory entry. */ +#define GET_FIRST_CLUSTER(dir_entry) \ + (((dir_entry)->DIR_FstClusHI<<16) | (dir_entry)->DIR_FstClusLO) + +/** Helper function to compute the first sector number + * of the given cluster. */ +static sos_ui64_t +sos_fat_helper_first_sector_of_cluster( + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr) +{ + return ((cluster_nr - FAT_FIRST_VALID_CLUSTER) * data_struct->BootSector.BPB_SecPerClus) + data_struct->FirstDataSector; +} + +/** Helper function to compute the cluster containing + * the given sector. */ +static sos_ui32_t +sos_fat_helper_cluster_of_sector( + struct sos_fat_data_structure *data_struct, + sos_ui64_t sector_nr) +{ + return ((sector_nr - data_struct->FirstDataSector) / data_struct->BootSector.BPB_SecPerClus) + FAT_FIRST_VALID_CLUSTER; +} + +/** Helper function to compute the FAT offset from the FAT type. + * + * In this context the FAT refers to the cluster table. + * This function compute an offset (in Bytes) in the first FAT cluster table. + */ +static sos_ui32_t sos_fat_helper_fat_offset( + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr) +{ + if (data_struct->FAT_Type == FAT16) { + return cluster_nr * FAT16_CLUSTER_ENTRY_SIZE; + } else if (data_struct->FAT_Type == FAT32) { + return cluster_nr * FAT32_CLUSTER_ENTRY_SIZE; + } + + /** FAT12 is unsupported */ + return 0; +} + +/** Helper function to compute the FAT sector number in the cluster table and its offset + * in this sector for a given cluster. */ +static sos_ret_t +sos_fat_helper_get_sector_and_offset( + struct sos_fat_data_structure* data_struct, + sos_ui32_t cluster_nr, + sos_ui32_t *ThisFATSecNum, + sos_ui32_t *ThisFATEntOffset) +{ + *ThisFATSecNum = data_struct->BootSector.BPB_RsvdSecCnt + + (sos_fat_helper_fat_offset(data_struct, cluster_nr) / data_struct->BootSector.BPB_BytsPerSec); + *ThisFATEntOffset = + sos_fat_helper_fat_offset(data_struct, cluster_nr) % data_struct->BootSector.BPB_BytsPerSec; + + return SOS_OK; +} + +/** Helper function to find the first free cluster in + * the cluster table. */ +static sos_ret_t +sos_fat_helper_find_free_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t *cluster_nr_result) +{ + sos_ui32_t cluster_nr; + sos_ret_t retval; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + sos_ui32_t FATClusEntryVal; + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) { + return -SOS_ENOMEM; + } + + for (cluster_nr = FAT_FIRST_VALID_CLUSTER; + cluster_nr < data_struct->CountOfClusters; + cluster_nr++) { + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + if (cluster_nr == FAT_FIRST_VALID_CLUSTER || ThisFATEntOffset == 0) { + /* In case of we are at beginning of a new sector in the FAT cluster table, + * we need this sector then we read it in SecBuf variable */ + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + } + + /* SecBuf variable contains a sector of the FAT cluster table + * then we can get the cluster values */ + if (data_struct->FAT_Type == FAT16) { + FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]); + } else { + FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK; + } + + if (FATClusEntryVal == FAT_FREE_CLUSTER_ENTRY_VALUE) { + sos_kfree((sos_vaddr_t) SecBuf); + *cluster_nr_result = cluster_nr; + return SOS_OK; + } + } + + /* No cluster available */ + return -SOS_EIO; +} + +/** Helper function to set a cluster entry in the FAT cluster table with value. + * + * In this context the FAT refers to the cluster table. + * Updates on disk the cluster entry value in the FAT cluster table with the + * value in parameter. The cluster number is the cluster_nr label in parameter. + */ +static sos_ret_t +sos_fat_helper_set_cluster_value( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr, + sos_ui32_t value) +{ + sos_ret_t retval; + sos_ui32_t fat_nr; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + + SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER); + + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) + return -SOS_ENOMEM; + + /* Write value on each entries of each FATs */ + for (fat_nr = 0; + fat_nr < data_struct->BootSector.BPB_NumFATs; + fat_nr++) { + /* Read the FAT cluster table sector who contains the value to change. + * The sector of the FAT is in SecBuf variable */ + retval = sos_blockdev_kernel_read(block_device, + ((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + /* Change the cluster entry value in the SecBuf variable read below */ + if (data_struct->FAT_Type == FAT16) { + *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = (sos_ui16_t) (FAT16_CLUSTER_MASK & value); + } else { + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK); + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | (FAT32_CLUSTER_MASK & value); + } + + /* Write on disk the modified sector of the FAT */ + retval = sos_blockdev_kernel_write(block_device, + ((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + return -SOS_EIO; + } + } + + sos_kfree((sos_vaddr_t) SecBuf); + + return SOS_OK; +} + +/** Helper function to find the next cluster in the cluster list of a node + * + * In this context the FAT refers to the cluster table. + * This function read the next data cluster in the FAT cluster table. + * It reads the FAT sector of the current cluster (cluster_nr in parameter) and + * takes the cluster value of cluster_nr to get the next cluster of the + * file/dir. Result is return by next_cluster parameter. + */ +static sos_ret_t +sos_fat_helper_find_next_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr, + sos_ui32_t *next_cluster) +{ + sos_ret_t retval; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + sos_ui32_t FATClusEntryVal; + + SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER); + + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) + return -SOS_ENOMEM; + + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + /* Read the FAT sector who contains cluster_nr entry */ + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + /* Take the cluster_nr cluster value */ + if (data_struct->FAT_Type == FAT16) { + FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]); + } else { + FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK; + } + + SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE); + + sos_kfree((sos_vaddr_t) SecBuf); + *next_cluster = FATClusEntryVal; + return SOS_OK; +} + +/** Helper function to find the last cluster in the cluster list of a node + * + * In this context the FAT refers to the cluster table. + * This function read cluster by cluster in the FAT cluster table. + * It reads the FAT sector of the current cluster (cluster_nr in parameter) and + * loops on the cluster value of cluster_nr to get the next cluster of the + * file/dir until the end of cluster. Result is return by last_cluster parameter. + * + * This function is indirecly used in these VFS operations: + * truncate, node_descructor, new_mapping, write. + */ +static sos_ret_t +sos_fat_helper_find_last_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr, + sos_ui32_t *last_cluster) +{ + sos_ret_t retval; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + sos_ui32_t FATClusEntryVal = cluster_nr; + + SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER); + + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) + return -SOS_ENOMEM; + + /* Loop on the cluster list. Start of the list: cluster_nr. End of the loop: + * cluster value == End Of Cluster */ + do { + cluster_nr = FATClusEntryVal; + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + /* Read the sector of the FAT that contains cluster_nr entry */ + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + /* Get the cluster value */ + if (data_struct->FAT_Type == FAT16) { + FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]); + } else { + FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK; + } + + SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE); + } while (FATClusEntryVal != data_struct->EndOfCluster); + + sos_kfree((sos_vaddr_t) SecBuf); + *last_cluster = cluster_nr; + return SOS_OK; +} + +/* Helper function to remove the last cluster of the cluster list of a node. + * + * This function is indirecly used in these VFS operations: + * truncate, node_destructor. + */ +static sos_ret_t +sos_fat_helper_remove_last_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t cluster_nr) +{ + sos_ret_t retval; + sos_ui32_t fat_nr; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + sos_ui32_t FATClusEntryVal = cluster_nr; + sos_ui32_t previous_cluster; + + SOS_ASSERT_FATAL(cluster_nr >= FAT_FIRST_VALID_CLUSTER); + + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) + return -SOS_ENOMEM; + + /* Iterate on the cluster list. Start of the list: cluster_nr. End of the + * loop: cluster value == End of cluster */ + do { + previous_cluster = cluster_nr; + cluster_nr = FATClusEntryVal; + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + if (data_struct->FAT_Type == FAT16) { + FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]); + } else { + FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK; + } + + SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE); + } while (FATClusEntryVal != data_struct->EndOfCluster); + + /* Node is composed by more than one cluster? */ + if (previous_cluster != cluster_nr) { + /* Mark the last cluster as free */ + if (data_struct->FAT_Type == FAT16) { + *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = FAT_FREE_CLUSTER_ENTRY_VALUE; + } else { + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK); + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | FAT_FREE_CLUSTER_ENTRY_VALUE; + } + + /* Write the modified FAT sector in all FATs */ + for (fat_nr = 0; + fat_nr < data_struct->BootSector.BPB_NumFATs; + fat_nr++) { + retval = sos_blockdev_kernel_write(block_device, + ((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + } + + /* Now mark the previous cluster as the end of the cluster list */ + sos_fat_helper_get_sector_and_offset(data_struct, previous_cluster, &ThisFATSecNum, &ThisFATEntOffset); + + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + /* Mark the cluster as EOC */ + if (data_struct->FAT_Type == FAT16) { + *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]) = FAT16_EOC; + } else { + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & (~FAT32_CLUSTER_MASK); + *((sos_ui32_t *) &SecBuf[ThisFATEntOffset]) = + (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) | FAT32_EOC; + } + + /* Write the modified FAT sector in all FATs */ + for (fat_nr = 0; + fat_nr < data_struct->BootSector.BPB_NumFATs; + fat_nr++) { + retval = sos_blockdev_kernel_write(block_device, + ((fat_nr * data_struct->FATSz) + ThisFATSecNum)*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + } + } + + sos_kfree((sos_vaddr_t) SecBuf); + return SOS_OK; +} + +/** Helper function to remove all clusters of a node + * + * This function is indirecly used in these VFS operations: + * node_destructor. + */ +static sos_ret_t +sos_fat_helper_remove_all_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t first_cluster) +{ + sos_ret_t retval; + sos_ui32_t current_cluster, next_cluster; + + SOS_ASSERT_FATAL(first_cluster >= FAT_FIRST_VALID_CLUSTER); + + current_cluster = first_cluster; + + while (current_cluster != data_struct->EndOfCluster) { + retval = sos_fat_helper_find_next_cluster(block_device, + data_struct, + current_cluster, + &next_cluster); + if (retval != SOS_OK) { + return retval; + } + + retval = sos_fat_helper_set_cluster_value(block_device, + data_struct, + current_cluster, + FAT_FREE_CLUSTER_ENTRY_VALUE); + if (retval != SOS_OK) { + return retval; + } + + current_cluster = next_cluster; + } + + retval = sos_fat_helper_set_cluster_value(block_device, + data_struct, + current_cluster, + FAT_FREE_CLUSTER_ENTRY_VALUE); + if (retval != SOS_OK) { + return retval; + } + + return SOS_OK; +} + +/** Helper function to append a new cluster at the end of + * the cluster list of a node. + * + * This function is indirecly used in these VFS operations: + * truncate, new_mapping, write. + */ +static sos_ret_t +sos_fat_helper_add_new_cluster( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t first_cluster, + sos_ui32_t *new_cluster) +{ + sos_ret_t retval; + sos_ui32_t last_cluster; + + SOS_ASSERT_FATAL(first_cluster >= FAT_FIRST_VALID_CLUSTER); + + retval = sos_fat_helper_find_last_cluster( + block_device, + data_struct, + first_cluster, + &last_cluster); + if (retval != SOS_OK) + return retval; + retval = sos_fat_helper_find_free_cluster( + block_device, + data_struct, + new_cluster); + if (retval != SOS_OK) + return retval; + sos_fat_helper_set_cluster_value( + block_device, + data_struct, + last_cluster, + *new_cluster); + if (retval != SOS_OK) + return retval; + sos_fat_helper_set_cluster_value( + block_device, + data_struct, + *new_cluster, + data_struct->EndOfCluster); + if (retval != SOS_OK) + return retval; + + return SOS_OK; +} + +/** Helper function read a slice of node. + * + * The slice to read starts at 'start_storage_location' offset of the + * partition and has a length of 'len' bytes. + * + * This function is used in these VFS functions: + * read, link and mmap. + */ +static sos_ret_t +sos_fat_helper_read( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui64_t start_storage_location, + sos_vaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + sos_ui32_t next_cluster; + sos_size_t clus_size = data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus; + sos_ui32_t clus_len = (*len / clus_size) + 1; + + int i; + sos_size_t current_len, saved_len, total_len = 0; + /* Read the file by cluster */ + for (i = 0; i < clus_len; i++) { + /* The first and the last clusters are the only clusters whose can be not full */ + if (i == 0) { + /* In the first cluster the begining can be truncate */ + current_len = clus_size - (start_storage_location % clus_size); + if (current_len > *len) + current_len = *len; + } else if (i == clus_len - 1) { + /* In the last cluster the end can be truncate */ + current_len = *len - total_len; + if (current_len == 0) + break; + } else { + current_len = clus_size; + } + saved_len = current_len; + + retval = sos_blockdev_kernel_read(block_device, + start_storage_location, + dest_buf + total_len, + ¤t_len); + if (retval != SOS_OK) { + return retval; + } else if (current_len != saved_len) { + return -SOS_EIO; + } + + total_len += current_len; + + if (total_len == *len) + break; + + retval = sos_fat_helper_find_next_cluster( + block_device, + data_struct, + sos_fat_helper_cluster_of_sector(data_struct, + start_storage_location / data_struct->BootSector.BPB_BytsPerSec), + &next_cluster); + if (retval != SOS_OK) { + return retval; + } + + start_storage_location = sos_fat_helper_first_sector_of_cluster(data_struct, next_cluster) + * data_struct->BootSector.BPB_BytsPerSec; + } + + return SOS_OK; +} + +/** Helper function to find a free entry in a directory node. + * + * This function iterates on the clusters list of a directory to find a free + * directory entry. A directory is simply a sequencial list of 32-Bytes directory entry. + * A free directory can be found when DIR_Name[0] == 0 or 0xe5. + */ +static sos_ret_t +sos_fat_helper_find_free_directory_entry( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui64_t start_storage_location, + sos_ui64_t *new_storage_location) +{ + sos_ret_t retval; + int i, j; + sos_ui32_t cluster_nr, sector_limit; + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_size_t dirent_size = sizeof(struct sos_fat_directory_entry); + struct sos_fat_directory_entry *dir_entry = (struct sos_fat_directory_entry *) sos_kmalloc(dirent_size, 0); + + sos_ui64_t sector_nr = start_storage_location / sector_size; + sos_ui64_t sector_rem = start_storage_location % sector_size; + + if (sector_nr >= data_struct->FirstRootDirSecNum && + sector_nr < data_struct->FirstDataSector) { + sector_limit = data_struct->RootDirSectors; + } else { + sector_limit = data_struct->BootSector.BPB_SecPerClus; + } + + /* Loop on directory's clusters */ + do { + /* Loop on the cluster's sectors */ + for (i = 0; i < sector_limit; i++) { + /* Loop on the sector's directory entries */ + for (j = sector_rem / sizeof(struct sos_fat_directory_entry); + j < sector_size / sizeof(struct sos_fat_directory_entry); + j++) { + + retval = sos_fat_helper_read(block_device, + data_struct, + ((sector_nr + i) * sector_size) + + (j * sizeof(struct sos_fat_directory_entry)), + (sos_vaddr_t)dir_entry, + &dirent_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + /* A directory entry is free if DIR_Name[0] == 0xe5 or 0 */ + if ((dir_entry->DIR_Name[0] & 0xFF) == 0xE5 || + (dir_entry->DIR_Name[0] & 0xFF) == 0) { + *new_storage_location = ((sector_nr + i) * sector_size) + + (j * sizeof(struct sos_fat_directory_entry)); + sos_kfree((sos_vaddr_t) dir_entry); + return SOS_OK; + } + } + } + + /* Find the next cluster of the directory */ + if (sector_nr >= data_struct->FirstRootDirSecNum && + sector_nr < data_struct->FirstDataSector) { + /* Root directory is a special case */ + break; + } else { + cluster_nr = sos_fat_helper_cluster_of_sector(data_struct, sector_nr); + retval = sos_fat_helper_find_next_cluster(block_device, data_struct, cluster_nr, &cluster_nr); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + } + } while (cluster_nr != data_struct->EndOfCluster); + + sos_kfree((sos_vaddr_t) dir_entry); + /* No free entry in this directory */ + return -SOS_ENOENT; +} + +/** Helper function to write an entry in the current directory. */ +static sos_ret_t +sos_fat_helper_set_directory_entry( + struct sos_blockdev_instance *block_device, + sos_ui64_t storage_location, + struct sos_fat_directory_entry *dir_entry) +{ + sos_ret_t retval; + sos_size_t dir_entry_size = sizeof(struct sos_fat_directory_entry); + + retval = sos_blockdev_kernel_write(block_device, + storage_location, + (sos_vaddr_t) dir_entry, + &dir_entry_size); + if (retval != SOS_OK) { + return retval; + } else if (dir_entry_size != sizeof(struct sos_fat_directory_entry)) { + return -SOS_EIO; + } + + return SOS_OK; +} + +/** Helper function to copy from an entry in a string. + * + * This function will convert the FAT specific name format to the generic VFS + * format. readdir operation use it. + */ +static sos_ret_t +sos_fat_helper_copy_file_name( + struct sos_fat_directory_entry *dir_entry, + char *name, + sos_ui16_t *namelen) +{ + int i = 0, j; + char c; + + if (dir_entry->DIR_Name[i] == 0x05) { + name[i] = 0xE5; + i++; + } + + for ( ; i < 8; i++) { + c = dir_entry->DIR_Name[i]; + if (c != ' ') { + if (IS_FAT_VALID_CHAR(c)) { + name[i] = dir_entry->DIR_Name[i]; + } else { + return -SOS_EINVAL; + } + } else { + break; + } + } + + if (dir_entry->DIR_Name[8] != ' ') { + name[i++] = '.'; + } else { + *namelen = i; + return SOS_OK; + } + + for (j = 8; j < 11; j++) { + c = dir_entry->DIR_Name[j]; + if (c != ' ') { + if (IS_FAT_VALID_CHAR(c)) { + name[i++] = dir_entry->DIR_Name[j]; + } else { + return -SOS_EINVAL; + } + } else { + break; + } + } + *namelen = i; + + return SOS_OK; +} + +static sos_bool_t +fat_nsnode_same_name(const char *name1, sos_ui16_t namelen1, + const char *name2, sos_ui16_t namelen2); + +/** Helper function to compare names between a FAT's entry and a string. */ +static sos_bool_t +sos_fat_helper_same_name(const char *name, sos_ui16_t namelen, struct sos_fat_directory_entry *dir_entry) +{ + char name2[SOS_FS_DIRENT_NAME_MAXLEN]; + memset(name2, 0, SOS_FS_DIRENT_NAME_MAXLEN); + sos_ui16_t namelen2 = SOS_FS_DIRENT_NAME_MAXLEN; + + sos_fat_helper_copy_file_name(dir_entry, name2, &namelen2); + + return fat_nsnode_same_name(name, namelen, name2, namelen2); +} + +/** Hepler function to get the next entry in a directory. + * + * This function skip empty directory entry to return the first not free + * directory entry. A directory entry shouldn't have a DIR_Name[0] == 0xe5 or + * 0 and shouldn't be a long name entry (DIR_Attr == ATTR_LONG_NAME). + */ +static sos_ret_t +sos_fat_helper_get_next_directory_entry( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui64_t storage_location, + struct sos_fat_directory_entry **dir_entry, + sos_ui64_t *dir_storage_location, + sos_si64_t *dir_offset) +{ + sos_ret_t retval; + int i, j; + sos_ui32_t cluster_nr, sector_limit; + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + struct sos_fat_directory_entry *SecBuf = (struct sos_fat_directory_entry *) sos_kmalloc(sector_size, 0); + sos_ui64_t sector_nr = storage_location / data_struct->BootSector.BPB_BytsPerSec; + sos_ui32_t sector_rem = (storage_location % data_struct->BootSector.BPB_BytsPerSec) / + sizeof(struct sos_fat_directory_entry); + + *dir_offset = 0; + + if (sector_nr >= data_struct->FirstRootDirSecNum && + sector_nr < data_struct->FirstDataSector) { + sector_limit = data_struct->FirstRootDirSecNum + data_struct->RootDirSectors; + } else { + sector_limit = + sos_fat_helper_first_sector_of_cluster( + data_struct, + sos_fat_helper_cluster_of_sector( + data_struct, + sector_nr)) + + data_struct->BootSector.BPB_SecPerClus; + } + + /* loop on directory's clusters */ + do { + /* loop on cluster's sectors */ + for (i = sector_nr; i < sector_limit; i++) { + retval = sos_blockdev_kernel_read(block_device, + i * sector_size, + (sos_vaddr_t) SecBuf, + §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + /* loop on directory's entry in sector i */ + for (j = sector_rem; j < sector_size / sizeof(struct sos_fat_directory_entry); j++) { + *dir_offset += sizeof(struct sos_fat_directory_entry); + /* A directory entry is normal if DIR_Name[0] != 0xe5 and 0 + * and is not a long name directory entry */ + if ((SecBuf[j].DIR_Name[0] & 0xFF) != 0xE5 && + (SecBuf[j].DIR_Name[0] & 0xFF) != 0 && + SecBuf[j].DIR_Attr != ATTR_LONG_NAME) { + if (dir_entry != NULL) { + memcpy(*dir_entry, &SecBuf[j], sizeof(struct sos_fat_directory_entry)); + } + *dir_storage_location = (i * sector_size) + + (j * sizeof(struct sos_fat_directory_entry)); + sos_kfree((sos_vaddr_t) SecBuf); + return SOS_OK; + } else if ( (SecBuf[j].DIR_Name[0] & 0xFF) == 0 ) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_ENOENT; + } + } + } + + /* Find the next cluster of the directory */ + if (sector_nr >= data_struct->FirstRootDirSecNum && + sector_nr < data_struct->FirstDataSector) { + /* Root directory is a special case */ + break; + } else { + cluster_nr = sos_fat_helper_cluster_of_sector(data_struct, sector_nr); + retval = sos_fat_helper_find_next_cluster(block_device, data_struct, cluster_nr, &cluster_nr); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } + } + + sector_rem = 0; + } while (cluster_nr != data_struct->EndOfCluster); + + sos_kfree((sos_vaddr_t) SecBuf); + /* No next entry in the directory */ + return -SOS_ENOENT; +} + +/** Helper function to compute the storage location in a file + * from an offset in this file. + * + * This function, with the cluster number find in the directory entry and an + * offset (both in parameters), computes the storage location of the byte from + * the beginning of the partition with the FAT cluster list. + * The position is a cluster and an offset in this cluster. When cluster is + * known, the FAT cluster list can be loop on until this cluster is find then + * the cluster is convert in the absolut position in the partition. This + * position is added with the offset in this cluster and the storage location + * is find. + */ +static sos_ret_t +sos_fat_helper_storage_location_of_file( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + sos_ui32_t first_cluster_of_file, + sos_ui32_t offset_in_file, + sos_ui64_t *storage_location) +{ + sos_ret_t retval; + sos_ui32_t ThisFATSecNum, ThisFATEntOffset; + sos_ui32_t FATClusEntryVal = first_cluster_of_file; + sos_ui32_t cluster_nr; + int i; + + SOS_ASSERT_FATAL(first_cluster_of_file >= FAT_FIRST_VALID_CLUSTER); + + sos_size_t sector_size = data_struct->BootSector.BPB_BytsPerSec; + sos_ui8_t *SecBuf = (sos_ui8_t *) sos_kmalloc(sector_size, 0); + if (SecBuf == NULL) + return -SOS_ENOMEM; + + /* Compute the cluster of the position and the offset in this cluster of the + * position */ + sos_ui32_t cluster_offset = offset_in_file / (sector_size * data_struct->BootSector.BPB_SecPerClus); + sos_ui32_t offset_rem = offset_in_file % (sector_size * data_struct->BootSector.BPB_SecPerClus); + + /* Find the cluster computed below */ + for (i = 0; i < cluster_offset; i++) { + cluster_nr = FATClusEntryVal; + sos_fat_helper_get_sector_and_offset(data_struct, cluster_nr, &ThisFATSecNum, &ThisFATEntOffset); + + retval = sos_blockdev_kernel_read(block_device, + ThisFATSecNum*data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)SecBuf, §or_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) SecBuf); + return retval; + } else if (sector_size != data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + + if (data_struct->FAT_Type == FAT16) { + FATClusEntryVal = *((sos_ui16_t *) &SecBuf[ThisFATEntOffset]); + } else { + FATClusEntryVal = (*((sos_ui32_t *) &SecBuf[ThisFATEntOffset])) & FAT32_CLUSTER_MASK; + } + + SOS_ASSERT_FATAL(FATClusEntryVal != FAT_FREE_CLUSTER_ENTRY_VALUE); + + if (FATClusEntryVal == data_struct->EndOfCluster) { + sos_kfree((sos_vaddr_t) SecBuf); + return -SOS_EIO; + } + } + + /* Adds the offset in the cluster to find the storage location */ + *storage_location = + (sos_fat_helper_first_sector_of_cluster(data_struct, FATClusEntryVal) * sector_size) + offset_rem; + + sos_kfree((sos_vaddr_t) SecBuf); + return SOS_OK; +} + +/** Helper function to resize a file. + * + * Only file can be resize. + * With the new_size parameter, clusters to add or to delete can be compute + * and modified as desired. + * + * This function is used in these VFS operations: + * truncate, new_mapping, write. + */ +static sos_ret_t +sos_fat_helper_resize( + struct sos_blockdev_instance *block_device, + struct sos_fat_data_structure *data_struct, + struct sos_fs_node *fs_node, + sos_ui32_t new_size) +{ + int i; + sos_ret_t retval; + struct sos_fat_directory_entry *dir_entry = fs_node->custom_data; + sos_ui32_t first_cluster; + sos_ui32_t old_size; + if (dir_entry != NULL) { + first_cluster = GET_FIRST_CLUSTER(dir_entry); + old_size = dir_entry->DIR_FileSize; + } else { + first_cluster = data_struct->FirstRootDirSecNum; + old_size = data_struct->RootDirSectors; + } + sos_size_t clus_size = + (data_struct->BootSector.BPB_SecPerClus * data_struct->BootSector.BPB_BytsPerSec); + + /* How many clusters before and after the resize? */ + sos_ui32_t new_cluster, old_cluster, free_cluster; + new_cluster = new_size / clus_size; + old_cluster = old_size / clus_size; + + /* File should be bigger or smaller? */ + if (new_cluster > old_cluster) { + /* Bigger */ + sos_ui32_t *clus_buf = (sos_ui32_t*)sos_kmalloc(clus_size, 0); + if (clus_buf == NULL) + return -SOS_ENOMEM; + + memset(clus_buf, 0, clus_size); + + for (i = 0; i < new_cluster - old_cluster; i++) { + retval = sos_fat_helper_add_new_cluster(block_device, + data_struct, + first_cluster, + &free_cluster); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) clus_buf); + return retval; + } + + /* Clean the new cluster */ + retval = sos_blockdev_kernel_write(block_device, + sos_fat_helper_first_sector_of_cluster(data_struct, free_cluster) * + data_struct->BootSector.BPB_BytsPerSec, + (sos_vaddr_t)clus_buf, + &clus_size); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) clus_buf); + return retval; + } else if (clus_size != data_struct->BootSector.BPB_SecPerClus * data_struct->BootSector.BPB_BytsPerSec) { + sos_kfree((sos_vaddr_t) clus_buf); + return -SOS_EIO; + } + } + + sos_kfree((sos_vaddr_t) clus_buf); + } else if (old_cluster > new_cluster) { + /* Smaller */ + for (i = 0; i < old_cluster - new_cluster; i++) { + retval = sos_fat_helper_remove_last_cluster(block_device, + data_struct, + first_cluster); + if (retval != SOS_OK) + return retval; + if (dir_entry != NULL) + dir_entry->DIR_FileSize -= clus_size; + } + } + + if (dir_entry != NULL) { + dir_entry->DIR_FileSize = new_size; + dir_entry->DIR_Attr |= ATTR_ARCHIVE; + } + + return SOS_OK; +} +/* }}} */ + +/* ******************************************************** + * File mapping stuff {{{ + */ + +inline static struct sos_fat_file * +get_fatnode_of_vr(struct sos_umem_vmm_vr * vr) +{ + struct sos_umem_vmm_mapped_resource *mr + = sos_umem_vmm_get_mapped_resource_of_vr(vr); + + return (struct sos_fat_file *)(((struct sos_fs_node *)mr->custom_data)->custom_data); +} + + +static void fat_map_ref(struct sos_umem_vmm_vr * vr) +{ + struct sos_fat_file * fatnode = get_fatnode_of_vr(vr); + sos_fs_ref_fsnode((struct sos_fs_node *) fatnode->mapres.custom_data); + fatnode->num_mappings++; +} + + +static void fat_map_unref(struct sos_umem_vmm_vr * vr) +{ + struct sos_fat_file * fatnode = get_fatnode_of_vr(vr); + + SOS_ASSERT_FATAL(fatnode->num_mappings > 0); + fatnode->num_mappings --; + + _sos_fs_unref_fsnode((struct sos_fs_node *) fatnode->mapres.custom_data); +} + + +static void fat_map_unmap(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_size_t size) +{ + sos_paging_unmap_interval(uaddr, size); +} + + +static sos_ret_t fat_map_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + struct sos_fat_file * fatnode = get_fatnode_of_vr(vr); + sos_luoffset_t offset = uaddr - sos_umem_vmm_get_start_of_vr(vr); + sos_ret_t retval; + sos_paddr_t ppage_paddr; + sos_vaddr_t page_vaddr; + sos_bool_t allocate_new_page = FALSE; + struct sos_fat_mmap_page_list *elt, *previous_elt = NULL; + + /* The region is not allowed to be resized */ + if (SOS_PAGE_ALIGN_SUP(offset) > fatnode->dir_entry.DIR_FileSize) + return -SOS_EFAULT; + + if (fatnode->list != NULL) { + /* There are already mapping on this file */ + elt = fatnode->list; + while (elt != NULL) { + if (elt->offset == sos_umem_vmm_get_offset_in_resource(vr)) { + /* page already allocated and filled */ + page_vaddr = elt->mmap_page; + break; + } else if (elt->offset > sos_umem_vmm_get_offset_in_resource(vr)) { + /* Allocate a kernel page to fill */ + page_vaddr = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if (page_vaddr == (sos_vaddr_t)NULL) + return -SOS_ENOMEM; + allocate_new_page = TRUE; + break; + } + previous_elt = elt; + elt = elt->next; + } + } else { + /* Allocate a kernel page to fill */ + page_vaddr = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if (page_vaddr == (sos_vaddr_t)NULL) + return -SOS_ENOMEM; + allocate_new_page = TRUE; + } + + struct sos_fs_node *fs_node = (struct sos_fs_node *) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + struct sos_fat_data_structure *data_struct = + (struct sos_fat_data_structure *)fs_node->fs->custom_data; + + /* Fill the mapping in the new allocated page with the data read from the file */ + if (allocate_new_page) { + sos_size_t page_size = SOS_PAGE_SIZE; + sos_ui64_t storage_location; + offset = SOS_PAGE_ALIGN_INF(offset); + retval = sos_fat_helper_storage_location_of_file(fs_node->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(&fatnode->dir_entry), + offset + sos_umem_vmm_get_offset_in_resource(vr), + &storage_location); + if (retval != SOS_OK) { + sos_kmem_vmm_free(page_vaddr); + return retval; + } + + retval = sos_fat_helper_read(fs_node->fs->device->block_device, + data_struct, + storage_location, + page_vaddr, + &page_size); + if (retval != SOS_OK) { + sos_kmem_vmm_free(page_vaddr); + return retval; + } + } + + /* Lookup physical kernel page */ + ppage_paddr = sos_paging_get_paddr(page_vaddr); + + /* Cannot access unmapped kernel pages */ + if (! ppage_paddr) { + sos_kmem_vmm_free(page_vaddr); + return -SOS_EFAULT; + } + + /* Initialize a mmap page element and insert this element in the file mmap list */ + if (allocate_new_page) { + elt = (struct sos_fat_mmap_page_list *) sos_kmalloc(sizeof(struct sos_fat_mmap_page_list), 0); + if (elt == NULL) { + sos_kmem_vmm_free(page_vaddr); + return -SOS_ENOMEM; + } + + elt->mmap_page = page_vaddr; + elt->offset = sos_umem_vmm_get_offset_in_resource(vr); + + if (fatnode->list != NULL) { + /* There is one or more element in the list */ + + if (previous_elt == NULL) { + /* There is no element before elt */ + elt->next = fatnode->list; + fatnode->list = elt; + } else { + /* elt must be insert after previous_elt */ + elt->next = previous_elt->next; + previous_elt->next = elt; + } + } else { + /* elt is the first of the list */ + elt->next = NULL; + fatnode->list = elt; + } + } + + /* Remap it in user space */ + return sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + sos_umem_vmm_get_prot_of_vr(vr)); +} + + +static sos_ret_t fat_map_sync_page(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_ui32_t flags) +{ + struct sos_fat_file * fatnode = get_fatnode_of_vr(vr); + sos_luoffset_t offset = SOS_PAGE_ALIGN_INF(uaddr - sos_umem_vmm_get_start_of_vr(vr)); + sos_ret_t retval; + int i; + sos_size_t clus_size, clus_size_write; + sos_ui64_t storage_location; + + struct sos_fs_node *fs_node = (struct sos_fs_node *) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + struct sos_fat_data_structure *data_struct = + (struct sos_fat_data_structure *)fs_node->fs->custom_data; + + clus_size = clus_size_write = data_struct->BootSector.BPB_SecPerClus * + data_struct->BootSector.BPB_BytsPerSec; + + /* Sync all cluster in a memory page */ + for (i = 0; i < SOS_PAGE_SIZE / clus_size; i++) { + /* Get the storage location of the cluster */ + retval = sos_fat_helper_storage_location_of_file(fs_node->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(&fatnode->dir_entry), + offset + sos_umem_vmm_get_offset_in_resource(vr) + (i * clus_size), + &storage_location); + if (retval != SOS_OK) { + return retval; + } + + /* Sync the cluster */ + sos_blockdev_kernel_write(fs_node->fs->device->block_device, + storage_location, + uaddr + (i * clus_size), + &clus_size_write); + if (retval != SOS_OK) { + return retval; + } else if (clus_size_write != clus_size) { + return -SOS_EIO; + } + } + + /* Unset the dirty flag */ + sos_paging_set_dirty(uaddr, FALSE); + + return SOS_OK; +} + + +static struct sos_umem_vmm_vr_ops fat_map_ops + = (struct sos_umem_vmm_vr_ops){ + .ref = fat_map_ref, + .unref = fat_map_unref, + .unmap = fat_map_unmap, + .page_in = fat_map_page_in, + .sync_page = fat_map_sync_page + }; + +static sos_ret_t fat_new_mapping(struct sos_umem_vmm_vr *vr) +{ + struct sos_fat_file * fat_file = get_fatnode_of_vr(vr); + struct sos_fs_node *fs_node = fat_file->mapres.custom_data; + sos_size_t reqsize; + sos_ret_t retval; + + reqsize = sos_umem_vmm_get_offset_in_resource(vr); + reqsize += sos_umem_vmm_get_size_of_vr(vr); + + /* Resize the region NOW when the mapping size is greater than the file size */ + if (reqsize > fat_file->dir_entry.DIR_FileSize) + { + retval = sos_fat_helper_resize(fs_node->fs->device->block_device, + fs_node->fs->custom_data, + fs_node, + SOS_PAGE_ALIGN_SUP(reqsize)); + if (SOS_OK != retval) + return retval; + } + + return sos_umem_vmm_set_ops_of_vr(vr, &fat_map_ops); +} + +/* }}} */ + +/* ******************************************************** + * Opened file operations {{{ + */ + + +static sos_ret_t +fat_duplicate_opened_file(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result) +{ + *result = (struct sos_fs_opened_file*) + sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); + if (! *result) + return -SOS_ENOMEM; + + /* Copy the parent node */ + memcpy(*result, this, sizeof(*this)); + /* Change the owner */ + (*result)->owner = for_owner; + + /* If a custom_data exist, copy it too */ + if (this->custom_data != NULL) { + struct sos_fs_dirent *dirent = (struct sos_fs_dirent*) + sos_kmalloc(sizeof(struct sos_fs_dirent), 0); + if (dirent == NULL) + return -SOS_ENOMEM; + + /* Copy and set the custom_data of the parent */ + memcpy(dirent, this->custom_data, sizeof(*dirent)); + (*result)->custom_data = dirent; + } + + return SOS_OK; +} + + +static sos_ret_t +fat_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + sos_lsoffset_t ref_offs; + struct sos_fs_node * fatnode; + + fatnode = (struct sos_fs_node*) + sos_fs_nscache_get_fs_node(this->direntry); + + if (fatnode->type != SOS_FS_NODE_REGULAR_FILE) + return -SOS_ENOSUP; + + *result_position = this->position; + switch (whence) + { + case SOS_SEEK_SET: + ref_offs = 0; + break; + + case SOS_SEEK_CUR: + ref_offs = this->position; + break; + + case SOS_SEEK_END: + ref_offs = ((struct sos_fat_directory_entry*) fatnode->custom_data)->DIR_FileSize; + break; + + default: + return -SOS_EINVAL; + } + + /* Prevent the underflow of the file position */ + if (offset < -ref_offs) + return -SOS_EINVAL; + + /* Update the file position */ + this->position = ref_offs + offset; + *result_position = this->position; + + return SOS_OK; +} + + +static sos_ret_t fat_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + struct sos_fs_node * fatnode; + struct sos_fat_directory_entry *dir_entry; + struct sos_fat_data_structure *data_struct; + sos_ui64_t storage_location; + sos_vaddr_t kern_buf; + + fatnode = (struct sos_fs_node*) + sos_fs_nscache_get_fs_node(this->direntry); + + data_struct = (struct sos_fat_data_structure*) fatnode->fs->custom_data; + + dir_entry = fatnode->custom_data; + + if (fatnode->type != SOS_FS_NODE_REGULAR_FILE) + return -SOS_ENOSUP; + + /* Cannot read after end of file */ + if (this->position >= dir_entry->DIR_FileSize) + { + *len = 0; + return SOS_OK; + } + + /* Cannot read more than the file size */ + if (this->position + *len >= dir_entry->DIR_FileSize) + *len = dir_entry->DIR_FileSize - this->position; + + retval = sos_fat_helper_storage_location_of_file( + fatnode->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(dir_entry), + this->position, + &storage_location); + if (retval != SOS_OK) { + return retval; + } + + kern_buf = sos_kmalloc(*len, 0); + if (kern_buf == (sos_vaddr_t)NULL) + return -SOS_ENOMEM; + + retval = sos_fat_helper_read(fatnode->fs->device->block_device, + data_struct, + storage_location, + kern_buf, + len); + if (retval != SOS_OK) { + sos_kfree(kern_buf); + return retval; + } + + retval = sos_memcpy_to_user(dest_buf, + (sos_vaddr_t)kern_buf, + *len); + if (retval < 0) { + sos_kfree((sos_vaddr_t)kern_buf); + return retval; + } + + this->position += retval; + *len = retval; + + sos_kfree((sos_vaddr_t)kern_buf); + + return SOS_OK; +} + + +static sos_ret_t fat_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + struct sos_fs_node * fatnode; + struct sos_fat_directory_entry *dir_entry; + struct sos_fat_data_structure *data_struct; + sos_ui64_t storage_location; + sos_vaddr_t kern_buf; + + fatnode = (struct sos_fs_node*) + sos_fs_nscache_get_fs_node(this->direntry); + + dir_entry = fatnode->custom_data; + + data_struct = fatnode->fs->custom_data; + + if (fatnode->type != SOS_FS_NODE_REGULAR_FILE) + return -SOS_ENOSUP; + + kern_buf = sos_kmalloc(*len, 0); + if (kern_buf == (sos_vaddr_t)NULL) + return -SOS_ENOMEM; + + if (this->position + *len >= dir_entry->DIR_FileSize) + { + /* Try to resize if needed */ + if ( SOS_OK != sos_fat_helper_resize( + fatnode->fs->device->block_device, + data_struct, + fatnode, + this->position + *len) ) { + *len = dir_entry->DIR_FileSize - this->position; + } else { + sos_fs_mark_dirty(this); + } + } + + retval = sos_memcpy_from_user(kern_buf, + src_buf, + *len); + if (retval < 0) + { + sos_kfree(kern_buf); + return retval; + } + + sos_size_t clus_size = data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus; + sos_ui32_t clus_pos = this->position / clus_size; + sos_ui32_t clus_len = (this->position + *len) / clus_size; + + int i; + sos_size_t current_len, saved_len, total_len = 0; + sos_ui32_t current_pos = this->position; + /* Write buffer by cluster */ + for (i = 0; i < clus_len - clus_pos + 1; i++) { + /* First and last clusters can be not full clusters */ + if (i == 0) { + /* The first cluster begining can be truncate */ + current_len = clus_size - (this->position % clus_size); + if (current_len > *len) + current_len = *len; + } else if (i == clus_len - clus_pos) { + /* The last cluster end can be truncate */ + current_len = *len - total_len; + if (current_len == 0) + break; + } else { + current_len = clus_size; + } + saved_len = current_len; + + retval = sos_fat_helper_storage_location_of_file( + fatnode->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(dir_entry), + current_pos, + &storage_location); + if (retval != SOS_OK) { + sos_kfree(kern_buf); + return retval; + } + + retval = sos_blockdev_kernel_write(fatnode->fs->device->block_device, + storage_location, + kern_buf + (total_len/sizeof(sos_vaddr_t)), + ¤t_len); + if (retval != SOS_OK) { + sos_kfree(kern_buf); + return retval; + } else if (current_len != saved_len) { + sos_kfree(kern_buf); + return -SOS_EIO; + } + + current_pos += current_len; + total_len += current_len; + } + + dir_entry->DIR_Attr |= ATTR_ARCHIVE; + this->position = current_pos; + *len = total_len; + + /* Set the dirty flag */ + sos_fs_mark_dirty(this); + + sos_kfree(kern_buf); + + return SOS_OK; +} + + +static sos_ret_t fat_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + struct sos_fs_node * fat_node; + struct sos_fat_file * fat_file; + + fat_node = (struct sos_fs_node*) + sos_fs_nscache_get_fs_node(this->direntry); + + if (fat_node->type != SOS_FS_NODE_REGULAR_FILE) + return -SOS_ENOSUP; + + fat_file = (struct sos_fat_file *) fat_node->custom_data; + + /* Set the dirty flag if file can be writen */ + if (access_rights & SOS_VM_MAP_PROT_WRITE) + sos_fs_mark_dirty(this); + + return sos_umem_vmm_map(sos_process_get_address_space(this->owner), + uaddr, size, access_rights, + flags, &fat_file->mapres, offset); +} + + +static sos_ret_t fat_readdir(struct sos_fs_opened_file *this, + struct sos_fs_dirent * result) +{ + /* For directories, "custom_data" indicates the address + of the last directory entry */ + sos_ret_t retval; + struct sos_fat_directory_entry *dir_entry, *this_dir_entry; + struct sos_fs_node * fatnode; + struct sos_fat_data_structure *data_struct; + struct sos_fs_dirent *fs_dirent; + sos_ui64_t storage_location; + sos_si64_t offset; + + fatnode = (struct sos_fs_node*) + sos_fs_nscache_get_fs_node(this->direntry); + + SOS_ASSERT_FATAL(fatnode->type == SOS_FS_NODE_DIRECTORY); + + data_struct = (struct sos_fat_data_structure *) fatnode->fs->custom_data; + + this_dir_entry = (struct sos_fat_directory_entry *) fatnode->custom_data; + + dir_entry = (struct sos_fat_directory_entry*) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0); + fs_dirent = (struct sos_fs_dirent *) this->custom_data; + + /* First acces on the directory entries ? */ + if (fs_dirent == NULL) { + fs_dirent = (struct sos_fs_dirent *) sos_kmalloc(sizeof(struct sos_fs_dirent), 0); + memset(fs_dirent, 0, sizeof(struct sos_fs_dirent)); + + if ( this_dir_entry == NULL ) { + storage_location = data_struct->FirstRootDirSecNum * + data_struct->BootSector.BPB_BytsPerSec; + } else { + storage_location = sos_fat_helper_first_sector_of_cluster( + data_struct, + GET_FIRST_CLUSTER(this_dir_entry)) * + data_struct->BootSector.BPB_BytsPerSec; + } + } else { + storage_location = fs_dirent->storage_location + sizeof(struct sos_fat_directory_entry); + } + retval = sos_fat_helper_get_next_directory_entry( + fatnode->fs->device->block_device, + data_struct, + storage_location, + &dir_entry, + &storage_location, + &offset); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + if (this->custom_data == NULL) { + sos_kfree((sos_vaddr_t) fs_dirent); + } + return retval; + } + + /* Update the result */ + memset(result, 0, sizeof(struct sos_fs_dirent)); + result->storage_location = storage_location; + result->offset_in_dirfile = this->position + offset; + if (dir_entry->DIR_Attr & ATTR_DIRECTORY) { + result->type = SOS_FS_NODE_DIRECTORY; + } else { + result->type = SOS_FS_NODE_REGULAR_FILE; + } + retval = sos_fat_helper_copy_file_name(dir_entry, result->name, &result->namelen); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + memcpy(fs_dirent, result, sizeof(struct sos_fs_dirent)); + + /* Update the custom data */ + this->position += offset; + this->custom_data = (void *) fs_dirent; + + sos_kfree((sos_vaddr_t) dir_entry); + + return SOS_OK; +} + +static struct sos_fs_ops_opened_file fat_ops_opened_file = + (struct sos_fs_ops_opened_file){ + .seek = fat_seek, + .read = fat_read, + .write = fat_write, + .mmap = fat_mmap, + /*.fcntl = fat_fcntl*/ + }; + + +static struct sos_fs_ops_opened_dir fat_ops_opened_dir = + (struct sos_fs_ops_opened_dir){ + .readdir = fat_readdir + }; +/* }}} */ + +/* ******************************************************** + * FS node operations {{{ + */ + +static sos_ret_t fat_stat_node(struct sos_fs_node * this, + struct sos_fs_stat * result) +{ + /* Establish the major/minor fields */ + result->st_rdev.device_class = 0; + result->st_rdev.device_instance = 0; + /* In FAT, nodes are only file or directory, there is no special file. + Retrieve it from the device that it is mounted + on (might not exist...) */ + struct sos_fs_node * rootdev = this->fs->device; + if (rootdev) + { + result->st_rdev.device_class = rootdev->dev_id.device_class; + result->st_rdev.device_instance = rootdev->dev_id.device_instance; + } + + result->st_type = this->type; + result->st_access_rights = this->access_rights; + result->st_nlink = this->ondisk_lnk_cnt; + /* Separate root directory and other nodes */ + if (this->custom_data != NULL) { + result->st_size = (sos_si64_t)((struct sos_fat_directory_entry *) this->custom_data)->DIR_FileSize; + result->st_storage_location = this->storage_location; + } else { + struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data; + result->st_size = 0; + result->st_storage_location = (sos_ui64_t) data_struct->FirstRootDirSecNum * data_struct->BootSector.BPB_BytsPerSec; + } + + return SOS_OK; +} + + +static sos_ret_t fat_truncate(struct sos_fs_node *this, + sos_lsoffset_t length) +{ + sos_ret_t retval; + struct sos_fat_directory_entry *dir_entry; + struct sos_fat_data_structure *data_struct; + sos_size_t new_size; + + if ( (this->type != SOS_FS_NODE_REGULAR_FILE) ) + return -SOS_ENOSUP; + + new_size = (sos_size_t) length; + dir_entry = (struct sos_fat_directory_entry*) this->custom_data; + data_struct = (struct sos_fat_data_structure*) this->fs->custom_data; + + sos_ui32_t current_clus = dir_entry->DIR_FileSize / + (data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus); + + sos_ui32_t cluster = new_size / + (data_struct->BootSector.BPB_BytsPerSec * data_struct->BootSector.BPB_SecPerClus); + + int i; + sos_ui32_t last_cluster; + if (dir_entry->DIR_FileSize >= new_size) { + for (i = 0; i < current_clus - cluster; i++) { + retval = sos_fat_helper_remove_last_cluster( + this->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(dir_entry)); + if (retval != SOS_OK) + return retval; + } + } else { + for (i = 0; i < cluster - current_clus; i++) { + retval = sos_fat_helper_add_new_cluster( + this->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(dir_entry), + &last_cluster); + if (retval != SOS_OK) + return retval; + } + } + dir_entry->DIR_FileSize = new_size; + dir_entry->DIR_Attr |= ATTR_ARCHIVE; + + return SOS_OK; +} + +/** + * This function syncronize the in-memory data on the disk. + * Datas to write on disk are directory entry of the node and, for files, mappings. + */ +static sos_ret_t fat_sync_node(struct sos_fs_node *this) +{ + sos_ret_t retval = SOS_OK; + sos_size_t clus_size, clus_size_write; + sos_ui64_t storage_location; + + if (this->custom_data != NULL && this->storage_location != 0) { + /* Sync file's mappings */ + if (this->type == SOS_FS_NODE_REGULAR_FILE) { + struct sos_fat_file *fat_file = (struct sos_fat_file *)this->custom_data; + + /* Mapping list is empty? */ + if (fat_file->list != NULL) { + struct sos_fat_mmap_page_list *elt = fat_file->list; + + struct sos_fat_data_structure *data_struct = + (struct sos_fat_data_structure *)this->fs->custom_data; + + clus_size = clus_size_write = data_struct->BootSector.BPB_SecPerClus * + data_struct->BootSector.BPB_BytsPerSec; + + /* Loop on the mappings */ + while (elt != NULL) { + unsigned int i; + /* Loop on the pages of the current mapping */ + for (i = 0; i < SOS_PAGE_SIZE / clus_size; i++) { + retval = sos_fat_helper_storage_location_of_file(this->fs->device->block_device, + data_struct, + GET_FIRST_CLUSTER(&fat_file->dir_entry), + elt->offset + (i * clus_size), + &storage_location); + if (retval != SOS_OK) { + return retval; + } + + /* Write on disk the page */ + sos_blockdev_kernel_write(this->fs->device->block_device, + storage_location, + elt->mmap_page + (i * clus_size), + &clus_size_write); + if (retval != SOS_OK) { + return retval; + } else if (clus_size_write != clus_size) { + return -SOS_EIO; + } + } + + elt = elt->next; + } + } + } + + /* Sync the directory entry of the node */ + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, + this->storage_location, + (struct sos_fat_directory_entry *) this->custom_data); + } + + return retval; +} + + +static sos_ret_t fat_chmod_node(struct sos_fs_node * this, + sos_ui32_t access_rights) +{ + struct sos_fat_directory_entry * dir_entry = + (struct sos_fat_directory_entry*)this->custom_data; + + if (dir_entry != NULL) { + if ( (access_rights & SOS_FS_READABLE) && + !(access_rights & SOS_FS_WRITABLE) ) { + /* Read-Only case */ + if (! (dir_entry->DIR_Attr & ATTR_READ_ONLY)) { + dir_entry->DIR_Attr |= ATTR_READ_ONLY; + dir_entry->DIR_Attr |= ATTR_ARCHIVE; + } + } else { + /* Read-Write case */ + if ( (access_rights & SOS_FS_READABLE) && + (dir_entry->DIR_Attr & ATTR_READ_ONLY) ) { + dir_entry->DIR_Attr &= ~ATTR_READ_ONLY; + dir_entry->DIR_Attr |= ATTR_ARCHIVE; + } + } + } + + this->access_rights = access_rights; + + return SOS_OK; +} + + +static struct sos_fs_node_ops_file fat_ops_file = + (struct sos_fs_node_ops_file){ + .truncate = fat_truncate, + .stat = fat_stat_node, + .chmod = fat_chmod_node, + .sync = fat_sync_node + }; + + +/** Callback when nothing (in particular no sos_fs_nscache_node) make + reference to the node */ +static sos_ret_t fat_node_destructor(struct sos_fs_node * this) +{ + sos_ret_t retval; + struct sos_fat_directory_entry * dir_entry = + (struct sos_fat_directory_entry*)this->custom_data; + + if (dir_entry != NULL) { + /* This callback is called only when the fsnode is not needed + anymore by any process or by the kernel. But the node must remain + stored "on disk" as long as the FS is mounted AND the node is + still "on disk" */ + if (this->ondisk_lnk_cnt <= 0 && this->storage_location != 0) + { + /* Disable the directory entry of the node */ + dir_entry->DIR_Name[0] = 0xE5; + + /* Free allocated clusters of the file */ + retval = sos_fat_helper_remove_all_cluster(this->fs->device->block_device, + this->fs->custom_data, + GET_FIRST_CLUSTER(dir_entry)); + if (retval != SOS_OK) { + return retval; + } + + /* Write modified directory entry */ + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, + this->storage_location, + dir_entry); + if (retval != SOS_OK) { + return retval; + } + } + + /* Free file's mappings elements */ + if (this->type == SOS_FS_NODE_REGULAR_FILE) { + struct sos_fat_mmap_page_list *elt_tmp, *elt = ((struct sos_fat_file *) this->custom_data)->list; + while (elt != NULL) { + sos_kmem_vmm_free(elt->mmap_page); + elt_tmp = elt->next; + sos_kfree((sos_vaddr_t)elt); + elt = elt_tmp; + } + } + sos_kfree((sos_vaddr_t) dir_entry); + } + + sos_kfree((sos_vaddr_t) this); + + return SOS_OK; +} + + +static sos_ret_t fat_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of) +{ + struct sos_fs_opened_file * of + = (struct sos_fs_opened_file*)sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); + if (! of) + return -SOS_ENOMEM; + + memset(of, 0x0, sizeof(*of)); + of->owner = owner; + of->duplicate = fat_duplicate_opened_file; + of->open_flags = open_flags; + of->ops_file = &fat_ops_opened_file; + if (this->type == SOS_FS_NODE_DIRECTORY) + of->ops_dir = & fat_ops_opened_dir; + + *result_of = of; + return SOS_OK; +} + + +static sos_ret_t fat_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of) +{ + if (of->custom_data != NULL) + sos_kfree((sos_vaddr_t)of->custom_data); + + sos_kfree((sos_vaddr_t)of); + + return SOS_OK; +} + +static sos_ret_t fat_dir_lookup(struct sos_fs_node *this, + const char * name, sos_ui16_t namelen, + sos_ui64_t * result_storage_location) +{ + sos_ret_t retval; + sos_ui64_t sector_nr, storage_location; + sos_si64_t offset; + struct sos_fat_directory_entry *this_dir_entry, *dir_entry; + struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data; + + this_dir_entry = (struct sos_fat_directory_entry*)this->custom_data; + + dir_entry = (struct sos_fat_directory_entry*) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0); + if (dir_entry == NULL) + return -SOS_ENOMEM; + + if (this_dir_entry != NULL) { + sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct, + GET_FIRST_CLUSTER(this_dir_entry)); + } else { + sector_nr = data_struct->FirstRootDirSecNum; + } + storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec; + + /* Loop on the directory entries */ + do { + retval = sos_fat_helper_get_next_directory_entry(this->fs->device->block_device, + data_struct, + storage_location, + &dir_entry, + &storage_location, + &offset); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t)dir_entry); + return retval; + } + + /* Is it the good node entry? */ + if (sos_fat_helper_same_name(name, namelen, dir_entry) == TRUE) + { + *result_storage_location = storage_location; + sos_kfree((sos_vaddr_t) dir_entry); + return SOS_OK; + } + storage_location += sizeof(struct sos_fat_directory_entry); + } while (retval != -SOS_ENOENT); + + /* No entry match */ + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_ENOENT; +} + +/** fat_link is the specific FAT VFS hard link function. + * + * The node 'node' in parameter will be link with the parent node 'this'. A + * link occurs when a hard link on 'node' is needed. + * FAT filesystem doesn't allow much than one hard link then link can only be + * execute when a file or a directory is created. + * + * This function is used in these VFS operations: + * rename, link, open (with the O_CREAT flag), creat, mkdir, symlink, mknod. + * + * FAT doesn't allow symlink, mknod operation. + * rename operation doesn't work too because of the SOS implementation of this + * operation. In SOS, rename begins by made a hard link on the old file to + * rename and then unlink the old file. Makes a hard link on the old file is + * forbidden. + * + * Others operations are allowed. This function makes many things: + * - Check is a hard link is asked. In this case returns an error. + * - Copy the file name in the directory entry structure. + * - Allocate the first cluster of the file and clean it. + * - Mark the corresponding FAT cluster entry as EOC. + * - Create the directory entries '.' and '..' if the node is a directory. + * - Set the new directory entry in its parent directory 'this'. + * - Insert the new node in the nodes' hashtable. + * - Increment the link counter of the node and its parent. + * + */ +static sos_ret_t fat_link(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen, + struct sos_fs_node * node) +{ + sos_ret_t retval; + sos_ui32_t new_cluster; + struct sos_fat_directory_entry * dir_entry; + struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure*)this->fs->custom_data; + + /* Hard link not allowed in FAT */ + if (node->storage_location != 0) { + if (node->ondisk_lnk_cnt > 0) { + if (islower(entry_name[0])) { + ((struct sos_fat_directory_entry*)node->custom_data)->DIR_Name[0] = entry_name[0] - 0x20; + } else { + ((struct sos_fat_directory_entry*)node->custom_data)->DIR_Name[0] = entry_name[0]; + } + + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, + node->storage_location, + (struct sos_fat_directory_entry *) node->custom_data); + if (retval != SOS_OK) { + return retval; + } + } + + if (node->ondisk_lnk_cnt == 0) { + node->ondisk_lnk_cnt++; + this->ondisk_lnk_cnt++; + } + + return -SOS_ENOSUP; + } + + if (node->type == SOS_FS_NODE_REGULAR_FILE) { + dir_entry = &((struct sos_fat_file *)node->custom_data)->dir_entry; + } else { + dir_entry = (struct sos_fat_directory_entry*)sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0); + if (! dir_entry) + return -SOS_ENOMEM; + } + + memset(dir_entry, 0, sizeof(struct sos_fat_directory_entry)); + + /* Copy the node name in the directory entry structure*/ + int i = 0, j, k; + + if (entry_namelen > 12) { + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_ENAMETOOLONG; + } + + /* Case of the Kanji 0xE5 in the first character */ + if ((entry_name[i] & 0xFF) == 0xE5) { + dir_entry->DIR_Name[i] = 0x05; + i++; + } + + /* Copy the name part of the node name */ + for (; i < 8; i++) { + if (entry_name[i] == '.' || i >= entry_namelen) { + break; + } + if (IS_FAT_VALID_CHAR(entry_name[i])) { + if (islower(entry_name[i])) { + dir_entry->DIR_Name[i] = entry_name[i] - 0x20; + } else { + dir_entry->DIR_Name[i] = entry_name[i]; + } + } else { + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_EINVAL; + } + } + + if (i < entry_namelen && entry_name[i] != '.') { + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_ENAMETOOLONG; + } + + /* Fill rest by space ' ' */ + for (k = i; k < 8; k++) { + dir_entry->DIR_Name[k] = ' '; + } + i++; + + /* Copy the extension part of the node name */ + for (j = 0; j < 3; j++) { + if (i >= entry_namelen) + break; + if (IS_FAT_VALID_CHAR(entry_name[i])) { + if (islower(entry_name[i])) { + dir_entry->DIR_Name[j+8] = entry_name[i] - 0x20; + } else { + dir_entry->DIR_Name[j+8] = entry_name[i]; + } + i++; + } else { + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_EINVAL; + } + } + + if (i < entry_namelen && j == 2) { + sos_kfree((sos_vaddr_t) dir_entry); + return -SOS_ENAMETOOLONG; + } + + /* Fill rest by space ' ' */ + for (k = j; k < 3; k++) { + dir_entry->DIR_Name[k+8] = ' '; + } + + /* Find the first cluster of the node */ + retval = sos_fat_helper_find_free_cluster(this->fs->device->block_device, data_struct, &new_cluster); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + /* Set EOC marker in the corresponding FAT cluster entry */ + retval = sos_fat_helper_set_cluster_value( + this->fs->device->block_device, + data_struct, + new_cluster, + data_struct->EndOfCluster); + + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + dir_entry->DIR_FstClusHI = (new_cluster>>16) & 0xFFFF; + dir_entry->DIR_FstClusLO = new_cluster & 0xFFFF; + + sos_size_t cluster_size = (data_struct->BootSector.BPB_BytsPerSec * + data_struct->BootSector.BPB_SecPerClus); + + sos_ui64_t new_cluster_storage_location = + sos_fat_helper_first_sector_of_cluster(data_struct, new_cluster) * + data_struct->BootSector.BPB_BytsPerSec; + + /* Clean the new cluster */ + sos_vaddr_t zero_buf = sos_kmalloc(cluster_size, 0); + if (((void *)zero_buf) == NULL) { + sos_kfree((sos_vaddr_t)dir_entry); + return -SOS_ENOMEM; + } + memset((void *)zero_buf, 0, cluster_size); + retval = sos_blockdev_kernel_write(this->fs->device->block_device, + new_cluster_storage_location, + zero_buf, + &cluster_size); + sos_kfree((sos_vaddr_t)zero_buf); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t)dir_entry); + return retval; + } else if (cluster_size != + (data_struct->BootSector.BPB_BytsPerSec * + data_struct->BootSector.BPB_SecPerClus)) { + sos_kfree((sos_vaddr_t)dir_entry); + return -SOS_EIO; + } + + dir_entry->DIR_Attr = ATTR_ARCHIVE; + if (! (node->access_rights & SOS_FS_WRITABLE) ) { + dir_entry->DIR_Attr |= ATTR_READ_ONLY; + } + + /* Write entry . and .. for a directory node */ + if (node->type == SOS_FS_NODE_DIRECTORY) { + dir_entry->DIR_Attr |= ATTR_DIRECTORY; + + struct sos_fat_directory_entry dot_entry; + memcpy(&dot_entry, dir_entry, sizeof(struct sos_fat_directory_entry)); + memcpy(dot_entry.DIR_Name, ". ", 11); + dot_entry.DIR_Attr = ATTR_DIRECTORY; + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, + new_cluster_storage_location, + &dot_entry); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t)dir_entry); + return retval; + } + + struct sos_fat_directory_entry *dotdot_entry = this->custom_data; + if (dotdot_entry != NULL) { + memcpy(&dot_entry, dotdot_entry, sizeof(struct sos_fat_directory_entry)); + } else { + memset(&dot_entry, 0, sizeof(struct sos_fat_directory_entry)); + } + memcpy(dot_entry.DIR_Name, ".. ", 11); + dot_entry.DIR_Attr = ATTR_DIRECTORY; + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, + new_cluster_storage_location + sizeof(struct sos_fat_directory_entry), + &dot_entry); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t)dir_entry); + return retval; + } + } + node->custom_data = dir_entry; + + sos_ui64_t sector_nr; + if (this->custom_data != NULL) { + struct sos_fat_directory_entry * this_dir_entry = this->custom_data; + sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct, + GET_FIRST_CLUSTER(this_dir_entry)); + } else { + sector_nr = data_struct->FirstRootDirSecNum; + } + + sos_hash_remove(node->fs->nodecache, node); + + /* While a storage location is used, it can't be re-used */ + struct sos_fs_node *unused_node; + node->storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec; + do { + retval = sos_fat_helper_find_free_directory_entry( + this->fs->device->block_device, + data_struct, + node->storage_location, + &node->storage_location); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + unused_node = (struct sos_fs_node *) sos_hash_lookup(this->fs->nodecache, &node->storage_location); + node->storage_location += sizeof(struct sos_fat_directory_entry); + } while (unused_node != NULL); + + node->storage_location -= sizeof(struct sos_fat_directory_entry); + + /* Set the new directory entry in its parent directory */ + retval = sos_fat_helper_set_directory_entry(this->fs->device->block_device, node->storage_location, dir_entry); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + /* Insert the node in the node hashtable */ + sos_hash_insert(node->fs->nodecache, node); + + /* Increment the link counter of the node and its parent */ + node->ondisk_lnk_cnt ++; + this->ondisk_lnk_cnt ++; + + return SOS_OK; +} + +/** + * This function search the entry in parameter and, if it present, decrements the + * ondisk_lnk_cnt counter for the node and its parent directory. + */ +static sos_ret_t +fat_unlink(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen) +{ + sos_ret_t retval; + struct sos_fat_data_structure *data_struct = (struct sos_fat_data_structure *) this->fs->custom_data; + struct sos_fat_directory_entry *result_dir_entry, *dir_entry = (struct sos_fat_directory_entry *) this->custom_data; + + result_dir_entry = (struct sos_fat_directory_entry *) sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0); + if (result_dir_entry == NULL) + return -SOS_ENOMEM; + + sos_ui64_t storage_location, sector_nr; + sos_si64_t offset; + if (dir_entry != NULL) { + sos_ui32_t cluster_nr = GET_FIRST_CLUSTER(dir_entry); + sector_nr = sos_fat_helper_first_sector_of_cluster(data_struct, cluster_nr); + } else { + sector_nr = data_struct->FirstRootDirSecNum; + } + + storage_location = sector_nr * data_struct->BootSector.BPB_BytsPerSec; + + /* Find the node entry in the current directory */ + do { + /* Read the next directory entry */ + retval = sos_fat_helper_get_next_directory_entry( + this->fs->device->block_device, + data_struct, + storage_location, + &result_dir_entry, + &storage_location, + &offset); + if (retval != SOS_OK) { + sos_kfree((sos_vaddr_t) result_dir_entry); + return retval; + } + + /* Compare name and directory entry */ + if (sos_fat_helper_same_name(entry_name, entry_namelen, result_dir_entry) == TRUE) { + this->ondisk_lnk_cnt--; + + struct sos_fs_node *node = (struct sos_fs_node *) sos_hash_lookup(this->fs->nodecache, &storage_location); + node->ondisk_lnk_cnt--; + + /* The node should be destroy on the disk? */ + if (node->ondisk_lnk_cnt <= 0) { + /* Free this directory entry */ + result_dir_entry->DIR_Name[0] = 0xE5; + + retval = sos_fat_helper_set_directory_entry( + this->fs->device->block_device, + storage_location, + result_dir_entry); + sos_kfree((sos_vaddr_t) result_dir_entry); + if (retval != SOS_OK) { + return retval; + } + } + + return SOS_OK; + } + + /* Next directory entry */ + storage_location += sizeof(struct sos_fat_directory_entry); + } while (result_dir_entry->DIR_Name[0] != 0); + + sos_kfree((sos_vaddr_t) result_dir_entry); + + return -SOS_ENOENT; +} + + +static struct sos_fs_node_ops_dir fat_ops_dir + = (struct sos_fs_node_ops_dir){ + .lookup = fat_dir_lookup, + .link = fat_link, + .unlink = fat_unlink + }; +/* }}} */ + +/* ******************************************************** + * FS instance operations {{{ + */ + +/** + * This function fetch the directory entry structure and "convert" it in the + * sos_fs_node generic VFS structure. + */ +static sos_ret_t +fat_fetch_node_from_disk(struct sos_fs_manager_instance * this, + sos_ui64_t storage_location, + struct sos_fs_node ** result) +{ + sos_ret_t retval; + struct sos_fs_node * fat_node; + struct sos_fat_data_structure *data_struct = this->custom_data; + + /* clean the result in case of error */ + *result = NULL; + + fat_node = (struct sos_fs_node *) sos_kmalloc(sizeof(struct sos_fs_node), 0); + if (fat_node == NULL) + return -SOS_ENOMEM; + + /* Initialize node */ + memset(fat_node, 0, sizeof(struct sos_fs_node)); + fat_node->fs = this; + fat_node->storage_location = storage_location; + fat_node->ondisk_lnk_cnt = 1; + fat_node->ops_file = &fat_ops_file; + fat_node->destructor = fat_node_destructor; + fat_node->new_opened_file = fat_new_opened_file; + fat_node->close_opened_file = fat_close_opened_file; + + sos_size_t dir_entry_size = sizeof(struct sos_fat_directory_entry); + struct sos_fat_directory_entry *dir_entry = + (struct sos_fat_directory_entry *) + sos_kmalloc(sizeof(struct sos_fat_directory_entry), 0); + if (dir_entry == NULL) { + sos_kfree((sos_vaddr_t)fat_node); + return -SOS_ENOMEM; + } + + /* Read the on disk node's informations */ + retval = sos_fat_helper_read(this->device->block_device, data_struct, + storage_location, (sos_vaddr_t)dir_entry, &dir_entry_size); + if (retval != SOS_OK){ + sos_kfree((sos_vaddr_t)fat_node); + sos_kfree((sos_vaddr_t)dir_entry); + return retval; + } + + /* Set type and access of the node */ + fat_node->custom_data = dir_entry; + if (dir_entry->DIR_Attr & ATTR_DIRECTORY) { + fat_node->type = SOS_FS_NODE_DIRECTORY; + fat_node->ops_dir = &fat_ops_dir; + } else { + fat_node->type = SOS_FS_NODE_REGULAR_FILE; + } + fat_node->access_rights = SOS_FS_READABLE | SOS_FS_EXECUTABLE; + if (! (dir_entry->DIR_Attr & ATTR_READ_ONLY) ) { + fat_node->access_rights |= SOS_FS_WRITABLE; + } + + /* Initialize ondisk_lnk_cnt for directory. + * For file ondisk_lnk_cnt must be 1 because + * hard link is forbidden in FAT */ + if (fat_node->type == SOS_FS_NODE_DIRECTORY) + { + fat_node->ondisk_lnk_cnt--; + sos_si64_t offset; + int i; + storage_location = sos_fat_helper_first_sector_of_cluster(data_struct, + GET_FIRST_CLUSTER(dir_entry)) * + data_struct->BootSector.BPB_BytsPerSec; + /* Skip directory '.' and '..' to the ondisk_lnk_cnt counter */ + for (i = 0; i < 2; i++) { + retval = sos_fat_helper_get_next_directory_entry(this->device->block_device, + data_struct, + storage_location, + NULL, + &storage_location, + &offset); + if (retval != SOS_OK && retval != -SOS_ENOENT) { + sos_kfree((sos_vaddr_t) fat_node); + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + + storage_location += sizeof(struct sos_fat_directory_entry); + } + + /* Increment ondisk_lnk_cnt on each directory entry */ + while (retval != -SOS_ENOENT) { + fat_node->ondisk_lnk_cnt++; + + retval = sos_fat_helper_get_next_directory_entry(this->device->block_device, + data_struct, + storage_location, + NULL, + &storage_location, + &offset); + if (retval != SOS_OK && retval != -SOS_ENOENT) { + sos_kfree((sos_vaddr_t) fat_node); + sos_kfree((sos_vaddr_t) dir_entry); + return retval; + } + storage_location += sizeof(struct sos_fat_directory_entry); + } + } + + /* Initialize mapping structure */ + if (fat_node->type == SOS_FS_NODE_REGULAR_FILE) + { + struct sos_fat_file * fat_file = (struct sos_fat_file *) sos_kmalloc(sizeof(struct sos_fat_file), 0); + if (fat_file == NULL) + return -SOS_ENOMEM; + + memcpy(&fat_file->dir_entry, dir_entry, sizeof(struct sos_fat_directory_entry)); + memset(&fat_file->mapres, 0, + sizeof(struct sos_fat_file) - sizeof(struct sos_fat_directory_entry)); + sos_kfree((sos_vaddr_t) dir_entry); + + fat_file->mapres.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + fat_file->mapres.custom_data = fat_node; + fat_file->mapres.mmap = fat_new_mapping; + fat_file->num_mappings = 0; + + fat_node->custom_data = fat_file; + } + + /* Set the sos_fs_node structure result */ + *result = fat_node; + + return SOS_OK; +} + + +/* + * This function allocate a new node for the VFS system but this node is not + * write on disk in this function. It will be write in the sync VFS operation. + */ +static sos_ret_t +fat_allocate_new_node(struct sos_fs_manager_instance * this, + sos_fs_node_type_t type, + const struct sos_process * creator, + sos_ui32_t access_rights, + sos_ui32_t flags, + struct sos_fs_node ** result) +{ + struct sos_fs_node * fat_node; + + /* clean the result in case of error */ + *result = NULL; + + /* Allow only DIRs or FILEs files */ + if ((type != SOS_FS_NODE_REGULAR_FILE) + && (type != SOS_FS_NODE_DIRECTORY)) + return -SOS_ENOSUP; + + fat_node = (struct sos_fs_node*) sos_kmalloc(sizeof(struct sos_fs_node), 0); + if (! fat_node) + return -SOS_ENOMEM; + + memset(fat_node, 0x0, sizeof(struct sos_fs_node)); + + /* Initialize "file" */ + fat_node->inmem_ref_cnt = 1; + fat_node->type = type; + fat_node->access_rights = access_rights; + fat_node->ops_file = &fat_ops_file; + fat_node->destructor = fat_node_destructor; + fat_node->new_opened_file = fat_new_opened_file; + fat_node->close_opened_file = fat_close_opened_file; + + if (type == SOS_FS_NODE_DIRECTORY) + fat_node->ops_dir = &fat_ops_dir; + + /* Initialize mapping structure */ + if (type == SOS_FS_NODE_REGULAR_FILE) + { + struct sos_fat_file * fat_file = (struct sos_fat_file *) sos_kmalloc(sizeof(struct sos_fat_file), 0); + if (fat_file == NULL) + return -SOS_ENOMEM; + + memset(fat_file, 0, sizeof(struct sos_fat_file)); + fat_file->mapres.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + fat_file->mapres.custom_data = fat_node; + fat_file->mapres.mmap = fat_new_mapping; + fat_file->num_mappings = 0; + + fat_node->custom_data = fat_file; + } + + /* Set the sos_fs_node structure result */ + *result = fat_node; + + return SOS_OK; +} + +/* + * By default the VFS is case sensitive but in the FAT file system the case + * isn't. That's why we redefine the file node compare function of the VFS. This + * function is generic: it use only VFS name and not FAT DIR_Name field. + */ +static sos_bool_t +fat_nsnode_same_name(const char *name1, sos_ui16_t namelen1, + const char *name2, sos_ui16_t namelen2) +{ + if (!name1) + SOS_ASSERT_FATAL(namelen1 == 0); + if (!name2) + SOS_ASSERT_FATAL(namelen2 == 0); + + if (namelen1 != namelen2) + return FALSE; + + if (namelen1 == 0) + return TRUE; + + int i; + for (i = 0; i < namelen1; i++) { + if (name1[i] == name2[i]) { + continue; + } else if ( (isupper(name1[i]) || islower(name1[i])) + && (isupper(name2[i]) || islower(name2[i])) ) { + if (isupper(name1[i])) { + if (name1[i] != name2[i] - 0x20) { + return FALSE; + } + } else { + if (name1[i] - 0x20 != name2[i]) { + return FALSE; + } + } + } else { + return FALSE; + } + } + + return TRUE; +} +/*}}}*/ + +/* ******************************************************** + * FS type (mount/umount) operations {{{ + */ + +static sos_ret_t fat_mount(struct sos_fs_manager_type * this, + struct sos_fs_node * device, + const char * args, + struct sos_fs_manager_instance ** mounted_fs) +{ + sos_ret_t retval; + struct sos_fs_manager_instance * fs; + struct sos_fs_node * fsnode_root; + struct sos_hash_table * hash; + sos_size_t first_sector_size; + struct sos_fat_boot_sector *sos_fat_bs; + + *mounted_fs = (struct sos_fs_manager_instance*)NULL; + + /* Create FS node hash table */ + hash = sos_hash_create("fat H", struct sos_fs_node, + sos_hash_ui64, + sos_hash_key_eq_ui64, 17, + storage_location, hlink_nodecache); + if (! hash) + return -SOS_ENOMEM; + + fs = (struct sos_fs_manager_instance*) + sos_kmalloc(sizeof(struct sos_fs_manager_instance), 0); + if (! fs) + { + sos_hash_dispose(hash); + return -SOS_ENOMEM; + } + + /* Initialize fs structure */ + memset(fs, 0x0, sizeof(struct sos_fs_manager_instance)); + fs->fs_type = this; + fs->allocate_new_node = fat_allocate_new_node; + fs->fetch_node_from_disk = fat_fetch_node_from_disk; + fs->device = device; + /*fs->statfs = fat_statfs;*/ + fs->nsnode_same_name = fat_nsnode_same_name; + fs->nodecache = hash; + + /* Read boot sector */ + first_sector_size = sizeof(struct sos_fat_boot_sector); + struct sos_fat_data_structure *data_struct = + (struct sos_fat_data_structure*) + sos_kmalloc(sizeof(struct sos_fat_data_structure), 0); + retval = sos_blockdev_kernel_read(device->block_device, + 0 /* the FAT boot sector nr */, + (sos_vaddr_t)&data_struct->BootSector, + &first_sector_size); + if ( (retval != SOS_OK) || + (first_sector_size != sizeof(struct sos_fat_boot_sector)) ) + { + sos_bochs_printf("Can't read first sector of the fat partition (only %dB read on %dB)\n", + first_sector_size, + sizeof(struct sos_fat_boot_sector)); + return retval; + } + + fs->custom_data = (void *)data_struct; + sos_fat_bs = &data_struct->BootSector; + + /* Compute FAT fs informations */ + + if (sos_fat_bs->BPB_FATSz16 != 0) + data_struct->FATSz = sos_fat_bs->BPB_FATSz16; + else + data_struct->FATSz = sos_fat_bs->BPB_specific.fat32_BPB.BPB_FATSz32; + + sos_bochs_printf("FAT size: %u\nNumber FATs %u\n", + (unsigned int)data_struct->FATSz, + (unsigned int)sos_fat_bs->BPB_NumFATs); + + if (sos_fat_bs->BPB_BytsPerSec == 0) { + sos_bochs_printf("Bytes per sector == 0 : NOT A FAT PARTITION"); + return -SOS_EIO; + } + + data_struct->RootDirSectors = + SOS_ALIGN_SUP((sos_fat_bs->BPB_RootEntCnt * 32), + sos_fat_bs->BPB_BytsPerSec) / + sos_fat_bs->BPB_BytsPerSec; + + sos_bochs_printf("Root Directory Sectors: %u\n", (unsigned int) data_struct->RootDirSectors); + + data_struct->FirstDataSector = + sos_fat_bs->BPB_RsvdSecCnt + + (sos_fat_bs->BPB_NumFATs * data_struct->FATSz) + + data_struct->RootDirSectors; + + sos_bochs_printf("First Data Sector: %u\n", (unsigned int) data_struct->FirstDataSector); + + if (sos_fat_bs->BPB_TotSec16 != 0) + data_struct->TotSec = sos_fat_bs->BPB_TotSec16; + else + data_struct->TotSec = sos_fat_bs->BPB_TotSec32; + + sos_bochs_printf("Total sector: %u\n", (unsigned int) data_struct->TotSec); + + data_struct->DataSec = data_struct->TotSec - + (sos_fat_bs->BPB_RsvdSecCnt + + (sos_fat_bs->BPB_NumFATs * data_struct->FATSz) + + data_struct->RootDirSectors); + + sos_bochs_printf("Data sector: %u\n", (unsigned int) data_struct->DataSec); + + if (sos_fat_bs->BPB_SecPerClus == 0) { + sos_bochs_printf("Sector per cluster == 0 : NOT A FAT PARTITION"); + return -SOS_EIO; + } + + data_struct->CountOfClusters = data_struct->DataSec / sos_fat_bs->BPB_SecPerClus; + + sos_bochs_printf("Count of Clusters: %u\n", (unsigned int) data_struct->CountOfClusters); + + /* Store the FAT type in the data structure. */ + /* In the following code, when it says <, it does not mean <=. Note also + * that the numbers are correct. The first number for FAT12 is 4085; the + * second number for FAT16 is 65525. These numbers and the '<' signs are not + * wrong. + * This is the one and only way that FAT type is determined. There is no + * such thing as a FAT12 volume that has more than 4084 clusters. There is + * no such thing as a FAT16 volume that has less than 4085 clusters or more + * than 65,524 clusters. There is no such thing as a FAT32 volume that has + * less than 65,525 clusters. */ + if (data_struct->CountOfClusters < FAT12_CLUSTER_LIMIT) { + data_struct->FAT_Type = FAT12; + } else if (data_struct->CountOfClusters < FAT16_CLUSTER_LIMIT) { + data_struct->FAT_Type = FAT16; + } else { + data_struct->FAT_Type = FAT32; + } + + /* Store the End Of Cluster marker of the corresponding FAT type. */ + if (data_struct->FAT_Type == FAT16) { + data_struct->EndOfCluster = FAT16_EOC; + } else { + data_struct->EndOfCluster = FAT32_EOC; + } + + sos_bochs_printf("Volume is %s (count of clusters = %u)\n", + sos_fat_type_str(data_struct->FAT_Type), + (unsigned int)data_struct->CountOfClusters); + + if (data_struct->FAT_Type != FAT32) { + data_struct->FirstRootDirSecNum = sos_fat_bs->BPB_RsvdSecCnt + (sos_fat_bs->BPB_NumFATs * sos_fat_bs->BPB_FATSz16); + } else { + data_struct->FirstRootDirSecNum = sos_fat_bs->BPB_specific.fat32_BPB.BPB_RootClus; + } + + sos_bochs_printf("First Root Directory Sector Number: %u\n", + (unsigned int) data_struct->FirstRootDirSecNum); + + fsnode_root = (struct sos_fs_node *) sos_kmalloc(sizeof(struct sos_fs_node), 0); + if (fsnode_root == NULL) + { + sos_hash_dispose(hash); + sos_kfree((sos_vaddr_t) fs); + return retval; + } + + /* Initialize the root directory structure */ + memset(fsnode_root, 0, sizeof(struct sos_fs_node)); + fsnode_root->fs = fs; + fsnode_root->storage_location = (data_struct->FirstRootDirSecNum + * data_struct->BootSector.BPB_BytsPerSec) + - sizeof(struct sos_fat_directory_entry); + fsnode_root->inmem_ref_cnt = 1; + fsnode_root->type = SOS_FS_NODE_DIRECTORY; + fsnode_root->access_rights = SOS_FS_READABLE | SOS_FS_WRITABLE | SOS_FS_EXECUTABLE; + fsnode_root->dev_id.device_class = device->dev_id.device_class; + fsnode_root->dev_id.device_instance = device->dev_id.device_instance; + fsnode_root->ops_file = &fat_ops_file; + fsnode_root->ops_dir = &fat_ops_dir; + fsnode_root->destructor = fat_node_destructor; + fsnode_root->new_opened_file = fat_new_opened_file; + fsnode_root->close_opened_file = fat_close_opened_file; + fsnode_root->custom_data = NULL; + + /* Initialize ondisk_lnk_cnt for root directory. */ + sos_si64_t offset; + sos_ui64_t storage_location = fsnode_root->storage_location; + retval = sos_fat_helper_get_next_directory_entry(device->block_device, + data_struct, + storage_location, + NULL, + &storage_location, + &offset); + if (retval != SOS_OK && retval != -SOS_ENOENT) { + sos_hash_dispose(hash); + sos_kfree((sos_vaddr_t) fs); + sos_kfree((sos_vaddr_t) fsnode_root); + return retval; + } + + /* Count the number of entries in the root directory to initialize + * the ondisk_lnk_cnt field. */ + while (retval != -SOS_ENOENT) { + fsnode_root->ondisk_lnk_cnt++; + + storage_location += sizeof(struct sos_fat_directory_entry); + retval = sos_fat_helper_get_next_directory_entry(device->block_device, + data_struct, + storage_location, + NULL, + &storage_location, + &offset); + if (retval != SOS_OK && retval != -SOS_ENOENT) { + sos_hash_dispose(hash); + sos_kfree((sos_vaddr_t) fs); + sos_kfree((sos_vaddr_t) fsnode_root); + return retval; + } + } + + retval = sos_fs_register_fs_instance(fs, fsnode_root); + sos_fs_unref_fsnode(fsnode_root); + if (SOS_OK != retval) + { + sos_hash_dispose(hash); + sos_kfree((sos_vaddr_t) fs); + sos_kfree((sos_vaddr_t) fsnode_root); + return retval; + } + + *mounted_fs = fs; + return SOS_OK; +} + + +static sos_ret_t fat_umount(struct sos_fs_manager_type * this, + struct sos_fs_manager_instance * mounted_fs) +{ + struct sos_fat_data_structure * data_struct = (struct sos_fat_data_structure*)mounted_fs->custom_data; + + sos_hash_dispose(mounted_fs->nodecache); + + sos_kfree((sos_vaddr_t)data_struct); + + sos_fs_unregister_fs_instance(mounted_fs); + sos_kfree((sos_vaddr_t)mounted_fs); + return SOS_OK; +} +/* }}} */ + +/* ******************************************************** + * FS register {{{ + */ + +/** The initialization function of the FAT filesystem */ +sos_ret_t sos_fs_fat_subsystem_setup() +{ + strzcpy(fat_type.name, "FAT", SOS_FS_MANAGER_NAME_MAXLEN); + fat_type.mount = fat_mount; + fat_type.umount = fat_umount; + + return sos_fs_register_fs_type(&fat_type); +} +/* }}} */ + diff --git a/drivers/fs_fat.h b/drivers/fs_fat.h new file mode 100644 index 0000000..f4b2b35 --- /dev/null +++ b/drivers/fs_fat.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2007 Anthoine Bourgeois + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FS_FAT_H_ +#define _SOS_FS_FAT_H_ + +#include + + +/** + * @file fs_fat.h + * + * Basic implementation of the FAT16/32 file system for SOS. + */ + + +/** + * Solely register the "FAT" FS type into the list of supported FS types + */ +sos_ret_t sos_fs_fat_subsystem_setup(void); + +#endif diff --git a/drivers/fs_virtfs.c b/drivers/fs_virtfs.c new file mode 100644 index 0000000..1fcb50c --- /dev/null +++ b/drivers/fs_virtfs.c @@ -0,0 +1,1054 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fs_virtfs.h" + +/** + * @file fs_virtfs.c + * + * All the sos_fs_nodes and their contents are stored in kernel + * memory. Thus, with "virtfs", the "disk" is actually the kernel + * memory. In a sos_fs_node, the storage_location field corresponds to + * the kernel address of the corresponding struct sos_fs_node. + * + * Basic kernel-memory virtual FS. Highly UNDER-optimized filesystem: + * each time we resize a file, a complete reallocation is done. This + * somehow complicates the file mapping, as we cannot do this anyhow + * as long as the file is mapped because the kernel address of the + * file contents must NOT change. For that reason, we forbid to resize + * a file as soon as it is mapped and the file's contents are aligned + * on a page boundary in kernel memory (use of sos_kmem_vmm_alloc + * instead of sos_kmalloc). + */ + + +/** + * A directory entry, used in virtfs_node of type "directory". This is + * as simple as a sos_fs_node with a name. + */ +struct virtfs_direntry +{ + char * name; + struct sos_fs_node * fsnode; + + /** + * Used by readdir to be resilient against creat/mkdir/unlink/rmdir + * between 2 successive readdirs. Each time a new child to a node is + * allocated, a new "creation_order" is uniquely attributed. 64bits + * should be large enough. + */ + sos_lcount_t creation_order; + + struct virtfs_direntry * sibling_prev, * sibling_next; +}; + + +/** + * Structure of a FS file or dir for virtfs. Virtfs only supports + * regular files, directories or symbolic links: + * - regular files correspond to an area in kernel memory + * - directories correspond to a list of virtfs_direntry + * + * Structural inheritance of sos_fs_node: "super" is the ancestor (in + * "OOP" terms). The "super" field might not be the first field: from + * a sos_fs_node, the corresponding virtfs_node is given by the + * sos_fs_node::custom_data field + */ +struct virtfs_node +{ + /** The ancestor */ + struct sos_fs_node super; + + union + { + /* A file */ + struct + { + /* Contents of the file */ + sos_size_t size; + void * data; + + /* Yes, the file can be mapped ! */ + struct sos_umem_vmm_mapped_resource mapres; + sos_count_t num_mappings; /**< Forbids the region to be resized + while it's being mapped, because + this woould cause the underlying + data area to be moved (because of + the implementation we chose, a new + allocation is made each time a file + is resized) */ + } file; + + /* A directory */ + struct + { + /** the children nodes are inserted in FIFO order. This is + important for the readdir function to be resilient against + mkdir/rmdir/creat/unlink */ + struct virtfs_direntry * list_entries; + + /** Used by readdir to remember the last unique "creation order" + attributed */ + sos_lcount_t top_creation_order; + } dir; + }; /* Anonymous union (gcc extension) */ + + /* The virtfs nodes are chained */ + struct virtfs_node * prev, * next; +}; + + +/** + * A virtfs FS instance + * + * Structural inheritance of sos_fs_manager_instance: "super" is the + * ancestor (in "OOP" terms). The "super" field might not be the first + * field: from a sos_fs_manager_instance, the corresponding + * virtfs_instance is given by the + * sos_fs_manager_instance::custom_data field + */ +struct virtfs_instance +{ + /** Ancestor */ + struct sos_fs_manager_instance super; + + /** For the serialization of virtfs "disk" (ie kernel memory) + accesses */ + struct sos_kmutex lock; + + /** The list of virtfs nodes "on disk" (ie in kernel memory) */ + struct virtfs_node * list_fsnodes; +}; + + +/** The description of the "Virtual FS" */ +static struct sos_fs_manager_type virtfs_type; + + +/* ********** Forward declarations */ +static sos_ret_t virtfs_mount(struct sos_fs_manager_type * this, + struct sos_fs_node * device, + const char * args, + struct sos_fs_manager_instance ** mounted_fs); + + +static sos_ret_t virtfs_umount(struct sos_fs_manager_type * this, + struct sos_fs_manager_instance * mounted_fs); + + +static sos_ret_t virtfs_new_mapping(struct sos_umem_vmm_vr *vr); + + +sos_ret_t sos_fs_virtfs_subsystem_setup() +{ + strzcpy(virtfs_type.name, "virtfs", SOS_FS_MANAGER_NAME_MAXLEN); + virtfs_type.mount = virtfs_mount; + virtfs_type.umount = virtfs_umount; + + return sos_fs_register_fs_type(& virtfs_type); +} + + +/* ******************************************************** + * Helper functions + */ + + +/* Helper function to serialize "disk" accesses */ +inline static void virtfs_lock(struct virtfs_node *a_node) +{ + struct virtfs_instance * fs = (struct virtfs_instance*)a_node->super.fs->custom_data; + sos_kmutex_lock(& fs->lock, NULL); +} + + +/* Helper function to serialize "disk" accesses */ +inline static void virtfs_unlock(struct virtfs_node *a_node) +{ + struct virtfs_instance * fs = (struct virtfs_instance*)a_node->super.fs->custom_data; + sos_kmutex_unlock(& fs->lock); +} + + +/* Helper function to resize the given virts node (ie kernel memory + reallocation) */ +static sos_ret_t virtfs_resize(struct virtfs_node *this, + sos_size_t new_size) +{ + void * new_data = NULL; + + if (this->file.size == new_size) + return SOS_OK; + + /* Don't allow to resize the region when the file is being mapped */ + if (this->file.num_mappings > 0) + return -SOS_EBUSY; + + if (new_size > 0) + { + /* Use kmem_vmm_alloc instead of kmalloc to make sure the data + WILL be page-aligned (needed by file mapping stuff) */ + sos_ui32_t npages = SOS_PAGE_ALIGN_SUP(new_size) / SOS_PAGE_SIZE; + new_data = (void*)sos_kmem_vmm_alloc(npages, + SOS_KMEM_VMM_MAP); + if (! new_data) + return -SOS_OK; + } + + /* Copy the data to its new location */ + if (this->file.size < new_size) + { + if (this->file.size > 0) + memcpy(new_data, this->file.data, this->file.size); + if (new_size > this->file.size) + memset(new_data + this->file.size, 0x0, + new_size - this->file.size); + } + else if (new_size > 0) + memcpy(new_data, this->file.data, new_size); + + if (this->file.data) + sos_kfree((sos_vaddr_t)this->file.data); + this->file.data = new_data; + this->file.size = new_size; + + return SOS_OK; +} + + +/* ******************************************************** + * Opened file operations + */ + + +static sos_ret_t +virtfs_duplicate_opened_file(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result) +{ + *result = (struct sos_fs_opened_file*) + sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); + if (! *result) + return -SOS_ENOMEM; + + memcpy(*result, this, sizeof(*this)); + (*result)->owner = for_owner; + return SOS_OK; +} + + +static sos_ret_t +virtfs_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + sos_lsoffset_t ref_offs; + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node*) + sos_fs_nscache_get_fs_node(this->direntry)->custom_data; + + if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE) + && (virtfsnode->super.type != SOS_FS_NODE_SYMLINK)) + return -SOS_ENOSUP; + + *result_position = this->position; + switch (whence) + { + case SOS_SEEK_SET: + ref_offs = 0; + break; + + case SOS_SEEK_CUR: + ref_offs = this->position; + break; + + case SOS_SEEK_END: + ref_offs = virtfsnode->file.size; + break; + + default: + return -SOS_EINVAL; + } + + if (offset < -ref_offs) + return -SOS_EINVAL; + + this->position = ref_offs + offset; + *result_position = this->position; + return SOS_OK; +} + + +static sos_ret_t virtfs_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node*) + sos_fs_nscache_get_fs_node(this->direntry)->custom_data; + + if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE) + && (virtfsnode->super.type != SOS_FS_NODE_SYMLINK)) + return -SOS_ENOSUP; + + if (this->position >= virtfsnode->file.size) + { + *len = 0; + return SOS_OK; + } + + virtfs_lock(virtfsnode); + + if (this->position + *len >= virtfsnode->file.size) + *len = virtfsnode->file.size - this->position; + + retval = sos_memcpy_to_user(dest_buf, + ((sos_vaddr_t)virtfsnode->file.data) + + this->position, + *len); + if (retval < 0) + { + virtfs_unlock(virtfsnode); + return retval; + } + + this->position += retval; + *len = retval; + + virtfs_unlock(virtfsnode); + + return SOS_OK; +} + + +static sos_ret_t virtfs_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node*) + sos_fs_nscache_get_fs_node(this->direntry)->custom_data; + + if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE) + && (virtfsnode->super.type != SOS_FS_NODE_SYMLINK)) + return -SOS_ENOSUP; + + virtfs_lock(virtfsnode); + if (this->position + *len >= virtfsnode->file.size) + { + /* Try to resize if needed */ + if (SOS_OK != virtfs_resize(virtfsnode, this->position + *len)) + *len = virtfsnode->file.size - this->position; + } + + retval = sos_memcpy_from_user(((sos_vaddr_t)virtfsnode->file.data) + + this->position, + src_buf, + *len); + if (retval < 0) + { + virtfs_unlock(virtfsnode); + return retval; + } + + this->position += retval; + *len = retval; + + virtfs_unlock(virtfsnode); + + return SOS_OK; +} + + +static sos_ret_t virtfs_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node*) + sos_fs_nscache_get_fs_node(this->direntry)->custom_data; + + if (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE) + return -SOS_ENOSUP; + + return sos_umem_vmm_map(sos_process_get_address_space(this->owner), + uaddr, size, access_rights, + flags, & virtfsnode->file.mapres, offset); +} + + +static sos_ret_t virtfs_readdir(struct sos_fs_opened_file *this, + struct sos_fs_dirent * result) +{ + /* For directories, "position" indicates the "creation_order" of the + last readdir operation, and "custom_data" indicates the address + of the last directory entry */ + + struct virtfs_direntry * direntry, * next_direntry; + struct virtfs_node * virtfsnode; + int nb; + + virtfsnode = (struct virtfs_node*) + sos_fs_nscache_get_fs_node(this->direntry)->custom_data; + next_direntry = NULL; + + /* If the "generation" of the node did not change, the next direntry + is the next in the list. We can safely do this because the list + of direntries is sorted in increasing creation_order. */ + if ((this->generation == virtfsnode->super.generation) + && (this->custom_data != NULL)) + { + direntry = (struct virtfs_direntry*)this->custom_data; + next_direntry = direntry->sibling_next; + + /* Did we go past the end of the list ? */ + if (next_direntry == list_get_head_named(virtfsnode->dir.list_entries, + sibling_prev, sibling_next)) + next_direntry = NULL; + } + else + /* Otherwise we have to lookup the next entry manually */ + { + /* Lookup the entry that has next creation_order */ + next_direntry = NULL; + list_foreach_forward_named(virtfsnode->dir.list_entries, + direntry, nb, + sibling_prev, sibling_next) + { + if (direntry->creation_order <= this->position) + continue; + + if (! next_direntry) + { + next_direntry = direntry; + continue; + } + + if (direntry->creation_order < next_direntry->creation_order) + next_direntry = direntry; + } + } + + if (! next_direntry) + { + this->custom_data = NULL; + this->position = 0; + return -SOS_ENOENT; + } + + /* Update the result */ + result->storage_location = ((sos_vaddr_t)next_direntry->fsnode); + result->offset_in_dirfile = next_direntry->creation_order; + result->type = next_direntry->fsnode->type; + result->namelen = strnlen(next_direntry->name, + SOS_FS_DIRENT_NAME_MAXLEN); + strzcpy(result->name, next_direntry->name, SOS_FS_DIRENT_NAME_MAXLEN); + + /* Update the custom data */ + this->position = next_direntry->creation_order; + this->custom_data = next_direntry; + + return SOS_OK; +} + + +static struct sos_fs_ops_opened_file virtfs_ops_opened_file = + (struct sos_fs_ops_opened_file){ + .seek = virtfs_seek, + .read = virtfs_read, + .write = virtfs_write, + .mmap = virtfs_mmap + }; + + +static struct sos_fs_ops_opened_dir virtfs_ops_opened_dir = + (struct sos_fs_ops_opened_dir){ + .readdir = virtfs_readdir + }; + + +/* ******************************************************** + * FS node operations + */ + +static sos_ret_t virtfs_stat_node(struct sos_fs_node * this, + struct sos_fs_stat * result) +{ + struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data; + + /* Establish the major/minor fields */ + result->st_rdev.device_class = 0; + result->st_rdev.device_instance = 0; + if ( (this->type == SOS_FS_NODE_DEVICE_CHAR) + || (this->type == SOS_FS_NODE_DEVICE_BLOCK) ) + { + /* If the current node is a special device: get the major/minor + directly from it */ + result->st_rdev.device_class = this->dev_id.device_class; + result->st_rdev.device_instance = this->dev_id.device_instance; + } + else + { + /* Otherwise, retrieve it from the device that it is mounted + on (might not exist...) */ + struct sos_fs_node * rootdev = this->fs->device; + if (rootdev) + { + result->st_rdev.device_class = rootdev->dev_id.device_class; + result->st_rdev.device_instance = rootdev->dev_id.device_instance; + } + } + + result->st_type = this->type; + result->st_storage_location = this->storage_location; + result->st_access_rights = this->access_rights; + result->st_nlink = this->ondisk_lnk_cnt; + if (this->type == SOS_FS_NODE_REGULAR_FILE) + result->st_size = virtfsnode->file.size; + else + result->st_size = 0; + return SOS_OK; +} + + +static sos_ret_t virtfs_truncate(struct sos_fs_node *this, + sos_lsoffset_t length) +{ + sos_ret_t retval; + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node*) this->custom_data; + + if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE) + && (virtfsnode->super.type != SOS_FS_NODE_SYMLINK)) + return -SOS_ENOSUP; + + virtfs_lock(virtfsnode); + retval = virtfs_resize(virtfsnode, length); + virtfs_unlock(virtfsnode); + + return retval; +} + + +static sos_ret_t virtfs_sync_node(struct sos_fs_node *this) +{ + /* No backing store for virtfs */ + return SOS_OK; +} + + +static sos_ret_t virtfs_chmod_node(struct sos_fs_node * this, + sos_ui32_t access_rights) +{ + this->access_rights = access_rights; + return SOS_OK; +} + + +/** Callback when nothing (in particular no sos_fs_nscache_node) make + reference to the node */ +static sos_ret_t virtfs_node_destructor(struct sos_fs_node * this) +{ + /* This callback is called only when the fsnode is not needed + anymore by any process or by the kernel. But the node must remain + stored "on disk" as long as the FS is mounted AND the node is + still "on disk" */ + if (this->ondisk_lnk_cnt <= 0) + { + struct virtfs_instance * virtfs = (struct virtfs_instance*)this->fs->custom_data; + struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data; + + list_delete(virtfs->list_fsnodes, virtfsnode); + sos_kfree((sos_vaddr_t) this->custom_data); + } + + return SOS_OK; +} + + +static struct sos_fs_node_ops_file virtfs_ops_file = + (struct sos_fs_node_ops_file){ + .truncate = virtfs_truncate, + .stat = virtfs_stat_node, + .chmod = virtfs_chmod_node, + .sync = virtfs_sync_node + }; + + +static sos_ret_t virtfs_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of) +{ + struct sos_fs_opened_file * of + = (struct sos_fs_opened_file*)sos_kmalloc(sizeof(*of), 0); + if (! of) + return -SOS_ENOMEM; + + memset(of, 0x0, sizeof(*of)); + of->owner = owner; + of->duplicate = virtfs_duplicate_opened_file; + of->open_flags = open_flags; + of->ops_file = & virtfs_ops_opened_file; + if (this->type == SOS_FS_NODE_DIRECTORY) + of->ops_dir = & virtfs_ops_opened_dir; + + *result_of = of; + return SOS_OK; +} + + +static sos_ret_t virtfs_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of) +{ + sos_kfree((sos_vaddr_t)of); + return SOS_OK; +} + + +static sos_ret_t virtfs_symlink_expand(struct sos_fs_node *this, + char const ** target, + sos_size_t * target_len) +{ + struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data; + + *target = virtfsnode->file.data; + *target_len = virtfsnode->file.size; + + return SOS_OK; +} + + +static struct sos_fs_node_ops_symlink virtfs_ops_symlink + = (struct sos_fs_node_ops_symlink){ + .expand = virtfs_symlink_expand + }; + + +static sos_ret_t virtfs_dir_lookup(struct sos_fs_node *this, + const char * name, sos_ui16_t namelen, + sos_ui64_t * result_storage_location) +{ + struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data; + struct virtfs_direntry * direntry; + int nbentries; + + list_foreach_forward_named(virtfsnode->dir.list_entries, + direntry, nbentries, + sibling_prev, sibling_next) + { + if (!memcmp(name, direntry->name, namelen) && !direntry->name[namelen]) + { + *result_storage_location = direntry->fsnode->storage_location; + return SOS_OK; + } + } + + return -SOS_ENOENT; +} + + +static sos_ret_t virtfs_link(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen, + struct sos_fs_node * node) +{ + struct virtfs_node * parent = (struct virtfs_node*)this->custom_data; + struct virtfs_direntry * direntry; + + direntry = (struct virtfs_direntry*)sos_kmalloc(sizeof(*direntry), 0); + if (! direntry) + return -SOS_ENOMEM; + + direntry->name = (char*)sos_kmalloc(entry_namelen + 1, 0); + if (! direntry->name) + { + sos_kfree((sos_vaddr_t)direntry->name); + return -SOS_ENOMEM; + } + + memcpy(direntry->name, entry_name, entry_namelen); + direntry->name[entry_namelen] = '\0'; + + direntry->fsnode = node; + node->ondisk_lnk_cnt ++; + this->ondisk_lnk_cnt ++; + list_add_tail_named(parent->dir.list_entries, direntry, + sibling_prev, sibling_next); + + /* Update the index of the new entry in order for the next readdirs + to fetch this new entry */ + parent->dir.top_creation_order ++; + direntry->creation_order = parent->dir.top_creation_order; + + return SOS_OK; +} + + +static sos_ret_t +virtfs_unlink(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen) +{ + struct virtfs_node * parent = (struct virtfs_node*)this->custom_data; + struct virtfs_direntry * direntry; + int nbentries; + + list_foreach_forward_named(parent->dir.list_entries, + direntry, nbentries, + sibling_prev, sibling_next) + { + if (!memcmp(entry_name, direntry->name, entry_namelen) + && !direntry->name[entry_namelen]) + { + list_delete_named(parent->dir.list_entries, direntry, + sibling_prev, sibling_next); + direntry->fsnode->ondisk_lnk_cnt --; + this->ondisk_lnk_cnt --; + sos_kfree((sos_vaddr_t)direntry); + return SOS_OK; + } + } + + return -SOS_ENOENT; +} + + +static struct sos_fs_node_ops_dir virtfs_ops_dir + = (struct sos_fs_node_ops_dir){ + .lookup = virtfs_dir_lookup, + .link = virtfs_link, + .unlink = virtfs_unlink + }; + + +/* ******************************************************** + * FS instance operations + */ + + +/** Simulate the access to a node located on disk. In virtfs, the + "disk" is directly the kernel memory, so the + "sos_fs_node::storage_location field corresponds to a kernel + address */ +static sos_ret_t +virtfs_fetch_node_from_disk(struct sos_fs_manager_instance * this, + sos_ui64_t storage_location, + struct sos_fs_node ** result) +{ + /* The "disk" is simply the ram */ + struct virtfs_node * virtfsnode; + + virtfsnode = (struct virtfs_node *)((sos_vaddr_t)storage_location); + *result = & virtfsnode->super; + + return SOS_OK; +} + + +static sos_ret_t +virtfs_allocate_new_node(struct sos_fs_manager_instance * this, + sos_fs_node_type_t type, + const struct sos_process * creator, + sos_ui32_t access_rights, + sos_ui32_t flags, + struct sos_fs_node ** result) +{ + struct virtfs_node * virtfsnode; + + /* Allow only DIRs, FILEs or special device files */ + if ((type != SOS_FS_NODE_REGULAR_FILE) + && (type != SOS_FS_NODE_SYMLINK) + && (type != SOS_FS_NODE_DIRECTORY) + && (type != SOS_FS_NODE_DEVICE_CHAR) + && (type != SOS_FS_NODE_DEVICE_BLOCK)) + return -SOS_ENOSUP; + + virtfsnode = (struct virtfs_node*) sos_kmalloc(sizeof(*virtfsnode), 0); + if (! virtfsnode) + return -SOS_ENOMEM; + + memset(virtfsnode, 0x0, sizeof(*virtfsnode)); + *result = & virtfsnode->super; + + /* Initialize "file" */ + (*result)->inmem_ref_cnt = 1; + (*result)->custom_data = virtfsnode; + (*result)->storage_location = (sos_ui64_t)((sos_vaddr_t)virtfsnode); + (*result)->type = type; + (*result)->access_rights = access_rights; + (*result)->destructor = virtfs_node_destructor; + (*result)->ops_file = & virtfs_ops_file; + + /* The "file" functions are defined by the FS code only for + non-special device files */ + if ((type != SOS_FS_NODE_DEVICE_CHAR) + && (type != SOS_FS_NODE_DEVICE_BLOCK)) + { + (*result)->new_opened_file = virtfs_new_opened_file; + (*result)->close_opened_file = virtfs_close_opened_file; + } + + if (type == SOS_FS_NODE_SYMLINK) + (*result)->ops_symlink = & virtfs_ops_symlink; + else if (type == SOS_FS_NODE_DIRECTORY) + (*result)->ops_dir = & virtfs_ops_dir; + + /* Initialize mapping structure */ + if (type == SOS_FS_NODE_REGULAR_FILE) + { + virtfsnode->file.mapres.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + virtfsnode->file.mapres.custom_data = virtfsnode; + virtfsnode->file.mapres.mmap = virtfs_new_mapping; + } + + list_add_tail(((struct virtfs_instance*)this->custom_data)->list_fsnodes, + virtfsnode); + + return SOS_OK; +} + + +/* ******************************************************** + * FS type (mount/umount) operations + */ + +static sos_ret_t virtfs_mount(struct sos_fs_manager_type * this, + struct sos_fs_node * device, + const char * args, + struct sos_fs_manager_instance ** mounted_fs) +{ + sos_ret_t retval; + struct virtfs_instance * fs; + struct sos_fs_node * fsnode_root; + struct sos_hash_table * hash; + + *mounted_fs = (struct sos_fs_manager_instance*)NULL; + + /* Create FS node hash table */ + hash = sos_hash_create("virtfs H", struct sos_fs_node, + sos_hash_ui64, + sos_hash_key_eq_ui64, 17, + storage_location, hlink_nodecache); + if (! hash) + return -SOS_ENOMEM; + + fs = (struct virtfs_instance*) + sos_kmalloc(sizeof(struct virtfs_instance), 0); + if (! fs) + { + sos_hash_dispose(hash); + return -SOS_ENOMEM; + } + + memset(fs, 0x0, sizeof(struct virtfs_instance)); + retval = sos_kmutex_init(& fs->lock, "virtfs", SOS_KWQ_ORDER_FIFO); + if (SOS_OK != retval) + { + sos_hash_dispose(hash); + sos_kfree((sos_vaddr_t) fs); + return retval; + } + fs->super.custom_data = fs; + fs->super.fs_type = this; + fs->super.allocate_new_node = virtfs_allocate_new_node; + fs->super.fetch_node_from_disk = virtfs_fetch_node_from_disk; + fs->super.nodecache = hash; + + retval = virtfs_allocate_new_node(& fs->super, SOS_FS_NODE_DIRECTORY, + NULL, + SOS_FS_READABLE | SOS_FS_WRITABLE + | SOS_FS_EXECUTABLE, + 0, + & fsnode_root); + if (SOS_OK != retval) + { + sos_hash_dispose(hash); + sos_kmutex_dispose(& fs->lock); + sos_kfree((sos_vaddr_t) fs); + return retval; + } + + retval = sos_fs_register_fs_instance(& fs->super, fsnode_root); + sos_fs_unref_fsnode(fsnode_root); + if (SOS_OK != retval) + { + sos_hash_dispose(hash); + sos_kmutex_dispose(& fs->lock); + sos_kfree((sos_vaddr_t) fs); + return retval; + } + + *mounted_fs = & fs->super; + return SOS_OK; +} + + +static sos_ret_t virtfs_umount(struct sos_fs_manager_type * this, + struct sos_fs_manager_instance * mounted_fs) +{ + struct virtfs_instance * virtfs = (struct virtfs_instance*)mounted_fs->custom_data; + + sos_hash_dispose(virtfs->super.nodecache); + while (! list_is_empty(virtfs->list_fsnodes)) + { + struct virtfs_node * virtfsnode = list_pop_head(virtfs->list_fsnodes); + + if (virtfsnode->super.type == SOS_FS_NODE_REGULAR_FILE) + { + if (virtfsnode->file.size > 0) + sos_kfree((sos_vaddr_t) virtfsnode->file.data); + } + else if (virtfsnode->super.type == SOS_FS_NODE_DIRECTORY) + { + while (! list_is_empty_named(virtfsnode->dir.list_entries, + sibling_prev, sibling_next)) + { + struct virtfs_direntry * direntry + = list_pop_head_named(virtfsnode->dir.list_entries, + sibling_prev, sibling_next); + + sos_kfree((sos_vaddr_t)direntry->name); + sos_kfree((sos_vaddr_t)direntry); + } + } + + sos_kfree((sos_vaddr_t)virtfsnode); + } + + sos_fs_unregister_fs_instance(& virtfs->super); + sos_kmutex_dispose(& virtfs->lock); + sos_kfree((sos_vaddr_t)virtfs); + return SOS_OK; +} + + +/* ******************************************************** + * File mapping stuff + */ +inline static struct virtfs_node * +get_virtfsnode_of_vr(struct sos_umem_vmm_vr * vr) +{ + struct sos_umem_vmm_mapped_resource *mr + = sos_umem_vmm_get_mapped_resource_of_vr(vr); + + return (struct virtfs_node *)mr->custom_data; +} + + +static void virtfs_map_ref(struct sos_umem_vmm_vr * vr) +{ + struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr); + sos_fs_ref_fsnode(& virtfsnode->super); + virtfsnode->file.num_mappings ++; +} + + +static void virtfs_map_unref(struct sos_umem_vmm_vr * vr) +{ + struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr); + + SOS_ASSERT_FATAL(virtfsnode->file.num_mappings > 0); + virtfsnode->file.num_mappings --; + + _sos_fs_unref_fsnode(& virtfsnode->super); +} + + +static sos_ret_t virtfs_map_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr); + sos_luoffset_t offset = uaddr - sos_umem_vmm_get_start_of_vr(vr); + sos_ret_t retval = SOS_OK; + sos_paddr_t ppage_paddr; + + /* The region is not allowed to be resized */ + if (SOS_PAGE_ALIGN_SUP(offset) > virtfsnode->file.size) + return -SOS_EFAULT; + + /* Lookup physical kernel page */ + ppage_paddr = sos_paging_get_paddr(SOS_PAGE_ALIGN_INF(virtfsnode->file.data + + offset)); + + /* Cannot access unmapped kernel pages */ + if (! ppage_paddr) + return -SOS_EFAULT; + + /* Remap it in user space */ + retval = sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + sos_umem_vmm_get_prot_of_vr(vr)); + + return retval; +} + + +static struct sos_umem_vmm_vr_ops virtfs_map_ops + = (struct sos_umem_vmm_vr_ops){ + .ref = virtfs_map_ref, + .unref = virtfs_map_unref, + .page_in = virtfs_map_page_in + }; + +static sos_ret_t virtfs_new_mapping(struct sos_umem_vmm_vr *vr) +{ + struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr); + sos_size_t reqsize; + sos_ret_t retval; + + reqsize = sos_umem_vmm_get_offset_in_resource(vr); + reqsize += sos_umem_vmm_get_size_of_vr(vr); + + /* Resize the region NOW */ + if (reqsize > virtfsnode->file.size) + { + retval = virtfs_resize(virtfsnode, + SOS_PAGE_ALIGN_SUP(reqsize)); + if (SOS_OK != retval) + return retval; + } + + return sos_umem_vmm_set_ops_of_vr(vr, &virtfs_map_ops); +} diff --git a/drivers/fs_virtfs.h b/drivers/fs_virtfs.h new file mode 100644 index 0000000..b584ec4 --- /dev/null +++ b/drivers/fs_virtfs.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FS_VIRTFS_H_ +#define _SOS_FS_VIRTFS_H_ + +#include + + +/** + * @file fs_virtfs.h + * + * Fake test file system. All the sos_fs_nodes and their contents are + * stored in kernel memory. Thus, with "virtfs", the "disk" is + * actually the kernel memory. In a sos_fs_node, the storage_location + * field corresponds to the kernel address of the corresponding struct + * sos_fs_node. + */ + + +/** + * Solely register the "virtfs" FS type into the list of supported FS types + */ +sos_ret_t sos_fs_virtfs_subsystem_setup(void); + +#endif diff --git a/drivers/ide.c b/drivers/ide.c new file mode 100644 index 0000000..204f83c --- /dev/null +++ b/drivers/ide.c @@ -0,0 +1,785 @@ +/* Copyright (C) 2005 Thomas Petazzoni, David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * @file ide.c + * + * Basic PIO IDE implementation based on the ATA standards + * http://www.t13.org/ + */ + + +/** + * Busy-wait for a given time + * + * @param delay Delay to wait + * + * @todo Implement a calibration routine + */ +static void udelay(int delay) +{ + int i; + + for (i = 0; i < (delay * 1000) ; i++) + { + i++; i--; + } +} + +/* + * Each IDE controller is controlled through a set of 9 I/O ports, + * starting from a base address, which is different for each IDE + * controller. On a standard PC with two onboard IDE controllers, the + * I/O registers of the first controller start at 0x1F0 + * (IDE_CONTROLLER_0_BASE), the I/O registers of the second controller + * start at 0x170 (IDE_CONTROLLER_1_BASE). These I/O registers are + * 8-bits wide, and can be read using the inb() macro, and written + * using the outb() macro. + * + * The first controller is generally known as "primary" controller, + * and the second one is know as "secondary" controller. Each of them + * can handle at most two devices : the "master" device and the + * "slave" device. + * + * The registers can be used to issue commands and send data to the + * IDE controller, for example to identify a device, to read or write + * a device. Then are different ways of transmitting data to the IDE + * controller : + * + * - program the IDE controller, send the data, and wait for the + * completion of the request. This method is called "polling". + * + * - program the IDE controller, send the data, block the current + * process and do something else. The completion of the request will + * be signaled asynchronously by an interrupt (IRQ), which will + * allow to restart the sleeping process. + * + * - program the IDE controller and the DMA controller. There's no + * need to transfer the data to the IDE controller : the DMA + * controller will do it. This allows to use the CPU to do something + * useful during the transfer of data between the main memory and + * the IDE controller. + * + * In this driver, we use the two first methods. The polling method is + * used to fetch the identification of the devices, while the IRQ + * method is used to read and write to devices. + */ + +#define IDE_CONTROLLER_0_BASE 0x1F0 +#define IDE_CONTROLLER_1_BASE 0x170 + +#define IDE_CONTROLLER_0_IRQ 14 +#define IDE_CONTROLLER_1_IRQ 15 + +/* + * All defines below are relative to the base address of the I/O + * registers (IDE_CONTROLLER_0_BASE and IDE_CONTROLLER_1_BASE) + */ + +/** + * Read/write register that allows to transfer the data to be written + * or to fetch the read data from the IDE controller. + */ +#define ATA_DATA 0x00 + +/** + * Read only register that gives information about errors that occured + * during operation + */ +#define ATA_ERROR 0x01 + +/** + * Write only register to set precompensation. It is in fact the same + * register as the ATA_ERROR register. It simply has a different + * behaviour when reading and writing it. + */ +#define ATA_PRECOMP 0x01 + +/** + * Write only register used to set the count of sectors on which the + * request applies. + */ +#define ATA_SECTOR_COUNT 0x02 + +/** + * Write only register used to set the number of the starting sector + * of the request. + */ +#define ATA_SECTOR_NUMBER 0x03 + +/** + * Write only register used to set the 8 lower bits of the starting + * cylinder number of the request + */ +#define ATA_CYL_LSB 0x04 + +/** + * Write only register used to set the 8 higher bits of the starting + * cylinder number of the request + */ +#define ATA_CYL_MSB 0x05 + +/** + * Write only register that allows to select whether the LBA mode + * should be used, and to select whether the request concerns the + * slave or master device. + */ +#define ATA_DRIVE 0x06 +#define ATA_D_IBM 0xa0 /* bits that must be set */ +#define ATA_D_LBA 0x40 /* use LBA ? */ +#define ATA_D_MASTER 0x00 /* select master */ +#define ATA_D_SLAVE 0x10 /* select slave */ + +/** + * Read only register that contains the status of the controller. Each + * bit of this register as a different signification. + */ +#define ATA_STATUS 0x07 +#define ATA_S_ERROR 0x01 /* error */ +#define ATA_S_INDEX 0x02 /* index */ +#define ATA_S_CORR 0x04 /* data corrected */ +#define ATA_S_DRQ 0x08 /* data request */ +#define ATA_S_DSC 0x10 /* drive Seek Completed */ +#define ATA_S_DWF 0x20 /* drive write fault */ +#define ATA_S_DRDY 0x40 /* drive ready */ +#define ATA_S_BSY 0x80 /* busy */ + +/** + * Write only register used to set the command that the IDE controller + * should process. In our driver, only ATA_C_ATA_IDENTIFY, ATA_C_READ + * and ATA_C_WRITE are used. + */ +#define ATA_CMD 0x07 +#define ATA_C_ATA_IDENTIFY 0xec /* get ATA params */ +#define ATA_C_ATAPI_IDENTIFY 0xa1 /* get ATAPI params*/ +#define ATA_C_READ 0x20 /* read command */ +#define ATA_C_WRITE 0x30 /* write command */ +#define ATA_C_READ_MULTI 0xc4 /* read multi command */ +#define ATA_C_WRITE_MULTI 0xc5 /* write multi command */ +#define ATA_C_SET_MULTI 0xc6 /* set multi size command */ +#define ATA_C_PACKET_CMD 0xa0 /* set multi size command */ + +/** + * Read register that contains more information about the status of + * the controller + */ +#define ATA_ALTPORT 0x206 /* (R) alternate + Status register */ + +/** + * Write only register that allows to control the controller + */ +#define ATA_DEVICE_CONTROL 0x206 /* (W) device control + register */ +#define ATA_A_nIEN 0x02 /* disable interrupts */ +#define ATA_A_RESET 0x04 /* RESET controller */ +#define ATA_A_4BIT 0x08 /* 4 head bits */ + +/** Magic numbers used to detect ATAPI devices */ +#define ATAPI_MAGIC_LSB 0x14 +#define ATAPI_MAGIC_MSB 0xeb + +/* This structure describe the informations returned by the Identify + Device command (imposed by the ATA standard) */ +struct ide_device_info +{ + sos_ui16_t general_config_info; /* 0 */ + sos_ui16_t nb_logical_cylinders; /* 1 */ + sos_ui16_t reserved1; /* 2 */ + sos_ui16_t nb_logical_heads; /* 3 */ + sos_ui16_t unformatted_bytes_track; /* 4 */ + sos_ui16_t unformatted_bytes_sector; /* 5 */ + sos_ui16_t nb_logical_sectors; /* 6 */ + sos_ui16_t vendor1[3]; /* 7-9 */ + sos_ui8_t serial_number[20]; /* 10-19 */ + sos_ui16_t buffer_type; /* 20 */ + sos_ui16_t buffer_size; /* 21 */ + sos_ui16_t ecc_bytes; /* 22 */ + sos_ui8_t firmware_revision[8]; /* 23-26 */ + sos_ui8_t model_number[40]; /* 27-46 */ + sos_ui8_t max_multisect; /* 47 */ + sos_ui8_t vendor2; + sos_ui16_t dword_io; /* 48 */ + sos_ui8_t vendor3; /* 49 */ + sos_ui8_t capabilities; + sos_ui16_t reserved2; /* 50 */ + sos_ui8_t vendor4; /* 51 */ + sos_ui8_t pio_trans_mode; + sos_ui8_t vendor5; /* 52 */ + sos_ui8_t dma_trans_mode; + sos_ui16_t fields_valid; /* 53 */ + sos_ui16_t cur_logical_cylinders; /* 54 */ + sos_ui16_t cur_logical_heads; /* 55 */ + sos_ui16_t cur_logical_sectors; /* 56 */ + sos_ui16_t capacity1; /* 57 */ + sos_ui16_t capacity0; /* 58 */ + sos_ui8_t multsect; /* 59 */ + sos_ui8_t multsect_valid; + sos_ui32_t lba_capacity; /* 60-61 */ + sos_ui16_t dma_1word; /* 62 */ + sos_ui16_t dma_multiword; /* 63 */ + sos_ui16_t pio_modes; /* 64 */ + sos_ui16_t min_mword_dma; /* 65 */ + sos_ui16_t recommended_mword_dma; /* 66 */ + sos_ui16_t min_pio_cycle_time; /* 67 */ + sos_ui16_t min_pio_cycle_time_iordy; /* 68 */ + sos_ui16_t reserved3[11]; /* 69-79 */ + sos_ui16_t major_version; /* 80 */ + sos_ui16_t minor_version; /* 81 */ + sos_ui16_t command_sets1; /* 82 */ + sos_ui16_t command_sets2; /* 83 dixit lk : b14 (smart enabled) */ + sos_ui16_t reserved4[4]; /* 84-87 */ + sos_ui16_t dma_ultra; /* 88 dixit lk */ + sos_ui16_t reserved5[37]; /* 89-125 */ + sos_ui16_t last_lun; /* 126 */ + sos_ui16_t reserved6; /* 127 */ + sos_ui16_t security; /* 128 */ + sos_ui16_t reserved7[127]; +} __attribute__((packed)); + +#define MAX_IDE_CONTROLLERS 2 +#define MAX_IDE_DEVICES 2 + +#define IDE_BLK_SIZE 512 + +#define IDE_DEVICE(ctrl,device) (((ctrl) * 2) + (device)) +#define IDE_MINOR(ctrl,device) (IDE_DEVICE(ctrl,device)*16) + +typedef enum { IDE_DEVICE_NONE, + IDE_DEVICE_HARDDISK, + IDE_DEVICE_CDROM } ide_device_type_t; + +struct ide_controller; +struct ide_device { + int id; + ide_device_type_t type; + enum { IDE_DEVICE_MASTER, IDE_DEVICE_SLAVE } position; + int cyls; + int heads; + int sectors; + int blocks; + sos_bool_t support_lba; + struct ide_controller *ctrl; +}; + +struct ide_controller { + int id; + int ioaddr; + int irq; + enum { IDE_CTRL_NOT_PRESENT, IDE_CTRL_PRESENT } state; + struct ide_device devices[MAX_IDE_DEVICES]; + struct sos_kmutex mutex; + + struct sos_ksema ack_io_sem; +}; + +struct ide_controller ide_controllers[MAX_IDE_CONTROLLERS] = { + { + .id = 0, + .ioaddr = IDE_CONTROLLER_0_BASE, + .irq = IDE_CONTROLLER_0_IRQ, + .state = IDE_CTRL_NOT_PRESENT, + }, + { + .id = 1, + .ioaddr = IDE_CONTROLLER_1_BASE, + .irq = IDE_CONTROLLER_1_IRQ, + .state = IDE_CTRL_NOT_PRESENT + } +}; + +void ide_irq_handler (int irq_level) +{ + int i; + struct ide_controller *ctrl = NULL; + + for (i = 0; i < MAX_IDE_CONTROLLERS ; i++) + { + if (ide_controllers[i].irq == irq_level) + { + ctrl = & ide_controllers[i]; + break; + } + } + + SOS_ASSERT_FATAL (ctrl != NULL); + + sos_ksema_up (& ctrl->ack_io_sem); +} + +static int ide_get_device_info (struct ide_device *dev) +{ + int devselect; + sos_ui16_t buffer[256]; + struct ide_device_info *info; + int timeout, i; + int status; + + SOS_ASSERT_FATAL (dev->type == IDE_DEVICE_HARDDISK); + + if (dev->position == IDE_DEVICE_MASTER) + devselect = ATA_D_MASTER; + else + devselect = ATA_D_SLAVE; + + /* Ask the controller to NOT send interrupts to acknowledge + commands */ + outb(ATA_A_nIEN | ATA_A_4BIT, dev->ctrl->ioaddr + ATA_DEVICE_CONTROL); + udelay(1); + + /* Select the device (master or slave) */ + outb(ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); + + /* Send the IDENTIFY command */ + outb(ATA_C_ATA_IDENTIFY, dev->ctrl->ioaddr + ATA_CMD); + + /* Wait for command completion (wait while busy bit is set) */ + for(timeout = 0; timeout < 30000; timeout++) + { + status = inb(dev->ctrl->ioaddr + ATA_STATUS); + if(!(status & ATA_S_BSY)) + break; + + udelay(1); + } + + /* DRQ bit indicates that data is ready to be read. If it is not set + after an IDENTIFY command, there is a problem */ + if(! (status & ATA_S_DRQ)) + { + return SOS_EFATAL; + } + + /* Read data from the controller buffer to a temporary buffer */ + for(i = 0; i < 256; i++) + buffer[i] = inw(dev->ctrl->ioaddr + ATA_DATA); + + /* The buffer contains an information structure, defined by ATA + specification. To ease its access, we use the previously defined + ide_device_info structure. */ + info = (struct ide_device_info *) buffer; + + /* Fetch intersting informations from the structure */ + dev->heads = info->nb_logical_heads; + dev->cyls = info->nb_logical_cylinders; + dev->sectors = info->nb_logical_sectors; + + dev->support_lba = FALSE; + + /* Determines if device supports LBA. The method is a bit ugly, but + there's no other way */ + if (info->capabilities & (1 << 1) + && info->major_version + && (info->fields_valid & 1) + && (info->lba_capacity & 1)) + dev->support_lba = TRUE; + + /* Determines the capacity of the device */ + if (dev->heads == 16 && + dev->sectors == 63 && + dev->cyls == 16383) + { + if (dev->support_lba) + dev->blocks = info->lba_capacity; + else + return SOS_EFATAL; + } + else + dev->blocks = dev->cyls * dev->sectors * dev->heads; + + return SOS_OK; +} + +static ide_device_type_t ide_probe_device (struct ide_device *dev) +{ + int status; + int devselect; + + if (dev->position == IDE_DEVICE_MASTER) + devselect = ATA_D_MASTER; + else + devselect = ATA_D_SLAVE; + + /* Select the given device */ + outb (ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); + + /* Read the status of the device */ + status = inb(dev->ctrl->ioaddr + ATA_STATUS); + + /* If status indicates a busy device, it means that there's no + device */ + if (status & ATA_S_BSY) + return IDE_DEVICE_NONE; + + /* If status indicates that drive is ready and drive has complete + seeking, then we've got an hard drive */ + if (status & (ATA_S_DRDY | ATA_S_DSC)) + return IDE_DEVICE_HARDDISK; + + /* Detect CD-ROM drives by reading the cylinder low byte and + cylinder high byte, and check if they match magic values */ + if(inb(dev->ctrl->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB && + inb(dev->ctrl->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB) + return IDE_DEVICE_CDROM; + + return IDE_DEVICE_NONE; +} + +static sos_ret_t ide_probe_controller(struct ide_controller *ctrl) +{ + sos_bool_t hasdevice = FALSE; + + sos_kmutex_init (& ctrl->mutex, "ide-mutex", SOS_KWQ_ORDER_FIFO); + sos_ksema_init (& ctrl->ack_io_sem, "ide-sem", 0, SOS_KWQ_ORDER_FIFO); + + /* Master */ + ctrl->devices[0].id = 0; + ctrl->devices[0].position = IDE_DEVICE_MASTER; + ctrl->devices[0].ctrl = ctrl; + ctrl->devices[0].type = ide_probe_device (& ctrl->devices[0]); + + if (ctrl->devices[0].type == IDE_DEVICE_HARDDISK) + { + ide_get_device_info (& ctrl->devices[0]); + hasdevice = TRUE; + } + + /* Slave */ + ctrl->devices[1].id = 1; + ctrl->devices[1].position = IDE_DEVICE_SLAVE; + ctrl->devices[1].ctrl = ctrl; + ctrl->devices[1].type = ide_probe_device (& ctrl->devices[1]); + + if (ctrl->devices[1].type == IDE_DEVICE_HARDDISK) + { + ide_get_device_info (& ctrl->devices[1]); + hasdevice = TRUE; + } + + if (hasdevice) + sos_irq_set_routine (ctrl->irq, ide_irq_handler); + + return SOS_OK; +} + + +static sos_ret_t ide_io_operation (struct ide_device *dev, + sos_vaddr_t buf, int block, + sos_bool_t iswrite) +{ + sos_ui8_t cyl_lo, cyl_hi, sect, head, status; + int devselect, i; + + SOS_ASSERT_FATAL (dev->type == IDE_DEVICE_HARDDISK); + + if (block > dev->blocks) + return -SOS_EFATAL; + + if (dev->position == IDE_DEVICE_MASTER) + devselect = ATA_D_MASTER; + else + devselect = ATA_D_SLAVE; + + /* Compute the position of the block in the device in terms of + cylinders, sectors and heads. As cylinders must be sent in two + separate parts, we compute it that way. */ + if (dev->support_lba) + { + sect = (block & 0xff); + cyl_lo = (block >> 8) & 0xff; + cyl_hi = (block >> 16) & 0xff; + head = ((block >> 24) & 0x7) | 0x40; + } + else + { + int cylinder = block / + (dev->heads * dev->sectors); + int temp = block % + (dev->heads * dev->sectors); + cyl_lo = cylinder & 0xff; + cyl_hi = (cylinder >> 8) & 0xff; + head = temp / dev->sectors; + sect = (temp % dev->sectors) + 1; + } + + /* Make sure nobody is using the same controller at the same time */ + sos_kmutex_lock (& dev->ctrl->mutex, NULL); + + /* Select device */ + outb(ATA_D_IBM | devselect, dev->ctrl->ioaddr + ATA_DRIVE); + udelay(100); + + /* Write to registers */ + outb(ATA_A_4BIT, dev->ctrl->ioaddr + ATA_DEVICE_CONTROL); + outb(1, dev->ctrl->ioaddr + ATA_ERROR); + outb(0, dev->ctrl->ioaddr + ATA_PRECOMP); + outb(1, dev->ctrl->ioaddr + ATA_SECTOR_COUNT); + outb(sect, dev->ctrl->ioaddr + ATA_SECTOR_NUMBER); + outb(cyl_lo, dev->ctrl->ioaddr + ATA_CYL_LSB); + outb(cyl_hi, dev->ctrl->ioaddr + ATA_CYL_MSB); + outb((ATA_D_IBM | devselect | head), + dev->ctrl->ioaddr + ATA_DRIVE); + + /* Send the command, either read or write */ + if (iswrite) + outb(ATA_C_WRITE, dev->ctrl->ioaddr + ATA_CMD); + else + outb(ATA_C_READ, dev->ctrl->ioaddr + ATA_CMD); + + /* Wait for the device ready to transfer */ + do { udelay(1); } while (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_BSY); + + /* If an error was detected, stop here */ + if (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR) + { + sos_kmutex_unlock (& dev->ctrl->mutex); + return -SOS_EFATAL; + } + + /* If it's a write I/O, transfer the contents of the buffer provided + by the user to the controller internal buffer, so that the + controller can write the data to the disk */ + if (iswrite) + { + /* Wait for the DRQ bit to be set */ + while (1) + { + status = inb(dev->ctrl->ioaddr + ATA_STATUS); + if (status & ATA_S_ERROR) + { + sos_kmutex_unlock (& dev->ctrl->mutex); + return -SOS_EFATAL; + } + + if (!(status & ATA_S_BSY) && (status & ATA_S_DRQ)) + break; + } + + /* Do the transfer to the device's buffer */ + sos_ui16_t *buffer = (sos_ui16_t *) buf; + for (i = 0 ; i < 256 ; i++) + outw (buffer[i], dev->ctrl->ioaddr + ATA_DATA); + + /* Wait for the device to have received all the data */ + while (1) + { + status = inb(dev->ctrl->ioaddr + ATA_STATUS); + if (status & ATA_S_ERROR) + { + sos_kmutex_unlock (& dev->ctrl->mutex); + return -SOS_EFATAL; + } + + if (!(status & ATA_S_BSY) && !(status & ATA_S_DRQ)) + break; + } + } + + /* Sleep until the IRQ wakes us up */ + sos_ksema_down (& dev->ctrl->ack_io_sem, NULL); + + /* ATA specs tell to read the alternate status reg and ignore its + result */ + inb(dev->ctrl->ioaddr + ATA_ALTPORT); + + if (! iswrite) + { + /* Wait for the DRQ bit to be set */ + while (1) + { + status = inb(dev->ctrl->ioaddr + ATA_STATUS); + if (status & ATA_S_ERROR) + { + sos_kmutex_unlock (& dev->ctrl->mutex); + return -SOS_EFATAL; + } + + if (!(status & ATA_S_BSY) && (status & ATA_S_DRQ)) + break; + } + + /* copy data from the controller internal buffer to the buffer + provided by the user */ + sos_ui16_t *buffer = (sos_ui16_t *) buf; + for (i = 0 ; i < 256 ; i++) + buffer [i] = inw (dev->ctrl->ioaddr + ATA_DATA); + + /* ATA specs tell to read the alternate status reg and ignore its + result */ + inb(dev->ctrl->ioaddr + ATA_ALTPORT); + } + + /* If an error was detected, stop here */ + if (inb(dev->ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR) + { + sos_kmutex_unlock (& dev->ctrl->mutex); + return -SOS_EFATAL; + } + + /* Someone else can safely use devices on this controller for other + requests */ + sos_kmutex_unlock (& dev->ctrl->mutex); + return SOS_OK; +} + +static sos_ret_t +ide_read_device (void *blkdev_instance, sos_vaddr_t dest_buf, + sos_luoffset_t block_offset) +{ + struct ide_device *dev; + + dev = (struct ide_device *) blkdev_instance; + + return ide_io_operation (dev, dest_buf, block_offset, FALSE); +} + +static sos_ret_t +ide_write_device (void *blkdev_instance, sos_vaddr_t src_buf, + sos_luoffset_t block_offset) +{ + struct ide_device *dev; + + dev = (struct ide_device *) blkdev_instance; + + return ide_io_operation (dev, src_buf, block_offset, TRUE); +} + +static struct sos_blockdev_operations ide_ops = { + .read_block = ide_read_device, + .write_block = ide_write_device, + .ioctl = NULL +}; + + +static sos_ret_t +ide_register_devices (void) +{ + int ctrl, dev; + sos_ret_t ret; + + for (ctrl = 0; ctrl < MAX_IDE_CONTROLLERS ; ctrl++) + { + for (dev = 0; dev < MAX_IDE_DEVICES ; dev++) + { + char name[16]; + struct ide_device *device; + + device = & ide_controllers[ctrl].devices[dev]; + + snprintf (name, sizeof(name), "hd%c", ('a' + IDE_DEVICE(ctrl, dev))); + + if (device->type == IDE_DEVICE_HARDDISK) + { + sos_bochs_printf("%s: harddisk %d Mb <", name, + (device->blocks * IDE_BLK_SIZE >> 20)); + ret = sos_blockdev_register_disk (SOS_BLOCKDEV_IDE_MAJOR, + IDE_MINOR(ctrl, dev), + IDE_BLK_SIZE, device->blocks, + 128, & ide_ops, device); + if (ret != SOS_OK) + { + sos_bochs_printf ("Warning: could not register disk>\n"); + continue; + } + + ret = sos_part_detect (SOS_BLOCKDEV_IDE_MAJOR, + IDE_MINOR(ctrl, dev), IDE_BLK_SIZE, + name); + if (ret != SOS_OK) + { + sos_bochs_printf ("Warning could not detect partitions (%d)>\n", + ret); + continue; + } + + /* Finalize output */ + sos_bochs_printf (">\n"); + } + + else if (device->type == IDE_DEVICE_CDROM) + sos_bochs_printf("%s: CDROM\n", name); + } + } + + return SOS_OK; +} + +static sos_ret_t +ide_unregister_devices (void) +{ + int ctrl, dev; + sos_ret_t ret; + + for (ctrl = 0; ctrl < MAX_IDE_CONTROLLERS ; ctrl++) + { + for (dev = 0; dev < MAX_IDE_DEVICES ; dev++) + { + struct ide_device *device; + + device = & ide_controllers[ctrl].devices[dev]; + + if (device->type != IDE_DEVICE_HARDDISK) + continue; + + sos_part_undetect (SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl,dev)); + + ret = sos_blockdev_unregister_device (SOS_BLOCKDEV_IDE_MAJOR, + IDE_MINOR(ctrl,dev)); + if (ret != SOS_OK) + sos_bochs_printf ("Error while unregistering device %d:%d\n", + SOS_BLOCKDEV_IDE_MAJOR, IDE_MINOR(ctrl,dev)); + } + } + + return SOS_OK; +} + + +sos_ret_t sos_ide_subsystem_setup (void) +{ + sos_ret_t ret; + + ret = ide_probe_controller(& ide_controllers[0]); + if (ret != SOS_OK) + sos_bochs_printf ("Error while probing IDE controller 0\n"); + + ret =ide_probe_controller(& ide_controllers[1]); + if (ret != SOS_OK) + sos_bochs_printf ("Error while probing IDE controller 1\n"); + + ide_register_devices (); + + return SOS_OK; +} + +sos_ret_t ide_driver_cleanup (void) +{ + return ide_unregister_devices (); +} + diff --git a/drivers/ide.h b/drivers/ide.h new file mode 100644 index 0000000..55a3448 --- /dev/null +++ b/drivers/ide.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DRV_IDE_H_ +#define _SOS_DRV_IDE_H_ + + +/** + * @file ide.h + * + * Basic PIO IDE implementation based on the ATA 3 & 4 standards + * http://www.t13.org/ + */ + +sos_ret_t sos_ide_subsystem_setup (void); + + +#endif /* _SOS_DRV_IDE_H_ */ diff --git a/drivers/kbd.c b/drivers/kbd.c new file mode 100644 index 0000000..ec8d7b6 --- /dev/null +++ b/drivers/kbd.c @@ -0,0 +1,118 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "tty.h" + +/* + * Keyboard ports and commands. + * + * @see Ralf Brown's interrupt (and port) list + * http://www-2.cs.cmu.edu/~ralf/files.html + */ + +#define KBD_DATA_PORT 0x60 + +#define KBD_BREAKCODE_LIMIT 0x80 +#define KBD_EXTENDED_SCANCODE 0xE0 + +#define KBD_IS_MAKECODE(c) ((c) < KBD_BREAKCODE_LIMIT) +#define KBD_IS_BREAKCODE(c) ((c) >= KBD_BREAKCODE_LIMIT) +#define KBD_BREAKCODE_2_MAKECODE(c) ((c) ^ KBD_BREAKCODE_LIMIT) + +#define KBD_LEFTSHIFT_SCANCODE 0x2a +#define KBD_RIGHTSHIFT_SCANCODE 0x36 + +typedef sos_ui8_t scancode_t; + +extern const char *kbd_regular_translate_table []; +extern const char *kbd_shift_translate_table []; + +static const char **kbd_current_translate_table = kbd_regular_translate_table; +static int is_ext = FALSE; + +static struct tty_device *tty; + +static void kbd_irq_handler (int irq_level) +{ + scancode_t scancode; + const char *key = NULL; + + scancode = inb (KBD_DATA_PORT); + + /* Mark that next interrupt wil give an extended scancode */ + if (scancode == KBD_EXTENDED_SCANCODE) + { + is_ext = TRUE; + } + + /* Handle extended scancode */ + else if (is_ext) + { + is_ext = FALSE; + } + + /* Normal scancode */ + else + { + /* Keypressed */ + if (KBD_IS_MAKECODE(scancode)) + { + /* If shift, change translation table */ + if ((scancode == KBD_LEFTSHIFT_SCANCODE) || + (scancode == KBD_RIGHTSHIFT_SCANCODE)) + kbd_current_translate_table = kbd_shift_translate_table; + + /* If normal key, compute the result using the translation + tables and the booleans */ + else if (kbd_current_translate_table[scancode]) + { + key = kbd_current_translate_table[scancode]; + } + } + + /* Key released */ + else + { + scancode_t makecode = KBD_BREAKCODE_2_MAKECODE(scancode); + + if ((makecode == KBD_LEFTSHIFT_SCANCODE) || + (makecode == KBD_RIGHTSHIFT_SCANCODE)) + kbd_current_translate_table = kbd_regular_translate_table; + } + } + + if (key) + tty_add_chars (tty, key); +} + + +sos_ret_t sos_kbd_subsystem_setup(struct tty_device *t) +{ + tty = t; + + sos_irq_set_routine (SOS_IRQ_KEYBOARD, kbd_irq_handler); + + return SOS_OK; +} + diff --git a/drivers/kbd.h b/drivers/kbd.h new file mode 100644 index 0000000..7e723e4 --- /dev/null +++ b/drivers/kbd.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DRV_KBD_H_ +#define _SOS_DRV_KBD_H_ + +/** + * @file kbd.h + * + * Basic keyboard driver for SOS. Handles normal, shift and function + * keys, does not handle alt/altgr/ctrl combinations. + */ + +#include +#include + + +/** + * Bind the physical keyboard to the given TTY device + */ +sos_ret_t sos_kbd_subsystem_setup(struct tty_device *tty); + +#endif /* _SOS_DRV_KBD_H_ */ diff --git a/drivers/kbdmapfr.c b/drivers/kbdmapfr.c new file mode 100644 index 0000000..0a76e63 --- /dev/null +++ b/drivers/kbdmapfr.c @@ -0,0 +1,282 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +const char *kbd_regular_translate_table [128] = { + /* 0 */ 0, + /* 1 */ "\e", + /* 2 */ "&", + /* 3 */ "é", + /* 4 */ "\"", + /* 5 */ "'", + /* 6 */ "(", + /* 7 */ "-", + /* 8 */ "è", + /* 9 */ "_", + /* 10 */ "ç", + /* 11 */ "à", + /* 12 */ ")", + /* 13 */ "=", + /* 14 */ "\b", /* Backspace */ + /* 15 */ "\t", /* Tab */ + /* 16 */ "a", + /* 17 */ "z", + /* 18 */ "e", + /* 19 */ "r", + /* 20 */ "t", + /* 21 */ "y", + /* 22 */ "u", + /* 23 */ "i", + /* 24 */ "o", + /* 25 */ "p", + /* 26 */ "^", + /* 27 */ "$", + /* 28 */ "\n", + /* 29 */ 0, /* left control */ + /* 30 */ "q", + /* 31 */ "s", + /* 32 */ "d", + /* 33 */ "f", + /* 34 */ "g", + /* 35 */ "h", + /* 36 */ "j", + /* 37 */ "k", + /* 38 */ "l", + /* 39 */ "m", + /* 40 */ "ù", + /* 41 */ 0, + /* 42 */ 0, /* left shift */ + /* 43 */ "*", + /* 44 */ "w", + /* 45 */ "x", + /* 46 */ "c", + /* 47 */ "v", + /* 48 */ "b", + /* 49 */ "n", + /* 50 */ ",", + /* 51 */ ";", + /* 52 */ ":", + /* 53 */ "!", + /* 54 */ 0, + /* 55 */ 0, + /* 56 */ 0, + /* 57 */ " ", + /* 58 */ 0, + /* 59 */ "\eOP", /* F1 */ + /* 60 */ "\eOQ", /* F2 */ + /* 61 */ "\eOR", /* F3 */ + /* 62 */ "\eOS", /* F4 */ + /* 63 */ "\e[15~", /* F5 */ + /* 64 */ "\e[17~", /* F6 */ + /* 65 */ "\e[18~", /* F7 */ + /* 66 */ "\e[19~", /* F8 */ + /* 67 */ "\e[20~", /* F9 */ + /* 68 */ "\e[21~", /* F10 */ + /* 69 */ 0, + /* 70 */ 0, + /* 71 */ 0, + /* 72 */ 0, + /* 73 */ 0, + /* 74 */ 0, + /* 75 */ 0, + /* 76 */ 0, + /* 77 */ 0, + /* 78 */ 0, + /* 79 */ 0, + /* 80 */ 0, + /* 81 */ 0, + /* 82 */ 0, + /* 83 */ 0, + /* 84 */ 0, + /* 85 */ 0, + /* 86 */ "<", + /* 87 */ "\e[23~", /* F11 */ + /* 88 */ "\e[24~", /* F12 */ + /* 89 */ 0, + /* 90 */ 0, + /* 91 */ 0, + /* 92 */ 0, + /* 93 */ 0, + /* 94 */ 0, + /* 95 */ 0, + /* 96 */ 0, + /* 97 */ 0, + /* 98 */ 0, + /* 99 */ 0, + /* 100 */ 0, + /* 101 */ 0, + /* 102 */ 0, + /* 103 */ 0, + /* 104 */ 0, + /* 105 */ 0, + /* 106 */ 0, + /* 107 */ 0, + /* 108 */ 0, + /* 109 */ 0, + /* 110 */ 0, + /* 111 */ 0, + /* 112 */ 0, + /* 113 */ 0, + /* 114 */ 0, + /* 115 */ 0, + /* 116 */ 0, + /* 117 */ 0, + /* 118 */ 0, + /* 119 */ 0, + /* 120 */ 0, + /* 121 */ 0, + /* 122 */ 0, + /* 123 */ 0, + /* 124 */ 0, + /* 125 */ 0, + /* 126 */ 0, + /* 127 */ 0 +}; + +const char *kbd_shift_translate_table [128] = { + /* 0 */ 0, + /* 1 */ "\e", + /* 2 */ "1", + /* 3 */ "2", + /* 4 */ "3", + /* 5 */ "4", + /* 6 */ "5", + /* 7 */ "6", + /* 8 */ "7", + /* 9 */ "8", + /* 10 */ "9", + /* 11 */ "0", + /* 12 */ "°", + /* 13 */ "+", + /* 14 */ "\b", /* Shift-Backspace */ + /* 15 */ "\e[Z", /* Shift-Tab */ + /* 16 */ "A", + /* 17 */ "Z", + /* 18 */ "E", + /* 19 */ "R", + /* 20 */ "T", + /* 21 */ "Y", + /* 22 */ "U", + /* 23 */ "I", + /* 24 */ "O", + /* 25 */ "P", + /* 26 */ "\"", + /* 27 */ "£", + /* 28 */ "\n", + /* 29 */ 0, /* left control */ + /* 30 */ "Q", + /* 31 */ "S", + /* 32 */ "D", + /* 33 */ "F", + /* 34 */ "G", + /* 35 */ "H", + /* 36 */ "J", + /* 37 */ "K", + /* 38 */ "L", + /* 39 */ "M", + /* 40 */ "%", + /* 41 */ 0, + /* 42 */ 0, + /* 43 */ "µ", + /* 44 */ "W", + /* 45 */ "X", + /* 46 */ "C", + /* 47 */ "V", + /* 48 */ "B", + /* 49 */ "N", + /* 50 */ "?", + /* 51 */ ".", + /* 52 */ "/", + /* 53 */ "§", + /* 54 */ 0, + /* 55 */ 0, + /* 56 */ 0, + /* 57 */ 0, + /* 58 */ 0, + /* 59 */ "\eOP", /* Shift-F1 */ + /* 60 */ "\eOQ", /* Shift-F2 */ + /* 61 */ "\eOR", /* Shift-F3 */ + /* 62 */ "\eOS", /* Shift-F4 */ + /* 63 */ "\e[15;2~", /* Shift-F5 */ + /* 64 */ "\e[17;2~", /* Shift-F6 */ + /* 65 */ "\e[18;2~", /* Shift-F7 */ + /* 66 */ "\e[19;2~", /* Shift-F8 */ + /* 67 */ "\e[20:2~", /* Shift-F9 */ + /* 68 */ "\e[21:2~", /* Shift-F10 */ + /* 69 */ 0, + /* 70 */ 0, + /* 71 */ 0, + /* 72 */ 0, + /* 73 */ 0, + /* 74 */ 0, + /* 75 */ 0, + /* 76 */ 0, + /* 77 */ 0, + /* 78 */ 0, + /* 79 */ 0, + /* 80 */ 0, + /* 81 */ 0, + /* 82 */ 0, + /* 83 */ 0, + /* 84 */ 0, + /* 85 */ 0, + /* 86 */ ">", + /* 87 */ "\e[23;2~", /* Shift-F11 */ + /* 88 */ "\e[24;2~", /* Shift-F12 */ + /* 89 */ 0, + /* 90 */ 0, + /* 91 */ 0, + /* 92 */ 0, + /* 93 */ 0, + /* 94 */ 0, + /* 95 */ 0, + /* 96 */ 0, + /* 97 */ 0, + /* 98 */ 0, + /* 99 */ 0, + /* 100 */ 0, + /* 101 */ 0, + /* 102 */ 0, + /* 103 */ 0, + /* 104 */ 0, + /* 105 */ 0, + /* 106 */ 0, + /* 107 */ 0, + /* 108 */ 0, + /* 109 */ 0, + /* 110 */ 0, + /* 111 */ 0, + /* 112 */ 0, + /* 113 */ 0, + /* 114 */ 0, + /* 115 */ 0, + /* 116 */ 0, + /* 117 */ 0, + /* 118 */ 0, + /* 119 */ 0, + /* 120 */ 0, + /* 121 */ 0, + /* 122 */ 0, + /* 123 */ 0, + /* 124 */ 0, + /* 125 */ 0, + /* 126 */ 0, + /* 127 */ 0 +}; + diff --git a/drivers/mem.c b/drivers/mem.c new file mode 100644 index 0000000..d97aa0d --- /dev/null +++ b/drivers/mem.c @@ -0,0 +1,489 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mem.h" + + +/** + * A mapped mem/kmem resource + */ +struct kernel_remapped_resource +{ + int ref_cnt; + struct sos_umem_vmm_mapped_resource mr; +}; + + +/** Called after the virtual region has been inserted inside its + address space */ +static void resource_ref(struct sos_umem_vmm_vr * vr) +{ + /* Retrieve the mem/kmem structure associated with the mapped resource */ + struct kernel_remapped_resource * resource; + resource + = (struct kernel_remapped_resource*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Increment ref counter */ + resource->ref_cnt ++; +} + + +/** Called when the virtual region is removed from its address + space */ +static void resource_unref(struct sos_umem_vmm_vr * vr) +{ + /* Retrieve the mem/kmem structure associated with the mapped resource */ + struct kernel_remapped_resource * resource; + resource + = (struct kernel_remapped_resource*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Decrement ref coutner */ + SOS_ASSERT_FATAL(resource->ref_cnt > 0); + resource->ref_cnt --; + + /* Free the resource if it becomes unused */ + if (resource->ref_cnt == 0) + sos_kfree((sos_vaddr_t)resource); +} + + +/** MOST IMPORTANT callback ! Called when a thread page faults on the + resource's mapping */ +static sos_ret_t kmem_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + sos_vaddr_t vaddr; + sos_ret_t retval = SOS_OK; + sos_paddr_t ppage_paddr; + + /* Compute address of kernel page */ + vaddr = uaddr - sos_umem_vmm_get_start_of_vr(vr) + + sos_umem_vmm_get_offset_in_resource(vr); + + /* Don't allow demand paging of non kernel pages */ + if (! SOS_PAGING_IS_KERNEL_AREA(vaddr, 1)) + return -SOS_EFAULT; + + /* Lookup physical kernel page */ + ppage_paddr = sos_paging_get_paddr(SOS_PAGE_ALIGN_INF(vaddr)); + + /* Cannot access unmapped kernel pages */ + if (! ppage_paddr) + return -SOS_EFAULT; + + /* Remap it in user space */ + retval = sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + sos_umem_vmm_get_prot_of_vr(vr)); + + return retval; +} + + +/** The callbacks for a mapped kmem resource */ +static struct sos_umem_vmm_vr_ops kmem_ops = (struct sos_umem_vmm_vr_ops) +{ + .ref = resource_ref, + .unref = resource_unref, + .page_in = kmem_page_in, +}; + + +/** The callback that gets called when the resource gets mapped */ +static sos_ret_t kmem_mmap(struct sos_umem_vmm_vr *vr) +{ + return sos_umem_vmm_set_ops_of_vr(vr, &kmem_ops); +} + + +/** The function responsible for mapping the /dev/kmem resource in + user space */ +static +sos_ret_t sos_dev_kmem_map(struct sos_umem_vmm_as * dest_as, + sos_uaddr_t *uaddr, + sos_size_t size, + sos_vaddr_t offset, + sos_ui32_t access_rights, + sos_ui32_t flags) +{ + sos_ret_t retval; + struct kernel_remapped_resource * kmem_resource; + + /* Allocate a new "descriptor" for the resource */ + kmem_resource + = (struct kernel_remapped_resource*) sos_kmalloc(sizeof(*kmem_resource), + 0); + if (! kmem_resource) + return -SOS_ENOMEM; + + memset(kmem_resource, 0x0, sizeof(*kmem_resource)); + kmem_resource->mr.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + kmem_resource->mr.custom_data = kmem_resource; + kmem_resource->mr.mmap = kmem_mmap; + + /* Map it in user space */ + retval = sos_umem_vmm_map(dest_as, uaddr, size, + access_rights, flags, + & kmem_resource->mr, offset); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t)kmem_resource); + return retval; + } + + return SOS_OK; +} + + +/** MOST IMPORTANT callback ! Called when a thread page faults on the + resource's mapping */ +static sos_ret_t physmem_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + sos_ret_t retval = SOS_OK; + sos_paddr_t ppage_paddr; + + /* Compute address of kernel page */ + ppage_paddr = uaddr - sos_umem_vmm_get_start_of_vr(vr) + + sos_umem_vmm_get_offset_in_resource(vr); + + /* Remap page in user space */ + retval = sos_paging_map(SOS_PAGE_ALIGN_INF(ppage_paddr), + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + sos_umem_vmm_get_prot_of_vr(vr)); + return retval; +} + + +/** The callbacks for a mapped physmem resource */ +static struct sos_umem_vmm_vr_ops physmem_ops = (struct sos_umem_vmm_vr_ops) +{ + .ref = resource_ref, + .unref = resource_unref, + .page_in = physmem_page_in, +}; + + +/** The callback that gets called when the resource gets mapped */ +static sos_ret_t physmem_mmap(struct sos_umem_vmm_vr *vr) +{ + return sos_umem_vmm_set_ops_of_vr(vr, &physmem_ops); +} + + +/** The function responsible for mapping the /dev/mem resource in + user space */ +static +sos_ret_t sos_dev_physmem_map(struct sos_umem_vmm_as * dest_as, + sos_uaddr_t *uaddr, + sos_size_t size, + sos_paddr_t offset, + sos_ui32_t access_rights, + sos_ui32_t flags) +{ + sos_ret_t retval; + struct kernel_remapped_resource * physmem_resource; + + physmem_resource + = (struct kernel_remapped_resource*) sos_kmalloc(sizeof(*physmem_resource), + 0); + if (! physmem_resource) + return -SOS_ENOMEM; + + memset(physmem_resource, 0x0, sizeof(*physmem_resource)); + physmem_resource->mr.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + physmem_resource->mr.custom_data = physmem_resource; + physmem_resource->mr.mmap = physmem_mmap; + + retval = sos_umem_vmm_map(dest_as, uaddr, size, + access_rights, flags, + & physmem_resource->mr, offset); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t)physmem_resource); + return retval; + } + + return SOS_OK; +} + + +/* + * /dev/mem and /dev/kmem character device operations + * + * the "custom_data" field of the FS node is used to store the total + * number of pages available + */ +#define GET_DEV_SIZE(fsnode) \ + ((sos_size_t)(fsnode)->custom_data) + +static sos_ret_t dev_mem_fs_open(struct sos_fs_node * fsnode, + struct sos_fs_opened_file * of, + void * chardev_class_custom_data) +{ + /* Make sure the device is supported by this driver and compute its + "size" (use the custom_data field to store it) */ + switch (fsnode->dev_id.device_instance) + { + /* For /dev/kmem, go to the end of the kernel mapping */ + case SOS_CHARDEV_KMEM_MINOR: + fsnode->custom_data = (void*)SOS_PAGING_BASE_USER_ADDRESS; + return SOS_OK; + break; + + /* For /dev/mem, go to the end of physical memory */ + case SOS_CHARDEV_PHYSMEM_MINOR: + { + sos_size_t ram_pages = 0; + sos_physmem_get_state(& ram_pages, NULL); + fsnode->custom_data = (void*)(ram_pages << SOS_PAGE_SHIFT); + } + return SOS_OK; + break; + + default: + break; + } + + return -SOS_ENODEV; +} + + +static sos_ret_t dev_mem_fs_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + /* Make sure the device is supported by this driver */ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + + /* Artificiallly update the position in the "file" */ + sos_lsoffset_t ref_offs; + sos_lsoffset_t dev_size = GET_DEV_SIZE(fsnode); + + *result_position = this->position; + switch (whence) + { + case SOS_SEEK_SET: + ref_offs = 0; + break; + + case SOS_SEEK_CUR: + ref_offs = this->position; + break; + + case SOS_SEEK_END: + ref_offs = dev_size; + break; + + default: + return -SOS_EINVAL; + } + + /* Forbid accesses "before" the start of the device */ + if (offset < -ref_offs) + return -SOS_EINVAL; + + /* Forbid accesses "after" the end of the device */ + else if (ref_offs + offset > dev_size) + return -SOS_EINVAL; + + this->position = ref_offs + offset; + *result_position = this->position; + return SOS_OK; +} + + +typedef enum { DO_READ, DO_WRITE } dev_mem_access_type_t; +static sos_ret_t dev_mem_fs_access(struct sos_fs_opened_file *this, + sos_uaddr_t user_buf, + sos_size_t * /* in/out */len, + dev_mem_access_type_t access_type) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + sos_vaddr_t physmem_transfer_kernel_page = 0; /* Used for /dev/mem only */ + sos_uoffset_t offs; + sos_size_t accesslen = 0; + + /* Readjust copy length to match the size of the device */ + if (this->position + *len >= GET_DEV_SIZE(fsnode)) + *len = GET_DEV_SIZE(fsnode) - this->position; + + /* Ignore zero-size requests */ + if (*len <= 0) + return SOS_OK; + + /* For /dev/mem device, prepare a kernel page to copy the physical + pages before transferring to user space */ + if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) + { + physmem_transfer_kernel_page = sos_kmem_vmm_alloc(1, 0); + if (! physmem_transfer_kernel_page) + return -SOS_ENOMEM; + } + + /* Try to copy the data in page-size chunks */ + offs = this->position; + while (offs < this->position + *len) + { + /* Retrieve page address of data in kernel memory */ + sos_uoffset_t page_boundary = SOS_PAGE_ALIGN_INF(offs); + sos_vaddr_t page_vaddr; + sos_uoffset_t offset_in_page; + sos_uoffset_t accesslen_in_page; + sos_ret_t retval; + + /* For /dev/mem device, we need to map the page in kernel memory + before */ + if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) + { + retval = sos_paging_map(page_boundary, + physmem_transfer_kernel_page, + FALSE, + (access_type==DO_WRITE)? + SOS_VM_MAP_PROT_WRITE + :SOS_VM_MAP_PROT_READ); + if (SOS_OK != retval) + break; + + page_vaddr = physmem_transfer_kernel_page; + } + /* For /dev/kmem device, the page should already be in kernel space */ + else if (! sos_kmem_vmm_is_valid_vaddr(page_boundary)) + break; /* No: page is not mapped in kernel space ! */ + else + page_vaddr = page_boundary; /* Yes, page is mapped */ + + /* Now copy the data from kernel to user space */ + offset_in_page = offs - page_boundary; + accesslen_in_page = SOS_PAGE_SIZE - offset_in_page; + if (accesslen + accesslen_in_page > *len) + accesslen_in_page = *len - accesslen; + + if (access_type==DO_WRITE) + retval = sos_memcpy_from_user(page_vaddr + offset_in_page, + user_buf + accesslen, + accesslen_in_page); + else + retval = sos_memcpy_to_user(user_buf + accesslen, + page_vaddr + offset_in_page, + accesslen_in_page); + + /* Now, for /dev/mem, unmap the page from kernel */ + if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) + sos_paging_unmap(physmem_transfer_kernel_page); + + /* Go to next page if possible */ + if (retval < 0) + break; + + accesslen += retval; + /* If transfer was interrupted, stop here */ + if (retval < (sos_ret_t)accesslen_in_page) + break; + + /* Go on to next page */ + offs = page_boundary + SOS_PAGE_SIZE; + } + + /* Release the temporary page for physical mem transfers */ + if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) + sos_kmem_vmm_free(physmem_transfer_kernel_page); + + /* Update the position in the "file" */ + *len = accesslen; + this->position += accesslen; + return SOS_OK; +} + + +static sos_ret_t dev_mem_fs_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + return dev_mem_fs_access(this, dest_buf, len, DO_READ); +} + + +static sos_ret_t dev_mem_fs_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + return dev_mem_fs_access(this, src_buf, len, DO_WRITE); +} + + +static sos_ret_t dev_mem_fs_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + if (SOS_CHARDEV_PHYSMEM_MINOR == fsnode->dev_id.device_instance) + return sos_dev_physmem_map(sos_process_get_address_space(this->owner), + uaddr, size, offset, access_rights, flags); + + return sos_dev_kmem_map(sos_process_get_address_space(this->owner), + uaddr, size, offset, access_rights, flags); +} + + +static struct sos_chardev_ops dev_mem_fs_ops + = (struct sos_chardev_ops) { + .open = dev_mem_fs_open, + .close = NULL, + .seek = dev_mem_fs_seek, + .read = dev_mem_fs_read, + .write = dev_mem_fs_write, + .mmap = dev_mem_fs_mmap, + .fcntl = NULL, + .ioctl = NULL + }; + + +sos_ret_t sos_dev_mem_chardev_setup() +{ + return sos_chardev_register_class(SOS_CHARDEV_MEM_MAJOR, + & dev_mem_fs_ops, + NULL); +} diff --git a/drivers/mem.h b/drivers/mem.h new file mode 100644 index 0000000..930769b --- /dev/null +++ b/drivers/mem.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DEV_MEM_H_ +#define _SOS_DEV_MEM_H_ + +/** + * @file mem.h + * + * Drivers to map the kernel areas ("/dev/kmem") and the physical RAM + * ("/dev/mem") into user space + */ + +#include + + +/** + * Bind to the /dev/kmem and /dev/mem character devices + */ +sos_ret_t sos_dev_mem_chardev_setup(void); + +#endif /* _SOS_EXEC_ELF32_H_ */ diff --git a/drivers/part.c b/drivers/part.c new file mode 100644 index 0000000..fb975ec --- /dev/null +++ b/drivers/part.c @@ -0,0 +1,216 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include + +/** + * This structure defines the structure of a partition entry + * (determined by the PC architecture) + */ +typedef struct partition_entry +{ + sos_ui8_t active; + sos_ui8_t start_dl; + sos_ui16_t start_cylinder; + sos_ui8_t type; + sos_ui8_t end_dl; + sos_ui16_t end_cylinder; + sos_ui32_t lba; + sos_ui32_t size; +} partition_entry_t; + +/** + * Offset of the partition table inside the 512-byte sector. + */ +#define PART_TABLE_OFFSET 446 + +/** + * The most common partition types. For a complete list, you can for + * example refer to + * http://www.win.tue.nl/~aeb/partitions/partition_types-1.html + */ +#define PART_TYPE_EXTENDED 0x5 +#define PART_TYPE_FAT16_1 0xe +#define PART_TYPE_FAT16_2 0x6 +#define PART_TYPE_FAT32_1 0xb +#define PART_TYPE_FAT32_2 0xc +#define PART_TYPE_LINUX_SWAP 0x82 +#define PART_TYPE_LINUX 0x83 + +/** + * Converts a partition type to a string + */ +static const char * +sos_part_type_str (unsigned int type) +{ + switch (type) + { + case PART_TYPE_EXTENDED: + return "Extended"; + case PART_TYPE_FAT16_1: + case PART_TYPE_FAT16_2: + return "FAT16"; + case PART_TYPE_FAT32_1: + case PART_TYPE_FAT32_2: + return "FAT32"; + case PART_TYPE_LINUX_SWAP: + return "Linux Swap"; + case PART_TYPE_LINUX: + return "Linux"; + default: + return "Unknown"; + } +} + +/** + * Detect the partitions on the given device, and registers them to + * the block device infrastructure. + */ +sos_ret_t +sos_part_detect (sos_ui32_t disk_class, sos_ui32_t disk_instance, + sos_size_t block_size, const char *name) +{ + sos_vaddr_t buffer; + struct sos_blockdev_instance *blkdev; + struct partition_entry *part_entry; + unsigned int extstart = 0, extsup = 0; + sos_ret_t ret; + sos_size_t size = block_size; + unsigned int partnum; + + blkdev = sos_blockdev_ref_instance (disk_class, disk_instance); + if (blkdev == NULL) + return -SOS_ENOENT; + + buffer = (sos_vaddr_t) sos_kmalloc (block_size, 0); + if (buffer == 0) + { + sos_blockdev_release_instance (blkdev); + return -SOS_ENOMEM; + } + + ret = sos_blockdev_kernel_read (blkdev, 0, buffer, & size); + if (ret != SOS_OK) + { + sos_blockdev_release_instance (blkdev); + sos_kfree (buffer); + return ret; + } + + if (size != block_size) + { + sos_blockdev_release_instance (blkdev); + sos_kfree (buffer); + return -SOS_EIO; + } + + part_entry = (struct partition_entry *) (buffer + PART_TABLE_OFFSET); + + /* Handle primary partitions */ + for (partnum = 0; partnum < 4; partnum++) + { + if (part_entry [partnum].size == 0) + continue; + + sos_bochs_printf ("%s%d (%lu Mb, %s) ", name, partnum+1, + (part_entry[partnum].size * block_size >> 20), + sos_part_type_str(part_entry[partnum].type)); + if (part_entry [partnum].type == PART_TYPE_EXTENDED) + { + if (extstart != 0) + sos_bochs_printf ("Warning: two extended partitions detected\n"); + extstart = part_entry [partnum].lba; + continue; + } + + ret = sos_blockdev_register_partition (disk_class, + disk_instance + partnum + 1, + blkdev, + part_entry[partnum].lba, + part_entry[partnum].size, NULL); + if (ret != SOS_OK) + { + sos_bochs_printf ("Could not register partition %d for disk %lu:%lu: error %d\n", + partnum, disk_class, disk_instance, ret); + continue; + } + } + + while (extstart != 0 && partnum < 15) + { + ret = sos_blockdev_kernel_read (blkdev, (extstart + extsup) * block_size, + (sos_luoffset_t) buffer, & size); + if (ret != SOS_OK) + { + sos_blockdev_release_instance (blkdev); + sos_kfree (buffer); + return ret; + } + + if (size != block_size) + { + sos_blockdev_release_instance (blkdev); + sos_kfree (buffer); + return -SOS_EIO; + } + + sos_bochs_printf ("%s%d (%lu Mb, %s) ", name, partnum+1, + (part_entry[0].size * block_size >> 20), + sos_part_type_str(part_entry[0].type)); + + ret = sos_blockdev_register_partition (disk_class, + disk_instance + partnum + 1, + blkdev, + extstart + part_entry[0].lba, + part_entry[0].size, NULL); + if (ret != SOS_OK) + sos_bochs_printf ("Could not register partition %d for disk %lu:%lu: error %d\n", + partnum, disk_class, disk_instance, ret); + + extsup = part_entry[1].lba; + partnum ++; + if (extsup == 0) + break; + } + + sos_blockdev_release_instance (blkdev); + sos_kfree (buffer); + return SOS_OK; +} + +/** + * Remove all detected partitions on given device + */ +sos_ret_t +sos_part_undetect (sos_ui32_t disk_class, sos_ui32_t disk_instance) +{ + int partnum; + sos_ret_t ret; + + for (partnum = 0; partnum < 15; partnum++) + { + ret = sos_blockdev_unregister_device (disk_class, disk_instance + partnum + 1); + if (ret != SOS_OK) + sos_bochs_printf ("Warning: could not unregister partition %d of device %lu:%lu\n", + partnum, disk_class, disk_instance); + } + + return SOS_OK; +} diff --git a/drivers/part.h b/drivers/part.h new file mode 100644 index 0000000..add5bf8 --- /dev/null +++ b/drivers/part.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2005 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#ifndef _SOS_PART_H_ +#define _SOS_PART_H_ + +/** + * @file part.h + * + * IBM PC partition "driver": in charge of registering each of the + * partition of the given block device + */ + +sos_ret_t sos_part_detect (sos_ui32_t disk_class, sos_ui32_t disk_instance, + sos_size_t block_size, const char *name); + +sos_ret_t sos_part_undetect (sos_ui32_t disk_class, sos_ui32_t disk_instance); + +#endif /* _SOS_PART_H_ */ diff --git a/drivers/serial.c b/drivers/serial.c new file mode 100644 index 0000000..c2406d2 --- /dev/null +++ b/drivers/serial.c @@ -0,0 +1,292 @@ +/* Copyright (C) 2000 David Decotigny, The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "tty.h" + +#define SERIAL_BAUDRATE_MAX 115200 + +/* Default parameters */ +#ifndef DEBUG_SERIAL_PORT +# define DEBUG_SERIAL_PORT 0 +#endif +#ifndef DEBUG_SERIAL_SPEED +# define DEBUG_SERIAL_SPEED 9600 +#endif + +/* The offsets of UART registers. */ +#define UART_TX 0 +#define UART_RX 0 +#define UART_DLL 0 +#define UART_IER 1 +#define UART_DLH 1 +#define UART_IIR 2 +#define UART_FCR 2 +#define UART_LCR 3 +#define UART_MCR 4 +#define UART_LSR 5 +#define UART_MSR 6 +#define UART_SR 7 + +/* For LSR bits. */ +#define UART_DATA_READY 0x01 +#define UART_EMPTY_TRANSMITTER 0x20 + +/* The type of parity. */ +#define UART_NO_PARITY 0x00 +#define UART_ODD_PARITY 0x08 +#define UART_EVEN_PARITY 0x18 + +/* The type of word length. */ +#define UART_5BITS_WORD 0x00 +#define UART_6BITS_WORD 0x01 +#define UART_7BITS_WORD 0x02 +#define UART_8BITS_WORD 0x03 + +/* The type of the length of stop bit. */ +#define UART_1_STOP_BIT 0x00 +#define UART_2_STOP_BITS 0x04 + +/* the switch of DLAB. */ +#define UART_DLAB 0x80 + +/* Enable the FIFO. */ +#define UART_ENABLE_FIFO 0xC7 + +/* Turn on DTR, RTS, and OUT2. */ +#define UART_ENABLE_MODEM 0x0B + + +static struct +{ + unsigned short iobase; + unsigned short is_enabled; + struct tty_device *tty; +} _serial_config [] = { { 0x3f8, FALSE }, + { 0x2f8, FALSE }, + { 0x3e8, FALSE }, + { 0x2e8, FALSE } }; + +#define SERIAL_PORT_MAX 4 + +#define serial_inb inb +#define serial_outb(port,val) outb(val,port) + +inline static int +serial_isready (unsigned short port) +{ + unsigned char status; + + status = serial_inb (port + UART_LSR); + return (status & UART_DATA_READY) ? : -1; +} + + +static char +serial_getchar (unsigned short unit) +{ + if(unit >= SERIAL_PORT_MAX + || _serial_config[unit].is_enabled == FALSE) + return -1; + + /* Wait until data is ready. */ + while ((serial_inb (_serial_config[unit].iobase + UART_LSR) + & UART_DATA_READY) == 0) + ; + + /* Read and return the data. */ + return serial_inb (_serial_config[unit].iobase + UART_RX); +} + + +/* 0 on success */ +static int +serial_putchar (unsigned short port, char c) +{ + /* Perhaps a timeout is necessary. */ + int timeout = 10000; + + /* Wait until the transmitter holding register is empty. */ + while ((serial_inb (port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0) + if (--timeout == 0) + /* There is something wrong. But what can I do? */ + return -1; + + serial_outb (port + UART_TX, c); + return 0; +} + +static char serial_printk_buf[1024]; + +inline static int serial_prints(unsigned short unit, const char *str) +{ + const char *c; + unsigned short port; + + if(unit >= SERIAL_PORT_MAX + || _serial_config[unit].is_enabled == FALSE) + return -1; + + port = _serial_config[unit].iobase; + + for (c = str; *c != '\0'; c++) + serial_putchar(port, *c); + + + return (int) (c-str); +} + +int sos_serial_printf(unsigned short unit, const char *format, ...) +{ + va_list args; + int len; + + va_start(args, format); + len=vsnprintf(serial_printk_buf,sizeof(serial_printk_buf),format,args); + + return serial_prints(unit, serial_printk_buf); +} + + +static void serial_irq_handler (int irq_level) +{ + unsigned char chr; + + if (irq_level == SOS_IRQ_COM1) + { + char c[2]; + chr = serial_inb (_serial_config[0].iobase + UART_RX); + + /* Little hacks to get it to work with Qemu serial port + emulation. */ + if (chr == '\r') + chr = '\n'; + else if (chr == 127) + chr = '\b'; + + /* Build a null-terminated string */ + c[0] = chr; + c[1] = '\0'; + + tty_add_chars (_serial_config[0].tty, c); + } +} + +static sos_ret_t sos_serial_putchar (char c) +{ + sos_ret_t ret; + unsigned int i; + + /* The serial port doesn't understand '\b', but requires ANSI + commands instead. So we emulate '\b' by outputing "\e[D \e[D" + which basically goes backward one character, prints a space and + goes backward one character again. */ + if (c == '\b') + { + const char *str = "\e[D \e[D"; + + for (i = 0; i < strlen(str); i++) + { + ret = serial_putchar (_serial_config[0].iobase, str[i]); + if (ret != SOS_OK) + return ret; + } + + return SOS_OK; + } + + return serial_putchar (_serial_config[0].iobase, c); +} + + +/* OK On success */ +sos_ret_t sos_serial_subsystem_setup (sos_bool_t enable) +{ + unsigned short div = 0; + unsigned char status = 0; + unsigned short serial_port; + + unsigned short unit = 0; + unsigned int speed = 115200; + int word_len = UART_8BITS_WORD; + int parity = UART_NO_PARITY; + int stop_bit_len = UART_1_STOP_BIT; + + if (unit >= SERIAL_PORT_MAX) + return -1; + + serial_port = _serial_config[unit].iobase; + + /* Turn off the interrupt. */ + serial_outb (serial_port + UART_IER, 0); + + /* Set DLAB. */ + serial_outb (serial_port + UART_LCR, UART_DLAB); + + /* Set the baud rate. */ + if (speed > SERIAL_BAUDRATE_MAX) + return -1; + + div = SERIAL_BAUDRATE_MAX / speed; + + serial_outb (serial_port + UART_DLL, div & 0xFF); + serial_outb (serial_port + UART_DLH, div >> 8); + + /* Set the line status. */ + status |= parity | word_len | stop_bit_len; + serial_outb (serial_port + UART_LCR, status); + + /* Enable the FIFO. */ + serial_outb (serial_port + UART_FCR, UART_ENABLE_FIFO); + + /* Turn on DTR, RTS, and OUT2. */ + serial_outb (serial_port + UART_MCR, UART_ENABLE_MODEM); + + /* Drain the input buffer. */ + while (serial_isready (serial_port) != -1) + (void) serial_getchar (unit); + + _serial_config[unit].is_enabled = TRUE; + + return SOS_OK; +} + + +/* Cannot be placed in sos_serial_subsystem_init() because when it + gets called, the IRQ handling subsystem is not yet initialized */ +sos_ret_t sos_ttyS0_subsystem_setup (void) +{ + sos_ret_t ret; + + ret = tty_create (SOS_CHARDEV_SERIAL_MINOR, sos_serial_putchar, + & _serial_config[0].tty); + if (SOS_OK != ret) + return ret; + + sos_irq_set_routine (SOS_IRQ_COM1, serial_irq_handler); + + /* Enable interrupts */ + serial_outb (_serial_config[0].iobase + UART_IER, 1); + + return SOS_OK; +} diff --git a/drivers/serial.h b/drivers/serial.h new file mode 100644 index 0000000..6377d28 --- /dev/null +++ b/drivers/serial.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2000 David Decotigny, Thomas Petazzoni, The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_SERIAL_H_ +#define _SOS_SERIAL_H_ + +/** + * @file serial.h + * + * 16550 Serial driver for SOS. This is a two-layers driver: + * - low-level layer: polling-style read/write functions + * - higher-level layer: interrupt-driver read function bound to a TTY device + */ + +#include +#include + + +/** + * Create a new TTY device (minor SOS_CHARDEV_SERIAL_MINOR) + * controlling the first serial line + */ +sos_ret_t sos_ttyS0_subsystem_setup (void); + + +#endif diff --git a/drivers/tty.c b/drivers/tty.c new file mode 100644 index 0000000..816df2f --- /dev/null +++ b/drivers/tty.c @@ -0,0 +1,318 @@ +/* Copyright (C) 2005 Thomas Petazzoni, David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tty.h" + +#define TTY_BUFFER_LEN 64 +#define TTY_NAME_LEN 16 + +struct tty_device { + sos_ui32_t instance; + unsigned int open_count; + char buffer[TTY_BUFFER_LEN]; + unsigned int buffer_read; + unsigned int buffer_write; + struct sos_ksema sem; + struct sos_kwaitq wq; + sos_ret_t (*write)(char c); + unsigned int param; + struct tty_device *next, *prev; +}; + +static struct tty_device *tty_device_list; + +static struct tty_device * +tty_device_find (sos_ui32_t instance) +{ + struct tty_device *t; + int n; + + list_foreach (tty_device_list, t, n) + { + if (t->instance == instance) + return t; + } + + return NULL; +} + +static sos_ret_t tty_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, sos_size_t *len) +{ + sos_size_t count = 0; + struct tty_device *t; + sos_ret_t ret; + + t = (struct tty_device *) this->custom_data; + + if (*len == 0) + return SOS_OK; + + while (1) + { + char c; + + /* Take the semaphore */ + sos_ksema_down (& t->sem, NULL); + + /* No keys available in the ring buffer, wait until the + add_chars callback wakes us up */ + if (t->buffer_read == t->buffer_write) + { + sos_ksema_up (& t->sem); + sos_kwaitq_wait (& t->wq, NULL); + + /* Go back to begining of loop: maybe someone else stole the + semaphore */ + continue; + } + + c = t->buffer[t->buffer_read]; + + /* Copy the received character from the ring buffer to the + destination buffer */ + ret = sos_memcpy_to_user (dest_buf, + (sos_vaddr_t) & t->buffer[t->buffer_read], + sizeof (char)); + if (sizeof(char) != ret) + { + *len = count; + sos_ksema_up (& t->sem); + return ret; + } + + dest_buf++; + + /* Update the ring buffer read pointer */ + t->buffer_read++; + if (t->buffer_read == TTY_BUFFER_LEN) + t->buffer_read = 0; + + count++; + + if (t->param & SOS_IOCTLPARAM_TTY_ECHO) + t->write (c); + + sos_ksema_up (& t->sem); + + /* Did we read enough bytes ? */ + if (count == *len + || (c == '\n' && t->param & SOS_IOCTLPARAM_TTY_CANON)) + break; + } + + *len = count; + return SOS_OK; +} + + +static sos_ret_t tty_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, sos_size_t *len) +{ + struct tty_device *t; + char c; + unsigned int i; + sos_ret_t ret; + + t = (struct tty_device *) this->custom_data; + + for (i = 0; i < *len; i++) + { + ret = sos_memcpy_from_user ((sos_vaddr_t) & c, src_buf, sizeof(char)); + if (sizeof(char) != ret) + { + *len = i; + return ret; + } + + sos_ksema_down (& t->sem, NULL); + t->write (c); + sos_ksema_up (& t->sem); + + src_buf++; + } + + return SOS_OK; +} + + +static sos_ret_t tty_ioctl(struct sos_fs_opened_file *this, int req_id, + sos_ui32_t req_arg) +{ + struct tty_device *t; + + if (req_arg != SOS_IOCTLPARAM_TTY_ECHO + && req_arg != SOS_IOCTLPARAM_TTY_CANON) + return -SOS_EINVAL; + + t = (struct tty_device *) this->custom_data; + + sos_ksema_down (& t->sem, NULL); + + switch (req_id) + { + case SOS_IOCTL_TTY_SETPARAM: + t->param |= req_arg; + break; + + case SOS_IOCTL_TTY_RESETPARAM: + t->param &= ~req_arg; + break; + + default: + sos_ksema_up (& t->sem); + return -SOS_EINVAL; + } + + sos_ksema_up (& t->sem); + + return SOS_OK; +} + +static sos_ret_t tty_open(struct sos_fs_node * fsnode, + struct sos_fs_opened_file * of, + void * chardev_class_custom_data) +{ + struct tty_device *t; + + t = tty_device_find (fsnode->dev_id.device_instance); + if (t == NULL) + return -SOS_ENOENT; + + sos_ksema_down (& t->sem, NULL); + of->custom_data = t; + t->open_count ++; + sos_ksema_up (& t->sem); + + return SOS_OK; +} + + +static sos_ret_t tty_close(struct sos_fs_opened_file *of, + void *custom_data) +{ + struct tty_device *t; + + t = (struct tty_device *) of->custom_data; + + sos_ksema_down (& t->sem, NULL); + t->open_count --; + sos_ksema_up (& t->sem); + + return SOS_OK; +} + +void tty_add_chars (struct tty_device *t, const char *s) +{ + sos_ksema_down (& t->sem, NULL); + while (*s) + { + /* Add all characters to the ring buffer */ + t->buffer[t->buffer_write] = *s; + t->buffer_write++; + if (t->buffer_write == TTY_BUFFER_LEN) + t->buffer_write = 0; + s++; + } + sos_ksema_up (& t->sem); + + /* Wake up a possibly waiting thread */ + sos_kwaitq_wakeup (& t->wq, SOS_KWQ_WAKEUP_ALL, SOS_OK); +} + +struct sos_chardev_ops tty_ops = { + .read = tty_read, + .write = tty_write, + .open = tty_open, + .close = tty_close, + .ioctl = tty_ioctl +}; + +sos_ret_t tty_create (sos_ui32_t device_instance, + sos_ret_t (*write_func) (char c), + struct tty_device **tty_out) +{ + struct tty_device *tty; + + if (tty_device_find (device_instance) != NULL) + return -SOS_EBUSY; + + tty = (struct tty_device *) sos_kmalloc (sizeof(struct tty_device), 0); + if (tty == NULL) + return -SOS_ENOMEM; + + memset (tty->buffer, 0, sizeof(tty->buffer)); + tty->open_count = 0; + tty->instance = device_instance; + tty->write = write_func; + tty->buffer_read = 0; + tty->buffer_write = 0; + tty->param = SOS_IOCTLPARAM_TTY_CANON; + sos_kwaitq_init(& tty->wq, "tty", SOS_KWQ_ORDER_FIFO); + sos_ksema_init(& tty->sem, "tty", 1, SOS_KWQ_ORDER_FIFO); + + list_add_tail (tty_device_list, tty); + + *tty_out = tty; + + return SOS_OK; +} + +sos_ret_t tty_remove (struct tty_device *tty) +{ + if (tty == NULL) + return -SOS_EINVAL; + + if (SOS_OK != sos_ksema_trydown (& tty->sem)) + return -SOS_EBUSY; + + if (tty->open_count != 0) + return -SOS_EBUSY; + + sos_kwaitq_dispose (& tty->wq); + list_delete (tty_device_list, tty); + + sos_kfree ((sos_vaddr_t) tty); + + return SOS_OK; +} + +sos_ret_t tty_subsystem_setup (void) +{ + list_init (tty_device_list); + return sos_chardev_register_class (SOS_CHARDEV_TTY_MAJOR, + & tty_ops, NULL); +} + +sos_ret_t tty_subsystem_cleanup (void) +{ + return sos_chardev_unregister_class (SOS_CHARDEV_TTY_MAJOR); +} + diff --git a/drivers/tty.h b/drivers/tty.h new file mode 100644 index 0000000..a41413b --- /dev/null +++ b/drivers/tty.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2005 David Decotigny, Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_TTY_H_ +#define _SOS_TTY_H_ + +#include + +struct tty_device; + +sos_ret_t tty_subsystem_setup (void); +sos_ret_t tty_subsystem_cleanup (void); + +sos_ret_t tty_create (sos_ui32_t device_instance, + sos_ret_t (*write_func) (char c), + struct tty_device **tty_out); +sos_ret_t tty_remove (struct tty_device *tty); + +/** + * @note Function called without synchronization + */ +void tty_add_chars (struct tty_device *t, const char *s); + +#endif + diff --git a/drivers/x86_videomem.c b/drivers/x86_videomem.c new file mode 100644 index 0000000..d6809bb --- /dev/null +++ b/drivers/x86_videomem.c @@ -0,0 +1,270 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include + +#include "x86_videomem.h" + +/* The text video memory starts at address 0xB8000. Odd bytes are the + ASCII value of the character, even bytes are attribute for the + preceding character. */ +#define VIDEO 0xb8000 + + +/* Console screen size */ +#define LINES 25 +#define COLUMNS 80 + +/* + * VGA ports and commands. + * + * @see Ralf Brown's interrupt (and port) list + * http://www-2.cs.cmu.edu/~ralf/files.html + */ + +/* VGA ports */ +#define VGA_COMMAND_PORT 0x3D4 +#define VGA_DATA_PORT 0x3D5 + +/* VGA commands */ +#define VGA_SET_CURSOR_START 0xA +#define VGA_SET_CURSOR_END 0xB +#define VGA_SET_CURSOR_HIGH 0xE +#define VGA_SET_CURSOR_LOW 0xF + +/** The structure of a character element in the video memory. @see + http://webster.cs.ucr.edu/AoA DOS edition chapter 23 */ +typedef struct { + unsigned char character; + unsigned char attribute; +} __attribute__ ((packed)) x86_video_mem[LINES*COLUMNS]; + + + +/** The base pointer for the video memory */ +static volatile x86_video_mem *video = (volatile x86_video_mem*)VIDEO; + +sos_ret_t sos_x86_videomem_setup(void) +{ + /* CRT index port => ask for access to register 0xa ("cursor + start") */ + outb(0x0a, VGA_COMMAND_PORT); + + /* (RBIL Tables 708 & 654) CRT Register 0xa => bit 5 = cursor OFF */ + outb(1 << 5, VGA_DATA_PORT); + + return SOS_OK; +} + + +sos_ret_t sos_x86_videomem_cls(unsigned char attribute) +{ + /* Clears the screen */ + int i; + for(i = 0 ; i < LINES*COLUMNS ; i++) + { + (*video)[i].character = 0; + (*video)[i].attribute = attribute; + } + + return SOS_OK; +} + + +sos_ret_t sos_x86_videomem_putstring(unsigned char row, unsigned char col, + unsigned char attribute, + const char *str) +{ + unsigned video_offs = row*COLUMNS + col; + + if (video_offs >= LINES*COLUMNS) + return -SOS_EINVAL; + + for ( ; str && *str && (video_offs < LINES*COLUMNS) ; str++, video_offs++) + { + (*video)[video_offs].character = (unsigned char)*str; + (*video)[video_offs].attribute = attribute; + } + + return SOS_OK; +} + + +sos_ret_t sos_x86_videomem_putchar(unsigned char row, unsigned char col, + unsigned char attribute, + unsigned char c) +{ + unsigned video_offs = row*COLUMNS + col; + + if (video_offs >= LINES*COLUMNS) + return -SOS_EINVAL; + + (*video)[video_offs].character = c; + (*video)[video_offs].attribute = attribute; + + return SOS_OK; +} + + +sos_ret_t sos_x86_videomem_printf(unsigned char row, unsigned char col, + unsigned char attribute, + const char *format, /* args */...) +{ + char buff[256]; + va_list ap; + + va_start(ap, format); + vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + return sos_x86_videomem_putstring(row, col, attribute, buff); +} + + +/* + * Console that supports scrolling, based on the low-level code + * above. This console only takes part of the screen, starting at row + * CONSOLE_ROW_START. The rows before that one are free for use by the + * kernel debugging messages. + */ + +/* Current row in the high-level console. Must be signed, because of + computations inside sos_screen_putchar() */ +static int row; + +/* Current column in the high-level console. Must be signed, because + of computations inside sos_screen_putchar() */ +static int col; + +/* The limit between the low-level console, accessible to the kernel, + and the high-level console, accessible to the user applications + through the sos_screen_putchar() function. */ +#define CONSOLE_ROW_START 12 + +static void sos_screen_set_cursor (unsigned int _row, unsigned int _col) +{ + unsigned int pos; + + pos = (_row * COLUMNS + _col); + + outb(VGA_SET_CURSOR_HIGH, VGA_COMMAND_PORT); + outb( (pos >> 8), VGA_DATA_PORT); + outb(VGA_SET_CURSOR_LOW, VGA_COMMAND_PORT); + outb( (pos & 0xFF), VGA_DATA_PORT); +} + +sos_ret_t sos_screen_putchar (char c) +{ + if (c == '\r') + { + /* Go to first row */ + col = 0; + } + + /* New line */ + else if (c == '\n') + { + /* Go to next line */ + col = 0; + row ++; + } + + /* Remove the last character */ + else if (c == '\b') + { + /* Next character should be displayed instead of the current + one */ + col --; + + /* Handle the case where we're at the beginning of a line */ + if (col < 0) + { + row --; + col = COLUMNS-1; + + if (row < CONSOLE_ROW_START) + { + row = CONSOLE_ROW_START; + col = 0; + } + } + + /* Replace the current character with a space */ + sos_x86_videomem_putchar + (row, col, SOS_X86_VIDEO_FG_BLUE | SOS_X86_VIDEO_BG_LTGRAY, ' '); + } + else if (c != 0) + { + sos_x86_videomem_putchar + (row, col, SOS_X86_VIDEO_FG_BLUE | SOS_X86_VIDEO_BG_LTGRAY, c); + col++; + if (col == COLUMNS) + { + col = 0; + row++; + } + } + + /* Need to scroll ? */ + if (row == LINES) + { + int i; + + /* Copy each line in the previous line */ + for (i = CONSOLE_ROW_START; i < LINES; i++) + memcpy ((char*) video + i * COLUMNS * 2, + (char*) video + ((i + 1) * COLUMNS * 2), + COLUMNS * 2); + + /* Reset the last line of the console */ + for (i = 0; i < COLUMNS; i++) + sos_x86_videomem_putchar + (LINES-1, i, SOS_X86_VIDEO_FG_BLUE | SOS_X86_VIDEO_BG_LTGRAY, ' '); + + row--; + } + + sos_screen_set_cursor (row, col); + + return SOS_OK; +} + +sos_ret_t sos_screen_init (void) +{ + int i, j; + + row = CONSOLE_ROW_START; + col = 0; + + /* Set the first scan line for the cursor, and the blinking + mode. First scan line is 11, so that we have a visible + cursor. */ + outb(VGA_SET_CURSOR_START, VGA_COMMAND_PORT); + outb(((0x2 << 5) | 14), VGA_DATA_PORT); + + for (i = CONSOLE_ROW_START; i < LINES; i++) + { + for (j = 0; j < COLUMNS; j++) + sos_x86_videomem_putchar + (i, j, SOS_X86_VIDEO_FG_BLUE | SOS_X86_VIDEO_BG_LTGRAY, ' '); + } + + sos_screen_set_cursor (row, col); + + return SOS_OK; +} diff --git a/drivers/x86_videomem.h b/drivers/x86_videomem.h new file mode 100644 index 0000000..02f789d --- /dev/null +++ b/drivers/x86_videomem.h @@ -0,0 +1,108 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_X86_VIDEOMEM_H_ +#define _SOS_X86_VIDEOMEM_H_ + +/** + * @file x86_videomem.h + * + * On x86 PC platforms, the text mode screen memory (and CGA/EGA/VGA + * too) is mapped into physical memory. This file handles access to + * this screen, supposed to be set in text-mode, through this memory + * area. All the functions below print the characters directly to the + * memory, without interpreting the escaped characters (such as \n, + * \r...) + */ + +#include + +/** + * x86 video attributes + * See http://webster.cs.ucr.edu/AoA/DOS/ch23/CH23-1.html + */ +/* Normal and Dark/Light foreground */ +#define SOS_X86_VIDEO_FG_BLACK 0 +#define SOS_X86_VIDEO_FG_DKGRAY 8 +#define SOS_X86_VIDEO_FG_BLUE 1 +#define SOS_X86_VIDEO_FG_LTBLUE 9 +#define SOS_X86_VIDEO_FG_GREEN 2 +#define SOS_X86_VIDEO_FG_LTGREEN 10 +#define SOS_X86_VIDEO_FG_CYAN 3 +#define SOS_X86_VIDEO_FG_LTCYAN 11 +#define SOS_X86_VIDEO_FG_RED 4 +#define SOS_X86_VIDEO_FG_LTRED 12 +#define SOS_X86_VIDEO_FG_MAGENTA 5 +#define SOS_X86_VIDEO_FG_LTMAGENTA 13 +#define SOS_X86_VIDEO_FG_BROWN 6 +#define SOS_X86_VIDEO_FG_YELLOW 14 +#define SOS_X86_VIDEO_FG_LTGRAY 7 +#define SOS_X86_VIDEO_FG_WHITE 15 +/* Background */ +#define SOS_X86_VIDEO_BG_BLACK (0 << 4) +#define SOS_X86_VIDEO_BG_BLUE (1 << 4) +#define SOS_X86_VIDEO_BG_GREEN (2 << 4) +#define SOS_X86_VIDEO_BG_CYAN (3 << 4) +#define SOS_X86_VIDEO_BG_RED (4 << 4) +#define SOS_X86_VIDEO_BG_MAGENTA (5 << 4) +#define SOS_X86_VIDEO_BG_BROWN (6 << 4) +#define SOS_X86_VIDEO_BG_LTGRAY (7 << 4) +/* Blinking */ +#define SOS_X86_VIDEO_FG_BLINKING (1 << 7) + + +/** Setup the video RAM mapping and clear the screen */ +sos_ret_t sos_x86_videomem_setup(void); + +/** Clears the screen and set the background color as given by + attribute */ +sos_ret_t sos_x86_videomem_cls(unsigned char attribute); + +/** Print the string on the scren with the given attribute. Does not + handle scrolling */ +sos_ret_t sos_x86_videomem_putstring(unsigned char row, unsigned char col, + unsigned char attribute, + const char *str); + +/** Print the character on the scren with the given attribute. Does not + handle scrolling */ +sos_ret_t sos_x86_videomem_putchar(unsigned char row, unsigned char col, + unsigned char attribute, + unsigned char c); + +/** + * Print the formatted string. Very restricted version of printf(3): + * 1/ can print max 255 chars, 2/ supports only %d/%i, %c, %s, %x + * without any support for flag charachters (eg %08x). + */ +sos_ret_t sos_x86_videomem_printf(unsigned char row, unsigned char col, + unsigned char attribute, + const char *format, /* args */...) + __attribute__ ((format (printf, 4, 5))); + + +/** + * Print a character on the console (support scrolling) + */ +sos_ret_t sos_screen_putchar (char c); + +/** + * Initialize the console that supports scrolling + */ +sos_ret_t sos_screen_init (void); + +#endif /* _SOS_X86_VIDEOMEM_H_ */ diff --git a/drivers/zero.c b/drivers/zero.c new file mode 100644 index 0000000..b6e997e --- /dev/null +++ b/drivers/zero.c @@ -0,0 +1,452 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zero.h" + + +/** + * A mapped page for a shared mapping of /dev/zero + */ +struct zero_mapped_page +{ + sos_uoffset_t page_id; + sos_paddr_t ppage_paddr; + + struct zero_mapped_page *prev, *next; +}; +/** The Slab cache of shared mapped pages */ +struct sos_kslab_cache * cache_of_zero_mapped_pages; + + +/** + * A mapped /dev/zero resource + */ +struct zero_mapped_resource +{ + int ref_cnt; + + /** + * For shared mappings: the list of shared pages mapped inside one + * or multiple VRs + */ + struct zero_mapped_page *list_mapped_pages; + + struct sos_umem_vmm_mapped_resource mr; +}; + + +/** Forward declaration: the FS operation for the /dev/zero character + device */ +static struct sos_chardev_ops dev_zero_fs_ops; + + +/** Helper function to insert the given physical page in the list of + physical pages used for shared anonymous mappings */ +static sos_ret_t insert_anonymous_physpage(struct zero_mapped_resource *mr, + sos_paddr_t ppage_paddr, + sos_uoffset_t page_id); + + +/** Helper function to insert the given physical page in the list of + physical pages used for shared anonymous mappings */ +static sos_paddr_t lookup_anonymous_physpage(struct zero_mapped_resource *mr, + sos_uoffset_t page_id); + + +sos_ret_t sos_dev_zero_subsystem_setup() +{ + sos_ret_t retval; + + cache_of_zero_mapped_pages = + sos_kmem_cache_create("shared anonymous mappings", + sizeof(struct zero_mapped_page), + 1, 0, + SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); + if (! cache_of_zero_mapped_pages) + return -SOS_ENOMEM; + + retval = sos_chardev_register_class(SOS_CHARDEV_ZERO_MAJOR, + & dev_zero_fs_ops, + NULL); + if (SOS_OK != retval) + { + sos_kmem_cache_destroy(cache_of_zero_mapped_pages); + return retval; + } + + return SOS_OK; +} + + +/** Called after the virtual region has been inserted inside its + address space */ +static void zero_ref(struct sos_umem_vmm_vr * vr) +{ + /* Retrieve the 'zero' structure associated with the mapped resource */ + struct zero_mapped_resource * zero_resource; + zero_resource + = (struct zero_mapped_resource*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Increment ref counter */ + zero_resource->ref_cnt ++; +} + + +/** Called when the virtual region is removed from its address + space */ +static void zero_unref(struct sos_umem_vmm_vr * vr) +{ + /* Retrieve the 'zero' structure associated with the mapped resource */ + struct zero_mapped_resource * zero_resource; + zero_resource + = (struct zero_mapped_resource*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Decrement ref coutner */ + SOS_ASSERT_FATAL(zero_resource->ref_cnt > 0); + zero_resource->ref_cnt --; + + /* Free the resource if it becomes unused */ + if (zero_resource->ref_cnt == 0) + { + /* Delete the list of anonymous shared mappings */ + struct zero_mapped_page *zmp; + list_collapse(zero_resource->list_mapped_pages, zmp) + { + /* Unreference the underlying physical page */ + sos_physmem_unref_physpage(zmp->ppage_paddr); + sos_kfree((sos_vaddr_t)zmp); + } + + sos_kfree((sos_vaddr_t)zero_resource); + } +} + + +/** MOST IMPORTANT callback ! Called when a thread page faults on the + resource's mapping */ +static sos_ret_t zero_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + sos_ret_t retval = SOS_OK; + sos_paddr_t ppage_paddr; + sos_uoffset_t required_page_id; + struct zero_mapped_resource * zero_resource; + sos_ui32_t vr_prot, vr_flags; + + /* Retrieve the 'zero' structure associated with the mapped resource */ + zero_resource + = (struct zero_mapped_resource*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Retrieve access rights/flags of the VR */ + vr_prot = sos_umem_vmm_get_prot_of_vr(vr); + vr_flags = sos_umem_vmm_get_flags_of_vr(vr); + + /* Identifies the page in the mapping that's being paged-in */ + required_page_id = SOS_PAGE_ALIGN_INF(uaddr) + - sos_umem_vmm_get_start_of_vr(vr) + + sos_umem_vmm_get_offset_in_resource(vr); + + /* For shared mappings, check if there is a page already mapping the + required address */ + if (vr_flags & SOS_VR_MAP_SHARED) + { + ppage_paddr = lookup_anonymous_physpage(zero_resource, required_page_id); + if (NULL != (void*)ppage_paddr) + { + retval = sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + vr_prot); + + return retval; + } + } + + /* For write accesses, directly maps a new page. For read accesses, + simply map in the zero_page (and wait for COW to handle the next + write accesses) */ + if (write_access) + { + /* Allocate a new page for the virtual address */ + ppage_paddr = sos_physmem_ref_physpage_new(FALSE); + if (! ppage_paddr) + return -SOS_ENOMEM; + + retval = sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + vr_prot); + if (SOS_OK != retval) + { + sos_physmem_unref_physpage(ppage_paddr); + return retval; + } + + memset((void*)SOS_PAGE_ALIGN_INF(uaddr), 0x0, SOS_PAGE_SIZE); + + /* For shared mappings, add the page in the list of shared + mapped pages */ + if (vr_flags & SOS_VR_MAP_SHARED) + insert_anonymous_physpage(zero_resource, ppage_paddr, + required_page_id); + + sos_physmem_unref_physpage(ppage_paddr); + } + else + { + /* Map-in the zero page in READ ONLY whatever the access_rights + or the type (shared/private) of the VR to activate COW */ + retval = sos_paging_map(sos_zero_physpage, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + SOS_VM_MAP_PROT_READ); + } + + return retval; +} + + +/** The callbacks for a mapped /dev/zero resource */ +static struct sos_umem_vmm_vr_ops zero_ops = (struct sos_umem_vmm_vr_ops) +{ + .ref = zero_ref, + .unref = zero_unref, + .page_in = zero_page_in, + .unmap = NULL +}; + + +/** The callback that gets called when the resource gets mapped */ +static sos_ret_t zero_mmap(struct sos_umem_vmm_vr *vr) +{ + return sos_umem_vmm_set_ops_of_vr(vr, &zero_ops); +} + + +/** The function responsible for mapping the /dev/zero resource in + user space */ +sos_ret_t sos_dev_zero_map(struct sos_umem_vmm_as * dest_as, + sos_uaddr_t *uaddr, + sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags) +{ + sos_ret_t retval; + struct zero_mapped_resource * zero_resource; + + zero_resource + = (struct zero_mapped_resource*) sos_kmalloc(sizeof(*zero_resource), 0); + if (! zero_resource) + return -SOS_ENOMEM; + + memset(zero_resource, 0x0, sizeof(*zero_resource)); + zero_resource->mr.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + zero_resource->mr.flags |= SOS_MAPPED_RESOURCE_ANONYMOUS; + zero_resource->mr.custom_data = zero_resource; + zero_resource->mr.mmap = zero_mmap; + + retval = sos_umem_vmm_map(dest_as, uaddr, size, + access_rights, flags, + &zero_resource->mr, 0); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t)zero_resource); + return retval; + } + + return SOS_OK; +} + + +static sos_ret_t insert_anonymous_physpage(struct zero_mapped_resource *mr, + sos_paddr_t ppage_paddr, + sos_uoffset_t page_id) +{ + struct zero_mapped_page * zmp + = (struct zero_mapped_page*)sos_kmem_cache_alloc(cache_of_zero_mapped_pages, + 0); + if (! zmp) + return -SOS_ENOMEM; + + zmp->page_id = page_id; + zmp->ppage_paddr = ppage_paddr; + + list_add_head(mr->list_mapped_pages, zmp); + sos_physmem_ref_physpage_at(ppage_paddr); + return SOS_OK; +} + + +static sos_paddr_t lookup_anonymous_physpage(struct zero_mapped_resource *mr, + sos_uoffset_t page_id) +{ + struct zero_mapped_page * zmp; + int nb_elts; + + list_foreach_forward(mr->list_mapped_pages, zmp, nb_elts) + { + if (zmp->page_id == page_id) + return zmp->ppage_paddr; + } + + return (sos_paddr_t)NULL; +} + +/* + * /dev/zero character device FS operations + */ + +static sos_ret_t dev_zero_fs_open(struct sos_fs_node * fsnode, + struct sos_fs_opened_file * of, + void * chardev_class_custom_data) +{ + /* Make sure the device instance is known to the driver */ + if ( (SOS_CHARDEV_NULL_MINOR != fsnode->dev_id.device_instance) + && (SOS_CHARDEV_ZERO_MINOR != fsnode->dev_id.device_instance) ) + return -SOS_ENODEV; + + return SOS_OK; +} + + +static sos_ret_t dev_zero_fs_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + /* Artificiallly update the position in the "file" */ + sos_lsoffset_t ref_offs; + + *result_position = this->position; + switch (whence) + { + case SOS_SEEK_SET: + ref_offs = 0; + break; + + case SOS_SEEK_CUR: + ref_offs = this->position; + break; + + case SOS_SEEK_END: + return -SOS_ENOSUP; + break; + + default: + return -SOS_EINVAL; + } + + if (offset < -ref_offs) + return -SOS_EINVAL; + + this->position = ref_offs + offset; + *result_position = this->position; + return SOS_OK; +} + + +static sos_ret_t dev_zero_fs_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + sos_size_t offs, rdlen; + + /* Reading /dev/null returns immediately */ + if (SOS_CHARDEV_NULL_MINOR == fsnode->dev_id.device_instance) + { + *len = 0; + return SOS_OK; + } + + /* ZERO the destination buffer using the zero page (by page_size + increments) */ + for (rdlen = offs = 0 ; offs < *len ; offs += SOS_PAGE_SIZE) + { + sos_ret_t retval; + sos_size_t memcpy_len = SOS_PAGE_SIZE; + if (offs + memcpy_len > *len) + memcpy_len = *len - offs; + + retval = sos_memcpy_to_user(dest_buf + offs, sos_zero_kernelpage, + memcpy_len); + if (retval < 0) + break; + + rdlen += retval; + if (retval != (sos_ret_t)memcpy_len) + break; + } + + /* Artificiallly update the position in the "file" */ + *len = rdlen; + this->position += rdlen; + return SOS_OK; +} + + +static sos_ret_t dev_zero_fs_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + /* Artificiallly update the position in the "file" */ + this->position += *len; + return SOS_OK; +} + + +static sos_ret_t dev_zero_fs_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + return sos_dev_zero_map(sos_process_get_address_space(this->owner), + uaddr, size, access_rights, flags); +} + + +static struct sos_chardev_ops dev_zero_fs_ops + = (struct sos_chardev_ops) { + .open = dev_zero_fs_open, + .close = NULL, + .seek = dev_zero_fs_seek, + .read = dev_zero_fs_read, + .write = dev_zero_fs_write, + .mmap = dev_zero_fs_mmap, + .fcntl = NULL, + .ioctl = NULL + }; diff --git a/drivers/zero.h b/drivers/zero.h new file mode 100644 index 0000000..8c518ae --- /dev/null +++ b/drivers/zero.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_DEV_ZERO_H_ +#define _SOS_DEV_ZERO_H_ + +/** + * "Driver" to map /dev/zero in user space + */ + + +#include + + +/** + * Register the /dev/zero and /dev/null devices + */ +sos_ret_t sos_dev_zero_subsystem_setup(void); + + +/** + * Map /dev/zero into user space + * @note usage RESTRICTED to the kernel for start_init and the exec + * syscall + */ +sos_ret_t sos_dev_zero_map(struct sos_umem_vmm_as * dest_as, + sos_uaddr_t *uaddr, + sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags); + +#endif /* _SOS_EXEC_ELF32_H_ */ diff --git a/extra/Makefile b/extra/Makefile new file mode 100644 index 0000000..3d30726 --- /dev/null +++ b/extra/Makefile @@ -0,0 +1,46 @@ +OBJCOPY=objcopy +LIBGCC := $(shell $(CC) -print-libgcc-file-name) # To benefit from FP/64bits artihm. +EXTRA := $(shell [ -f ../userland/userprogs.kimg ] && echo ../userland/userprogs.kimg) + +all: sos_qemu.img termslave + +-include ../.mkvars + +# The image is the simple concatenation of the boot sector and the kernel +# It may be use in bochs or on a real floppy, but NOT in qemu (see below) +sos_bsect.img: bsect.bin sos.bin + cat $^ > $@ + @echo "You can use the $@ image in bochs or on a real floppy (NOT qemu)" + +# For qemu, the trick is to tell it we have *more* than 1440 sectors (720kB). +# Rtherwise the qemu disk geometry will be configured to be that of a 720kB +# floppy, while our boot sector assumes it to be 1.44MB +sos_qemu.img: sos_bsect.img + # Padding with 0s after the bsect/kernel image + cat $< /dev/zero | dd of=$@ bs=1k count=1440 + @echo "You can use the $@ image in qemu, bochs, or on a real floppy" + +# we extract the boot sector from the main ELF binary +bsect.bin: sos_bsect.elf + $(OBJCOPY) -v -O binary -j .bootsect $< $@ + +# we extract the kernel code from the main ELF binary +sos.bin: sos_bsect.elf + $(OBJCOPY) -v -O binary -R .bootsect $< $@ + +# The main ELF binary contains the boot sector and the kernel code +# linked together (hence we deal with a SINGLE image that we split +# above) because they share some symbol definitions +sos_bsect.elf: bootsect.o compile_kernel + $(LD) --warn-common -T ./sos_bsect.lds -o $@ \ + bootsect.o $(wildcard ../hwcore/*.o ../drivers/*.o ../sos/*.o)\ + $(EXTRA) $(LIBGCC) + +compile_kernel: + $(MAKE) -C .. + +termslave: termslave.c + cc -Wall -o $@ $< + +clean: + $(RM) *.img *.elf *.bin *~ *.o *.out termslave diff --git a/extra/README b/extra/README new file mode 100644 index 0000000..05675c3 --- /dev/null +++ b/extra/README @@ -0,0 +1,85 @@ + +Contents of the extra/ directory +================================ + +Data and configuration files to support generation of sos on non-x86 +and/or grub-less hosts: + - dot.mkvars: file to copy as .mkvars in the root directory to + compile on a non-x86 host, and to generate the grub floppy image on + a grub-less host + - grub.img.gz: compressed image of a Grub floppy (without any + kernel). Used by dot.mkvars. + - mtoolsrc: file needed by .mkvars to compile a the floppy image + +Support of a sos-specific boot sector: + - Makefile: rules to compile sos_bsect.img, the floppy image with the + boot sector and the Sos + - bootsect.S: x86 Sos boot sector (GNU as). Depends on sos_bsect.lds + - sos_bsect.lds: ld script to bind the boot sector with the remaining + of the kernel + +Misc: + - patch-qemu-port-e9.diff: patch over qemu to support the bochs "port + 0xe9 hack" + - patch-qemu-pty.diff: patch over qemu to fix a bug related to the + handling of the "-monitor pty" and "-serial pty" options + - termslave.c: Linux program to dial with qemu's monitor (or serial + line) from within a terminal. See comments in the beginning + + +What you can do with these files +================================ + + +*** Compile SOS from another architecture: +------------------------------------------ + - compile a cross-compiler for the i586-gnu target. This involves + compiling the binutils and gcc. Here are example configuration + options for them: + binutils (replace sparc-cun-solaris with your arch): + ../binutils-2.13/configure --prefix=/udd/ddecotig/temp_dd/xgcc/host-sparc-solaris7/stow/binutils-2.11 --host=sparc-sun-solaris2.7 i586-gnu + make && make install + gcc (ditto): + CFLAGS="-O2 -Dinhibit_libc" ../gcc-3.2/configure --target=i586-gnu --prefix=/udd/ddecotig/temp_dd/xgcc/host-sparc-solaris7/stow/gcc-3.2 --with-as=/udd/ddecotig/temp_dd/xgcc/host-sparc-solaris7/bin/as --with-ld=/udd/ddecotig/temp_dd/xgcc/host-sparc-solaris7/bin/ld --with-gnu-as --with-gnu-ld --enable-languages=c --disable-shared --disable-multilib --disable-nls --enable-threads=single + make && make install + - compile the mtools + - copy dot.mkvars to the root directory of SOS, as ".mkvars" + - customize the CC/LD/... variables to suit your cross-compiler + installatioon + - now you may run make from the SOS root directory, it should + generate the Grub boot floppy image. The following warning is + normal: + .mkvars:16: attention : écrasement des commandes pour la cible « grub-sos.img » + Makefile:92: attention : anciennes commandes ignorées pour la cible « grub-sos.img » + + +*** To compile SOS from an x86 where grub is not or incorrectly installed: +-------------------------------------------------------------------------- + - copy dot.mkvars to the root directory of SOS, as ".mkvars" + - customize the CC/LD/... variables to suit your cross-compiler + installatioon + - now you may run make from the SOS root directory, it should + generate the Grub boot floppy image. The following warning is + normal: + .mkvars:16: attention : écrasement des commandes pour la cible « grub-sos.img » + Makefile:92: attention : anciennes commandes ignorées pour la cible « grub-sos.img » + + +*** To compile SOS with its own bootloader: +------------------------------------------- + - for cross-architecture compilation: see above + - cd to this extra/ directory + - run 'make' + - the floppy image is: sos_bsect.img for use with bochs or on a real + floppy disk + to use the image under qemu: use sos_qemu.img + + NOTE : From article 2 onward, be warned that using this bootsect + might lead to system crashes. This would be because the + solution we use to retrieve the RAM size might not work + properly on some systems (BIOS buggy or more than 1G RAM). THE + best way to boot SOS is always to use Grub. + + +-- +David Decotigny diff --git a/extra/bootsect.S b/extra/bootsect.S new file mode 100644 index 0000000..2d5c331 --- /dev/null +++ b/extra/bootsect.S @@ -0,0 +1,438 @@ + +/* + * @(#) $Id: bootsect.S 1310 2005-08-13 13:47:31Z d2 $ + * Description : Bootsecteur en syntaxe AT&T + * Auteurs : Thomas Petazzoni & Fabrice Gautier & Emmanuel Marty + * Jerome Petazzoni & Bernard Cassagne & coffeeman + * David Decotigny (SOS integration for kernel size detection) + * Christopher Goyet (RAM size determination through BIOS int 15H) + * Bug reports to kos-misc@enix.org + */ + +/* + * But global de ce bootsecteur : + * + * - Initialiser la becane + * - Charger le kernel + * - Passer en mode protege + * - Executer le kernel + * + * Taille restante : Je vous rappelle qu'un bootsecteur ne peut faire + * qu'au maximum 512 octets dont 2 octets obligatoires 0xAA55. Sur + * les 510 octets reellement utilisables, il reste 3 octets dispo (60 + * si on decide d'enlever le BPB un jour) !!! + * + * thomas_petazzoni : - detection des codes d'erreurs de chargement + * David_Decotigny : - Passage en GNU as + * David_Decotigny : - Chargement du noyau au-dela du 1er Mega (taille + * max = 0x9e000 octets = 632ko), pour avoir le + * meme noyau sous grub et avec le bootsecteur + */ + + /* + * Sequence d'operations : + * - Le BIOS charge le bootsect en 0x7c00 (BOOT_ADRESS). On choisit + * la representation 0x7c0:0000 pour que le .org 0 reste valide + * - Le bootsect se deplace de lui-meme en 0x9f000 (COPY_ADRESS). On + * choisit la representation 0x9f00:0000 pour que le .org 0 reste + * valide + * - Le bootsect verifie que le processeur est du type 386+ + * - Il charge le noyau depuis la disquette en memoire a partir de + * 0x1000 (LOAD_ADRESS). La place dispo est donc 0x9f000 - 0x1000 , soit + * 0x9E000, soit encore 1264 secteurs de 512 octets + * - Il passe en pmode flat (apres ouverture a20) + * - Il recopie le noyau (situe en LOAD_ADRESS) vers son adresse + * finale (FINAL_ADDRESS = 2Mo). La recopie se fait sur tout l'espace + * LOAD_ADRESS ---> COPY_ADRESS, c'est a dire sur 0x9e000 octets = + * 632ko. Le noyau peut donc au max faire 632ko. Le nombre max de + * secteurs de disquette qu'on peut charger est donc 1264 + */ + + +/* La taille de la pile */ +#define BOOT_STACK_SIZE 0x4000 + + .file "bootsect.S" + + /* Tout est place dans une seule section */ + .section ".bootsect" + + /* L'essentiel du bootsector (sauf les 1eres instructions) + sont a un offset 0. On fait en sorte que le compilo soit + d'accord la-dessus. Quand on a des adresse realm exotiques + (0x7c00, 0x9f000, ...), on s'arrange toujours pour avoir un + offset de 0 => on choisira le segment adapte (0x7c0, + 0x9f00, ...). Il ne faut pas oublier le ld -Ttext 0 */ + .org 0 + + /* Pour que gas genere du 16bits, afin que ca marche en realm */ + .code16 + +/* + * Parametres de la disquette. Comme c'est chiant de faire une + * procedure de detection auto, et que ca prend de la place, on fait + * ca "a la main". Par exemple, une DD 720 Ko a 9 secteurs/piste, une + * 1.44 Mo a 18 secteurs/pistes + */ +#define CYLS 80 +#define HEADS 1 +#define SECTS 18 + +#define BOOT_ADRESS 0x07C00 /* Adresse de demarrage (lineaire) */ +#define BOOT_SEG (BOOT_ADRESS>>4) /* Segment de Boot */ +#define BOOT_SIZE 512 /* Taille bu bootsecteur */ +#define COPY_ADRESS 0x9F000 /* La ou on va copier le + bootsecteur (lineaire) */ +#define COPY_SEG (COPY_ADRESS>>4) /* Segment de la ou on va + copier le bootsecteur */ +#define LOAD_ADRESS 0x01000 /* 1er chargement du systeme */ +#define LOAD_SEG (LOAD_ADRESS>>4) /* Segment du 1er chargement du */ +#define MAX_KERN_LEN (COPY_ADRESS-LOAD_ADRESS) /* Taille noyau maxi */ +#define MAX_KERN_SECTS ((MAX_KERN_LEN + 511) >> 9) /* Nbre de secteurs maxi */ + +/* IMPORTANT : Cette valeur DOIT etre identique a l'adresse presente + dans sos.lds ! */ +#define FINAL_ADDRESS 0x200000 /* Adresse finale (physique de 0 a 4G) + ou est charge le noyau */ + +#define OP16 .byte 0x66 ; +#define OP32 .byte 0x66 ; + +/* + * Procedure qui vide le buffer clavier. + */ +#define WAITKB \ + 1: ;\ + .word 0xeb ;\ + .word 0xeb ;\ + inb $0x64, %al ;\ + andb $0x2, %al ;\ + jnz 1b + + /* Le point d'entree dans le bootsect */ +.globl _bsect +_bsect: + + /* + * La portion qui suit est situee a un offset 0x7c00 en + * memoire. Attention donc aux references memoire dans cette + * partie. On choisit de rester en offset 0 (.org 0), mais on + * charge correctement les segments a 0x7c0. + */ + + movw $BOOT_SEG, %ax /* le bootsecteur est a 0x7C00 en lineaire */ + movw %ax, %ds /* on le copie a l'adresse COPY_ADRESS */ + xorw %si, %si /* comme cette adresse est la plus haute de la mem */ + xorw %di, %di /* on pourra charger un kernel + gros */ + movw $(BOOT_SIZE>>1), %cx + movw $COPY_SEG, %ax + movw %ax, %es + cld + rep ; movsw + + /* on continue a executer le bootsecteur, mais maintenant a + partir de 0x9F000, qu'on represente sous la forme + 0x9f00:offset */ + ljmp $COPY_SEG, $here + + /* + * A partir de maintenant, on est a un offset 0 en memoire + * (segment 0x9f00), conformement a ce que veut le compilo. + */ +here: + movw %ax, %ds + + /* Petite pile temporaire (1k - 3.84k en RAM ; les adresses 0-1k + correspondent au vecteur d'interruptions). */ + movw %ax, %ss + movw $(LOAD_ADRESS - 0x10), %sp + + /* Efface l'ecran */ + movb $0x0, %ah + movb $0x3, %al + int $0x10 + + /* Verifie que le noyau n'est pas trop gros a charger */ + cmpw $(MAX_KERN_SECTS), (load_size) + jb sizeOk + movw $toobig, %si + call message + call halt + +sizeOk: + /* Recupere la taille de la RAM */ + mov $0xE801, %ax + int $0x15 + movw %ax, (memsize1) + movw %bx, (memsize2) + + /* Affiche les messages d'attente */ + movw $loadkern, %si + call message + movw $check, %si + call message + +check386: + /* + * la attention, plus complexe : on teste si le proc est un + * 386+ pour cela, on va essayer de modifier les bits 12 ? 14 + * du registre E-flag si la modification reste, alors le proc + * est un 386+, sinon, c'est =< 286 + * + * Merci a Emmanuel Marty pour la compatibilite avec les 386 + * "pre-jurassique" + */ + + pushf /* on sauvegarde le E-Flag */ + movb $0x70, %ah + pushw %ax + popf + pushf + popw %ax + orb %ah, %ah + je no386 /* si la modif n'est pas valable, alors on saute a + no386 */ + popf /* on les restaure ? la fin ... */ + + /* Message de confirmation de 386+ et d'attente */ + movw $found386, %si + call message + movw $loading, %si + call message + +/* Copie du noyau disquette => RAM a partir de 0x1000 + L'adresse de destination est définie par es:0, où es vaut + initialement 0x100 (ie correspond alors à l'adresse 256*16, soit 4 + ko). Chaque itération incrémente ce registre es de 32, ce qui + correspond à un bond de 32*16 en mémoire, soit la taille d'un + secteur. De cette façon, puisqu'on joue sur les segments plutôt que + sur les offsets, la taille du noyau n'est pas limitée à 64 ko. Elle + est limitée par contre à la taille de la mémoire disponible sous + les 1Mo, \ie 640 ko (0x9f000 - 0x1000). */ +copyKernel: + /* Chargement du noyau en LOAD_SEG:0 */ + /* 3 iterateurs : + - load_size : le nbre de secteurs a charger + - cl : le secteur ou on en est pour le + cylindre en cours (<= SECTS) + - dh : la tete en cours (0/1) + */ + movb $0, %dl + movw $LOAD_SEG, %ax + movw %ax, %es + + xorw %bx, %bx + xorw %dx, %dx + movw $1, %cx /* premier secteur */ + +.nextsector: /* prochain secteur */ + incb %cl /* en incrementant CL */ + cmpb $SECTS, %cl /* si CL =< SECTS (=nbre de secteurs/pistes) + alors on charge */ + jbe .sector + movb $1, %cl /* sinon on revient au secteur 1 */ + incb %dh /* mais sur l'autre tete */ + cmpb $1, %dh /* on recompare, si DH =< 1 */ + je .sector /* on charge */ + movb $0, %dh /* sinon on repasse a la tete 0 */ + incb %ch /* mais on change de cylindre */ + +.sector: + pushw %es + movw $0x0201, %ax /* service 0x2, chargement 0x1 seecteur */ + int $0x13 /* Go ! */ + jc halt /* erreur */ + popw %ax + addw $32, %ax /* on a charge un secteur, donc on doit + charger 512 bytes plus loin */ + movw %ax, %es /* on avance donc le segment du buffer de + 32bytes, ie 1 secteur en RAM (car 32*16=512) */ + + movw $(0x0E*256+'.'), %ax /* affiche un point */ + int $0x10 + + decw (load_size) /* et on repart pour le prochain secteur + tant qu'on n'a pas fini ! */ + jnz .nextsector + +after: + movw $0x03f2, %dx + inb %dx, %al /* stoppe le moteur */ + andb $0x0f, %al + outb %al, %dx + + cli /* on interdit les interruptions */ + +fincopie: + pushw %cs + popw %ds + + /* on ouvre la porte A20 */ + WAITKB /* on vide le buffer */ + movb $0xd1, %al /* on met a jour le port */ + outb %al, $0x64 + WAITKB + movb $0xdf, %al /* bit 2 = ouverture/fermeture */ + outb %al, $0x60 + + /* + * init gdt + */ +InitGDT: + /* Préparation du flat mode */ + lgdt gdtr + +GoPMode: + /* Passage en mode protégé */ + movl %cr0, %eax + orb $1, %al /* set PE bit to 1 */ + movl %eax, %cr0 + + /* we are not yet in Pmode jump 'in' pmode clearing prefetch + * queue and loading a new selector */ + movw $0x10, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + +/* + * Code 32 bits ============================================================ + */ + .code32 + +JumpToHere32: /* Se deplace a l'endroit actuel, en passant en 32bits + et en utilisant la gdt, et vide la prefetch queue */ + .byte 0x66 /* Prefixe 32bits : en realite, jusqu'au jmp, on est + encore en 16 bits */ + ljmp $0x8, $(COPY_ADRESS+(Here32)) +Here32: + /* Et voila : On est en 32 bits vrai */ + +MoveKernelToFinalAddr: /* Deplace le noyau (en LOAD_ADDRESS) vers sa + destination finale (FINAL_ADDRESS) */ + movl $0x10, %eax + movl %eax, %ds /* Seg Src = DSeg */ + movl %eax, %es /* Sed Dest = DSeg */ + cld + movl $LOAD_ADRESS, %esi /* On commence la copie au debut du noyau */ + movl $FINAL_ADDRESS, %edi /* On copie vers cette adresse */ + movl $MAX_KERN_LEN, %ecx /* Taille recopie */ + shrl $2, %ecx + rep + movsl + +LaunchKernel: + /* Met en place une pile au niveau du symbole "stack" */ + movl %eax, %ss + movl $(stack + BOOT_STACK_SIZE), %ebp + movl %ebp, %esp + +/* passe les arguments a sos */ + xor %eax, %eax + xor %ebx, %ebx + movw (COPY_ADRESS+(memsize2)), %ax /*eax = num de block de 64KB apres 16MB*/ + movw (COPY_ADRESS+(memsize1)), %bx /*ebx = num de block de 1KB entre 1MB et 16MB*/ + movl $0x40, %ecx /*ecx=64 */ + mul %ecx + add %ebx, %eax + pushl %eax /* valeur de addr */ + pushl $0x42244224 /* valeur de magic pour indiquer qu'on a pousse + la taille de la RAM sur la pile */ + pushl $0 /* normalement call fait un push eip, mais la on a un jmp*/ + + /* Saut vers le noyau. La GDT est en place (flat mode), les + * selecteurs aussi, a20 est ouverte, et les interruptions sont + * cli + pas de idt. Le PIC n'est pas programme */ + ljmp $0x8, $sos_main + +/* + * Utilities ============================================================ + */ + .code16 + +message: + lodsb /* charge ds:si dans al et incremente si */ + orb %al, %al /* si al = 0 */ + jz 1f + movb $0x0e, %ah /* service 0Eh (affichage d'un caractere) */ + movw $0x0007, %bx /* Parametres : blanc sur fond noir */ + int $0x10 /* Appel de l'interruption 10h */ + jmp message /* On repart au début ... */ + 1: ret /* si la chaine est finie alors on retourne + dans la fonction appelante */ + +halt: + pushw %cs + popw %es + movw $haltmsg, %si + call message + cli + 1: jmp 1b + ret + +no386: + movw $need386, %si + call message + call halt + + /* + * GDT + */ + +gdt: +gdtr: +NULL_Desc: + .word (EndGDT)-(gdt)-1 /* Taille GDT */ + .long (gdt)+COPY_ADRESS +unused: + .word 0 + +CS_Desc: /* 0x8 */ + .word 0xFFFF, 0 + .byte 0, 0x9B, 0xCF, 0 + +DS_Desc: /* 0x10 */ + .word 0xFFFF, 0 + .byte 0, 0x93, 0xCF, 0 + +EndGDT: + + /* quelques messages */ + +loadkern: .string "This is SOS\r\n" +toobig: .string "Image too big\r\n" +check: .string "Checking 386+ processor... " +found386: .string " [OK]\r\n" +need386: .string " [FAILED]\r\n" +diskerror: .string "Disk Error\r\n" +loading: .string "Loading... " +haltmsg: .string "System Halted\r\n" + + /* Variables pour stocker la taille de la RAM (int 0x15) */ +memsize1: .long 0 +memsize2: .long 0 + +/*** Les code/données du boot secteur se terminent ICI. le marqueur de + * fin (aa55) est ajouté automatiquement par le script ld + * sos_bsect.lds ***/ + +/* La pile de 16k qu'on utilise au niveau de LaunchKernel se trouve + declaree avec le noyau, dans sa section ".init_stack", cad HORS du boot + secteur ! (sinon ca depasserait 512B, forcément). On aurait pu la + définir directement dans le sos_bsect.lds, ou dans un fichier .c + auxiliaire pour plus de clarté */ +/* Here is the stack */ +.section ".init_stack", "aw", @nobits +.p2align 4 +.size stack, BOOT_STACK_SIZE +stack: + .space BOOT_STACK_SIZE + +/* Some data characterizing the stack addresses */ +.data + .globl bootstrap_stack_bottom +bootstrap_stack_bottom: .long stack + + .globl bootstrap_stack_size +bootstrap_stack_size: .long BOOT_STACK_SIZE diff --git a/extra/dot.mkvars b/extra/dot.mkvars new file mode 100644 index 0000000..27cbf70 --- /dev/null +++ b/extra/dot.mkvars @@ -0,0 +1,31 @@ +# For cross-compilation and/or installations without grub available, +# copy this file as .mkvars to the root directory of the SOS sources, +# and customize the CC/LD/... variables. You still need the mtools +# installed and running + +CC := i586-pc-elf-gcc +LD := i586-pc-elf-ld +OBJCOPY := i586-pc-elf-objcopy +STRIP := i586-pc-elf-strip +CFLAGS += -g -O +LIBGCC := $(shell $(CC) -print-libgcc-file-name) # To benefit from FP/64bits artihm. + +# Configuration of mtools +MTOOLSRC = extra/mtoolsrc +export MTOOLSRC + +$(MULTIBOOT_IMAGE): $(KERNEL_OBJ) menu.txt + gzip -dc < extra/grub.img.gz > $@ + mcopy menu.txt v:/boot/grub/ + mmd v:/system + mcopy sos.elf v:/system/sos.elf + +menu.txt: + echo timeout 0 > $@ + echo default 0 >> $@ + echo title SOS >> $@ + echo "root (fd0)" >> $@ + echo kernel /system/sos.elf >> $@ + +runbochs: all + echo c | bochs -q diff --git a/extra/grub.img.gz b/extra/grub.img.gz new file mode 100644 index 0000000000000000000000000000000000000000..4f98e7417112c99be2dec77daeb2b3434f878237 GIT binary patch literal 67535 zcmV)OK(@ahiwFSNcv(OI1MFOXbQ5*9|ITEROxv^*ZBf<*iO;Iw4`>jS9*PEN3Lb&R zrY)t|k|Lc}L4LFo6}F)XNifqPDZ2W6>%!We>REO5z+_kO?k`<)CVzHs~E;=;m359ZE7 zBtalDhD>RLDd=WIqAQq^^}$_tEGaZBnY-9*02ItEHcp366)7R)%*G=0CFc=z+S-7S zF%;+v^nQp8!!4$UJc%QH-9^ET4six?zT3$ax6j@GZb>w=yr}Lm*KoHRdMG{W$tMZK@=ww|`5LsIHHP?niskcl?en6Ub?W_S zecaJbM>FeF!93#`L<4@5Fo8hUH)sM`%6QawBz%nYiN13tKA#4DZ%8Cro?=_`8(8B} z_QDKbajHykl&?4q%+JoJ9eGW~>7iqqrbVeEVfwSCHM;U9E)9ANM2!6%1PT4$yDbvX zENrmd-ie4nzwx|Lx_tJnNmbI_^BRJU4$*|-89|RjoQ2|=V89{fpx|#E;=L$7vY@DB zo&hbWUiU0oSXH;mkpFc3YE)ERwF()`b#?3N(0&57XGHby?s_Gjg0D-ygMk#SckMu zl+uFILMT0k7)0D;ATmwFI2&=RllWB&@#_#VF-A;6QAEu(Q1eZc*+wmJQjfJzkB6wmF{%)$pD?Jc zCUuETz0|2*)}me>Qdh*(E0Ctjpn1xqskUiqoSLUwG^<0JXJZ=hZi7KvZ_?Ur+UK3x zjV;>WgtU&Bwh_@T1MN1^UK{IQbTdkM$&m6}Q_8Ehl-HaoziUbPeJEvT zEaeT9+GF0&S!7hg~RlZ5`W(EBQ6M{7XeUJa&3EhO~|^Gg<)t=G|y z{$w#zpg@5F1qu`>P@q780tE^bC{Un4fdT~z6ev)j;OB~|hB4^Tb>8nyoqD78g#O$b zREO+nCBp9yoB#3sq5Lxk*L{De`soI(U<^0Te8sInKY`yLSEIEs1E+%iCJwzIzskPq{jqsT z3DzwxD>jV3Mn98y{3nb0&j;~^+kb?1;`d(&Oe(+s@{9cam!i_3$|X)FDh~{~Sm6P; zgF=kw2y!+^hKjLv z6R3OhQ?E7r55X>iXj_uuZpYCsfM}aFW#O2bFl%hLhhr2UHcH0xf&R;9-<%Y@*(Fw@ zDtQdvJjwkXJup=YX1T=2Q2A-J1)<=rE^!Xd!~~Z(69w;ZiPInxi`G@GdU|b*p`NR% zsh$pnBt12em+NGspoxNfe1D^S{Bm#?{5^;a^y&2w7rc(8(f`<#Gc|Vxn#``snKEtV zx^>*NnmYT+Y0p-#wNK?<;C^(!mlD+>5UOGM2i7+u#C8o%f-8S!xW+eb4KSjatk@5m z!-47PBdk72v1)4gL)z}{$$8716Iw+a!Ad;LvNgWwLb`k`>+bMNut&s9g6^f1< zXqHO&lh&mN7J>hQwyoL?oq#nYJ>0PgXkOb?LW^s${n+z?5H^Bx&hOY!mW~L^PFQ@e zEw-*OB_^tbWZ-~?6cH1w+Q(Pkh z9!|v*l-ZxJF)S>x#0Ajbav=a`Q=)kNOVWHV+sOj>W zlar=BuCJOW155KniP-yi9lSCoMt&;tO;_fyO;V+cVrl{E5UF9M2XO198kQl(wFAW%Lah4`!Td{z zJ^d1li-w7(M^GU{fmlEc^a@mV8?KM7wIIxY#DCn1?|3TP7>XoK@Ub@BI4ap83xFis zVYtfB>F>b$_(XrP5xGCYhuvo7f}E5ZW^q|670h~;KL{;h5w`VkQ;Dw0PK(>1jQY+6 zzKCY}j$tKMoPjyLm2xe!F2!oTkDJWnLot#Ki=;2yWX}R!R;VyERntx82`t|u7Jv@k z&x0z&55cKT?>ml9FH77bh@*1)#ZO!DRfiW$t3Dcqga@4#NBu;WRLgt((Zs!F<-9#kwzMu?^|0>mtJI zzfr};HEgFmjm1A+mUsLtp5~Kt2gW7-4Y(a4FjGfELaA-tad;Lm!en{jGWNU|Dg}|8 zGE1$3nI3a0BJQ}1XKDv3jLL&q0JqG($nq6q!n!NM!eUxnlaMEp^0!_iZxt$N*Kz3r zRDyi$5}B0xpW%cQ2nog6w0IA0MA_b>9AUZ`9am4b6TV}tcm%{MJV)(B#3J4U!`=Pa zsZ}RdWs~+xyT5WaWYdTvb>u0Vn1#S$bY2!oh`^QLs1vsM`N+0Iql>+U?NN`G zu5)bs^3|p6vXGv#4Iy_}6&@gi2~_J_y>W3-)zBiC|p`cmMpAENDn{y-#fJP?<+I&uDa9Dv>L-v@c8rQ&Q#yc;+$ ze99(FT&N+Dc*ig?t#2?Zi>a)u8QiNmkJxwJJR7ezB@Il$DHM5 z?J02Tit0zY;Wg0X(o4!r-kG)LR;%$FvAN zfxqssmIjY+!<$12A9+9bOZz##yK;G?G&sY5)ec_y%Rl`8Xq1SJ7bPir3*QYj73wuB zPPr&ACz!^@qvA@qX(yt$uv{8QygefBAHfUt&$tw@;>eIB$&ywv7X))8oIzIn7TCVe zeS`AKZ7R;zHF4Q`@z#U{i6uS;iA1F#a0g_LWIP(xcNkLkYf)#f7N&wM_J@QRB)P=O zw)O|0ICw+$@!}`IZ&&3=SZ31@b~jya>_i4!;=KKwmK9`{*g7N+00f@^irK6F61cvJ z_=+76UAUc$-p1CGdtpTPW7h|9LoHCi7H`92JQR-U<(T-NLxYW)E8%ng^XSTtVy}Oi z_0#!LWTbPd_5u9vjoQk`)q#Dc=SR&?XM&7CpBAD zm)-%(7x}BtEw5a0II>i-k1s8{ zPmIaU6H3Ng4w_HsdHWfbFFAG4d=iP#3s_SJ%cE78Pcgex%&z&g`ziehKVK*JV+CCa zP7j5U`+-ex8THyG*mX_^jd}|3nGZ3101q7`X>i6Uc|+y)nf?OW;xC{qrI=qXJE@0U zj`M<1cxntO*ZOiiMy`pzW>5#_x}oAGrKg*t^yj)iE(dY zM6NRn4e|i|UFW`*q#keV>%=p4kN3ZUB4ny>jQ9B%)jLLgGDaO5qdpp=LSxj2Ns904 z07?uuB2;cX3ZA9PG2nY43bhD(vqu*2xj;5j>FqD$)b8O)jT6L+@Z9R2V^Tw9w5Rr{ zF_8`ijYs2M#!&9g&*cG}NOt3x{tm8=_S^U`ri9%W(>CAa`fxHHBh>I;GoD{T{sdXr zl-tM4M8jX)UDyWU--G2CPbZXJXQMWS@$3SsHr7HQ{{q?~&Z?&Z{ik3N-5qj^G;dF5?-5wR*NPBQ{TFbN~gdArl92=jejs;10jM3GKFk1IrCb;Y2XIk)wx;U8l`n*S7 zmZaJi$qi4AHQ2_eRby0bk`hCO;KxTgcb7cL5_98w=!Wr}0SRFzj-Kl{jvtqrXgv$9 z(jGj+63$99P3a`6{Q=lwT_GB6leyy3Mr?lkY2UfA}L;Kx%{iP$)&_q!zqe*-lrNo|iLiM7>+ z?YTwDQak5LToS8I{Ex*re)&umNGp?PXpKF?&PAINUbyIm}l zh%z5;2#xvvI(Xvlk`3FLE8`s40{p}zX7&~2$SDS5s+>F{=1osVxeYI6E=x}F-^DuN zB(twiGw>#vSP+JR z(=dS(lXdIk=>J@1u7q>Iga;;f?5A=pwJez8XJQT6Wibb=E}K0Eo4peG<&3)qGPK%{ z8I#=&89D?{>kS!Mu$3e?jM=g$i2&FA*T5{C(EJR8AWv1I`$C_@W$5qd=H+`a*nrS# zS$4AUq$X=L62;5pNO(hx=X;pkqwJSA!LkhJeo+RC>dTSja#}5~iphNbEjr?m?EAn5 zd@QY}L|X&SK3?6h%mnNIvi2_UQ54DJ_-uAJ*@T1%kVsU}ML~%mvI-tCiRQru6oV@< z55NEc5|_y9l35-~Ud%3JGA?o_-07VtF?gq^=i^Quf?yIb3Cd}BsNtbTh&t;=jDipn z%>JvYXLdKJ_x=9|7uiTLrzs&CLvUIl-6 zPA-jEsgigYGDEiD@lM=NOW2O(R)k9Q>o(k-K$t8wQjOBNF}@9yQe zoLnDKt5TIm3>dpqP1A%jD(|l5xI`C*VaaqYg_J}};n!IqoF%(orP<^*B-oTQ1R~Ni z21hOy*_=Ghl<+>kcCHVbG%%r&7au}?)D!={v>BP9$BO8GJkoP5{MM}d0f;Cmg1B9wZR6iTq zBN7@{s8etlQfhuT;tQduAQ`XPwGNQ%Fz#9pKQ?7%4fpy3C%%Z}^=aSszB)JN^j}Zpu-`mkk30DStuOk(4rXHoxI3K%Cpv zg*?qo@NCNA87e^M+ZT};V`7oCt_Kiqe@<=BjT&g@W&i+9vTq!MGetqP(O;V)0i0Uw z_)LJtJ3}#J_l>;stPaNj^Q*Ji$U-t9-H7|{$tWK3>m|qu%|ao~<|DOU$st5U21cBB zJ^;CP-2(vPaTIOtDLVKm=JZA^U{hAJ642^mPWco*q{doIaX-y%#FYg8%uLunr}VnY zyEx(MnviNeC8twLTEhNPBP2M!@ppZ6Hg}b7UJZkLl(x{}7d0gj?WK&%3@k535SO8VL)i zyafTfZ6VTdw2gTF2pKz%zYlU<{~(Cw*vk=B3RHWS5bZZXtx}T1rr*}`5+L-fMr19X^9d& z@4@Gu2;_15BjEquMzMQ|aL&6YKw_R3eny1>>G)^h4W4-aXCHoE$WtBDMq#HaN#`3tQM_{>inwZKcE2~^Es5IysuR5{fSmH*F+x< z9k$(|wVkyWRXEQyTrg;PcfS*l7?7t7lXIg94f2%8_}2J_2bq$puHcsiprVQxZjNIh zEdxvhm8Qfnp+~mGFd;)a9RGv3Dvo2V59q@Vy=04HBA+}rPHKw>;S75wKM*NTiB4!J z84Rnzz=WeE5%CR))UXvnVrW$#q9zy#?#MbC}p_@WfKhXc8v1 z%K4WM?l0E#nVHLxThDZz%yWb*AC-_<)-G0sXeNxCn9h%Rm^4Umlt-YR-PkYB5}#%ygT~6qOs{MY3qcbe zryN@Xbo@QCJ6Iwe*IE2J$);a^BI3_gok2WRy{j$%!t~v^D7X#RCA5}ANJq6CISh1_7Y8v*xgM0@oGklRM9&Q% zD@f1B0RU;|F!-y~{GNm>&vaV(-bMZ%60+j&@}I{-$h%D`{ymPEFJK&8E|*2h`O%gO z%2l8HeCaiMXT4xZGCO{9)=MV@zVfbHdFRc5k2I7Veh|w{24T_#BPsvFnA(Slvn-^) zq&C+Zd=qh{@J+;p*;dTu=J-e?w$yvSYPN18{pmyaQwcJ~w5ncQU{@F`6K`|;2!D9z zn@DfgmxxEr#YzrW(-mqEa2e)p6+N-U{X#tb0r_{!i;LvP6HWkGiYnx88whK4cE~GpidgH^v zT_W@BHt0+-=eY&hw6%k^Ou--T8FxAqe~EZ>O-NEY(@c^IcqcYCX}+*BX@T%)(n4W= z(qthg=@H?+B)f2T(h}kJq!J-MsZ=m0tq@|;YC@c#glq=R3FzPHc>)4}DHBxCA$}O* zJt=-@_8vjp={Ro_{KC5B-3`A`iT5?asoDF2_+g6o8TbGi^SZ?kOI2C+nbQ4rzfek z1AW@0%r5!C%dR6bOwh7O>4WkDIkj8Yn$X2NL(qnhem)sRQr?M|6y3*f_!E+coPJrK z4g%Lp(B$~msrZe5y5n1I;^SSMuw>Wa7f?4jiq(CQNHfNl-qoB=dvh*d`CW2$wA*l( zEqBaBgpos~u2V~OV5yg(6choeP~!l*&?@L{mrA<<4)z%EA*G-9e!&v{fDIU9L~yJT zaVoej1UHKs5$#z4$YlAy_LW|~qC}Khq8I*p53y`NxO}|p0+Pz=^MnYO2e;4DbW-YN zZ4&@y`7n3(F3>KdL2~(kZqRw%4GcF_^0dLler?BU0(-%*>D$Xk7$yWT0t!Hp(>D!zrv7)Z=?Ag zjW&A(<|~4Hd9wmJeJEGLA7Kc;qe-kUu-OM%uVM>tg8Cc*D{EPV&cMtGm zG5IB&UBVd4!6JE6IDRl;VMk-45!sY5AyvE-R({%!NyS~Kxsz_guP z0}a5O#ataZyL6qxU9+Y-jPtfAFP{JnXBK2vUVuW%4)|kFp|+6q)%zt8gbjCDek!7c zijA&8YK1jS=eB~bd%({0Vc`~jAAZl7Oy7=}8mmuK%t>=rL=@knOtzw3_7G+9{-6wg zlosz@=+uHc@LBdrIC#r4Ao>C*&7jivLPc3hIaNE`aaDl*5c1rG-LvQ5W=aUhx~U}D zks#m`nle&(v)FG250rEq2#8hQU9Jm`VMSHRePy_pAPiEr!sFvR%Dlg=#Y>uW&@2F!DrH z1C*};{|Q|)XA5DLgDvOoGr_4egmN8 z-$@)eJ>lG%kG%h67|F_#%S^!hRjyPM2I5Gcz$e`%49KVnB^l*5$4`}abB+OGi%;Xh zO`%+_-GhX80mdR@Hgu=Fz;ZIzH(^W4W#k`eDrrwi7kl~gZ=k5y8!go5C_km6h%q-u z%Nefa90EDR)SM>aPqMYs`vvyKttOBbBx|=i zDBTh;+Ad{;Pu;?mte00UPzTo{TQ5tXc<>s&j2$i;q!`C?@PP>jYvMC?U@^Z&EbFppVRKWYW^nXD;^UN=?eZTtB0p>G-%Rxt+9S-0gY}sbTF* z97RTlPd;j#2di3~ym1q(!N6c68Ka9t@PEm1ySgr+feuv1xwC{0c?*8(ha+ocB0yV? zDjtC(%4+c`@7*8VC*C<4B6I8q0Ts$m7EqkVQ28wT&ma!i>v5U={q4lZd@!RFYmUEl zyz@JV(IgycKHBnK=YPfxGwd%a1HOrvsEnIR=0ShC?R}_wmHh~dkZLX`Wxc5nEk2eb zm1|;mfa)zzj@>;qp{rzis@NDUUecF;1K=+acmsjgso+1F(jR6v zleCpbt?&k{FuPyIUd*^t%eaSNx!!^--a|klFvIL9*V~lm618atvuvg&h*x!@L61Za z$%R(S+Gi=)3PH5Javr!-hQ@jr%z7qVT;L^MG)b;9fi1M{W!&##Qya zjAVC{*kcq%h&{Y8tg6R_F)=d1s>9YR?L-3_Ch?zVo=^2TAUO-zRW0VMLlh|gB*%(5 zLMwy8?>HqjHJpuB@T6u{FY2bkaCdrvEyq2j(q`vh1VUVV8%d!w2HvMx!gZLCMG2AK zuclP_)^oud3jDf5WNk*&QzKj_WkNnRLWl@B<*+imCRcgC`K^YZf0$Ma-kFb-|6lTvv9kwBvNUE$tN|5eK+Nm8~5ErW)F~xvc%q-%I`*XMi^(Q z$6sidWbG824dOkV<8?}5+}l!*+&e;f5gIhx+X*(bRWZm` zMFcsNBfpX`o`OzMN7(##;Z4N=hM=KN4pYjrgvz6~# z5SqRw!NR6VPMPOr(9Az@+$fesf(%ww7R{0G6N7X;LMnYn)sk}TeTb5g*nm7DUXYlt z1msWwJ-&+o4GI8Kp~%)~IqNMceHXtoM4xs!p-Y$`erpmR>*l1aw}h($yG3&PTSY#u zD*aUilX3o7@X)vBe@oqaxM*WO(q zcEq<u z5PHa`ZRXssg{;)D;sIH9{okPOBNMgNFbmARZdzsQ)-&a zJ5xY9%7MtW3AmF83mclu6afoZoo?$^8(JTgwG%XF0av#XEj-R<>sD-n;D?V+3s@2RP_2P06YXjN(@U2uri)<|w-}iB&tI#k~jxri@ZM8;0WF z$|gz?z5$^jn)3@L%IY*)VIbu{m)&K@_Pqln`Zh`-=UyyMwCf?S9E)6$mIJ#Ug{jTU z0kY)68Kk`PI}rP~ENALU)tO3?6~03UgsSAbA+tV2lWb6TChs6rymir_csss!o^(WQ z-3K2@mBMjQ)jn3v&%*s;LTPzU5H@({SzOhqDbi+K$*Doia_264i`spe8ZPb?NLFwy z#Aa^B75UtYyz5Q_Zrj?a>6oF0{=Sd`jjY*>1797RieVRZroHloOism<+q%i;*j7hY zq{F$YsbTgU-t{zX5Yrf#08eFaDF4d!TOxSZYHYFf4d`#E2{9qL9tP07tCOU~Xj3#z zS|U>%ez9yT=ZF#a64~;u$Mi_AThVAMTer&D+oa^l(4wNcG*r>8TVY~}yUBc5R5ul( z+-vbeNKREynF>MpP+e$rKe6a%Pz5+o(e{ zO{<`GT+FR6DQ$UZPIFtU?ObFu$57Ff<6p_}Buao#_Y8)v)#%ef5xk_Kb`T^ExC8%%7!nOhks^PeNxyHm>sKh0NBh zO89j|EMzrQ{&XJ+nND;dguyJ=Pgnw;WN-5~2Iqr0WO)*ShJ`~rG4+Z(}qTu!}sT4G(q)`M|C?Q^^ z>b)`}0+l+XSQ8AV;~GE8{{ywmWGr}&=W(FgVuRaSY0GN1R#L~lyPb*EAL7h21H-EF z4|M|dhIbyofidre)#IZZIc~1!E%+|Zu}cZ@785 zW1Kd|t^*h!YEL8>VOh#wwWN2E-#P~38d-Wf_Hq9l?>>N#Mi2ylHrhfy4hi>B!t8$K zg?bQ^vne6XpMWDP{2c`C8I%z3Pe8ACVMB4aeKsXT`xBVT`+NW5`YSfjV%}azrB2+c zt^46(W|z*BNQ}ib9>>9*qD$uHaX}kgv&tg_5{~y3TNXJ5DGlSv8k*~yh#9jK=_5L@ z;i|7gF^@@aqMj1n8M-?BwH)y*^^ux%h}pm#*NDlqT_z0L6%YmiI(KkCj9m|klQ$(3 zMpP(tctw?fGPH5M*UAUDI-*UL!<~D@8H~s^nXBaWjru;+I zOxuEdh!!*94s{}1@kl%h(vqco0vU_-_Buu@@S)faq1}VTCaF;vCbf#~_taF@zo+A> zC&zz~+VE{CG_1nlI^yUkHUkvLOF2!DFjA3kBWdQA;_GwlTfc{?nRE*_!pv&QQIvlJ zp6q`|k?$HalWNJ1#%!AoQ;s5yLxGP8=-s-R!kzH9#4L8}S6-W=gtP|<pceJs76C9@Jq#{S=1oGI(%rE9~!pgR8DbwT9Tqt{Wi2 z`%Q6(9n-Uv`dhS_0ZqrE)UUT;oG@dSvh~Vz{O5fphW(*LPLq(J=G_*jN)LnH*|-D??-Qr zIDW@r+$oKgtufMgXarF=4I#5RA=}(d*7(^Bonus+-0C4DEK2(uywPH>ex;!(MTq+a zmr%!)ZLn;+6$KxYW@Zl6|mFsZ~!P6OApAvfGB+8JY%;;qQ;(+FFzXGFRXWjd+R!xlmK`82BgpE>x9qGTa1h#6L+~N= zb`7BqLc41SIE41p5MT(s0~54{Dua)`IWpA&3f1IW_OXt#y5YOAsM&fU`yW99NXDJ0 zbgPD!M$bJ=X$LwmGdgId>d3tKQc6H+7shH75d%{BX@tfU8v#k;sw+rB-N$A__S6`4 z$h!SQ_S{YV#v%CObYZm8Le zm1b2xG(i*{?e#gv8mI{TAHTESLe6VVYwJgp`bUyf_9v;@d!!(htW3SiccSatyNY199HJ zp_2X?M9r3(NSw&?q@#r&Vw>Yh;Ch!CE@K%&(5s{dH~YJ<1}5u(E+<_(ldZb5h|N66 z_Q+u1UmO{SKo@e-=b zVtu8}?!j^uSEE7^x-eG=mGFLQzl00I`^@5m=S-We=Shc{gy-fQ1C?<;bi!5*LpJ5! zWE?ry$vhfW;TzWl=FFt*r%gDAdhlzI>gD5Y3fPrh7it-)`UQFNY|{21xeXlE~h%5w$PKnx|$Ynq|3t{Mal+-?MAIF*WKX6_?)F!5lRLvC zSv%ZT#fE2|m2o42x26xsRz@1Y;8lok5u?malP1W1blIX>7o794v(4k zk-~&XvD>8L)O!K~NV*;`>b2GbD1>Fhu0{i~d{caCu~e8E&6LFuTke%(=kM?GWdl6P~3U*P_*-=AEaWVRV+B#`5j^nWc8kg zSV@bmCyx977kFAQa7hC`)(;$Ft1LU4xYpY#&5Ue4g71g-)*MVrINqT5iNZPYNLA}=>*Wio6*#X0!{&x zaX*I#{IZm~L8ug45D&&dq%O!J5G>s3;S#}6 z8m~u_G6tpniupQ5)o-Hg@zV9osc$(8rBDvwL9hwv7ryCE5m8(v|f zWC9nNCrR&+l9^jXinblEY`K$}iv=BSTu|qDXAy2G-2948Z6BfCo&Oe$3Z9w3=|ALW z)o#E$??PI*?TXA>1hQz=Lw@`3u1Is?6=61I8&6}7;F?_LQSeaqUPcK9RvA26t9jhK zQ5hP-Sx)-i`V2=aN4ff1Cj9bFys{LgxM|H9-}Z;-L@c5Oo44~axxn<1UPINn$=qp6 z*h^WM=CkYU_jo%38t`G6dkdx+NG>=11LHuZ;bw??Y1r-PUArsw6*?-4VViwAlJOQ) zt+JF;cuB>930d|>aI_N#qMR5h6NaEYVQpbZZ|1h;GW1u=xi+Z&iPJ-P;J|Kg60BMvp z`LgQt=NRhSj_uE-=$o`?VV`?DA~Q!tt#V=*k}&0jjBL=A#9q=n3B%N+9OdmO8fxG6 zQ`EM)VYK6lXIhBL?0>;O8j-OnhEYmYl-h}HJsP>W{_wL_>UphHpkJ&kCU_noVi{uz z_;c{iA94GEUeT*L=IqWM|6Ws2Wz2^gdK&6 zsBP@$82f3(pW;yaE3crOa&D+L^#_ZY#|${P4_(Wu@tyz?>0gxVKmJ^ykW24vgOqnD z<=_2MLcIHmBkWC-@{B)av^zZ_SQ3`5Vh3fJZumq$0rxPK&G(m`;7;!+;AURcPrwz+ z6g~fBMnBQg`x?MGL2zRIa6l4kBeY(F8)_hVJVRmKtQs~5 zSMR=rVc&SW?o}9S{Qz=p%LD*K=-#i`&k^?XF5+(EHOLpOhb%s&D^6vN^Khe37-hNO z+kwF>rROS5RNaa!t!eBOaUW%JeD-MbSaBDHdFL`bl97K18@p{6PKKJy_M`PKYb)M# zj3e1Xqil>bs#c6^cldnC3rL~9F(0r?DAj#$>lInn}v#(`*#p?C35e`t7kRkR)$D_ai`#7Ugvb2*4zopzT74%kWq6_uVN3HE+jR;H5JXVJprK#u#1G9NTT?~~wm0-kp-if^-cL+2PKmbG)M*dBy*>1cbnwbg?*L8+|W`zXhM$T|s4>S`KvtSoC zcVo|7m*H>1t9WeD0C}cSC=x=mDHH>t63p$2Lwb06sOA;qxT|t6*1`$lm3zt4!!dLh zb~Dgnjg`AyBWdl+*iDYc-fvJjeH$R!;1f~fRT5i)0n)ScKe}~n>^Ww0>kfY%HMX;eW47t@2&T+ z`%2VMbSm&RxVMa?wfpV2QF6WJZOUq|#z{ISOhdFy(Wwc4zyvEL&$5iHh#I>cW3LJ$|792ioAS1r@E=V0Ih0n0(|wvnEa>Fd_YbVio5nO{49Xa z;%IxA4#L^w{cpBE1GH`*iZSUd^^FJ!|BecB=&OsLqwzBmFN7G)Dv6!MN>dhWjACS^ zp`n~|X(BZt8pGdBr1+s2{&Heaoi}65M-#OM)`!Dq{X{gvL_%AA6HWBjK!33__IanE z{+En|ZOk;DGL5H9<0;d4&b}2hiAIL6m>NRVB*K~_+>bBcM7+3ZhP@p#=NP$Q&&vAW z5$|5)*RHENN8`O5Rudfw3Gy=Y&jnhM+^(+6nl1YXAH4LO1XX|HKLaw>Cqs}H7r#mp zl-T`%FwlM%qV_)qvUI*GlM%Y8s>i2W8H4AxK#Q^2>pzDq|G@KwCqZJts(jnAM#YAJ zx*wP`R~XDusXdbyM|Hy z%m5z(lNm4iTt^p*?R4LSGQL~6j6Oe65Uz)174IzQpxZ#*q3Dad(`BMu?U&I%=oETW zxY{nO8}trPPEvT+^T-nRVj3rSWjEF#xm(defFi}c?UYo|>^i2Fl(%4%?7~cXiWT=_ zt-!<%!H;mW<$}B!BcvZ>s_v*FyD_C&CpMW{!i2|9J+(N zALriui(_CQZ(pw-+19)t5gMLjuqhQ$!6X&VZd-eool?Q0Wu#y?DA5h=27c!e9gD@| z2aI-`UEP;y3x`~Uu65HVBi2z-!REYM{lrR^RwW+wTIJoB{lq$i5i4#DVeO>t%bU5r z-=NrTfa?9kAJ>d!JIMbi0(Z$WFDrNZR9}lppgRg?x9c)C4gD+>$IC>ZGiM1FaeqjW znCNq~!|cG3@9_zhkSC#$cRFwlw9jh?$Td1*xpXd{;1rFj0?M!A9Pb9og;6=jKlvT5 zp+A#para4R*2+lVNY_G8pLd^uFG!62ncCrsR(}W51|KWI?*|B>#{=B|>B>ud#+k!u}yQkvMUv_8X&r|I5qdU!)Eq31^eYGOw z;C|nP&GU@Nqc%f}lz*}H)BBBPFSACrdVw23(S7m5P5b5wXg>qvc3$%#g0urfBTcAy`kYnXxXw$sY zI?ney)|OeUE$AZ-0##WDCmk3530MtH&8SS^96x!t)`V4uLbCTyGG8+Y0;$d;f?R!7 z&7cejIWBnCFx#85JQ5efpkqvf*P@sI8K8$3`uvB#O|JS?rbMF|h-D2qNiV8Lr&?p> zhA__i17(SnV=+0tF>BEgOk+7_xSz&6S;qQJVZdNg08c=$zoKZbH%mDJfm+Ou`$64? ziqMogGwF?&RsVo2=ISh`UXJe)S9j~A52Vz~Lav!kD{D6GQ0a_V_~>SbsM z|6~#Hh2~T`++7^iY>mW=@s%f_GO@gyo5{PzptPUS3CiL^ved&b(Q2 zMmJgpzT(Ic{-h&R&gmMHa~^W&GcHS?N*P^pcBI>IS3fReUCPVhG-`(!=){bUvZ&&_m1m*YY^d40rP%EKq1dd* zrYl;s=3BZS!Ez8fX^^LP-TP>3I(77Nd@J%*9J7Qbspz*O1-1I6}(omOfBoOzcn>&=dMKXjF`ce{o~ugIu)e9!G3V z>u|@Iq$Z#g@qi)GW-2R|(=R8q2&>{7+^ITtxq+ho=(=nQAA#4|snLoqf))e$@h$)) zXI*w1?$-UlD05nely$jcjIJfVRqUQ#e(f$Ki1==FcR#ND<7d=H{VJ8g65s0mifH|` zJ40vl!>Q*1C5^{I-kTMbAb*p%O6m$EwId*sqCx8ZhOw><4~ zd<$9`x~EH79a<~8Y|?SuApRLI+zYf~XnYHuBqClp7O(uZnj~v`v$d6(i%xNC!+;B! z$UG=n73I$moK;m8jf_RIw#_WRKKUNJQ`Sl`<%57YnyqaZ)wC94$gF5>^Epo80o}RK zShS3#^&v8pj8M9JPxM9b&ODG`L|+u|d;kMwaVWe@fi*jB~*=Pw2R$y`1N>x zLh7!ze&V)%Vnds*^;1s4Tf)32MBlZf*gKAD=%V^u#X66JZEB7|>lMJXp)>Cy76(=F zZZs&sz@BNg9zrzAnWrpADih4Sa~J@uF+@1)g}9_-AtuQzM5nQD6MW;fFkP1VgzPT< z`FiQ7WNneNPt_QPK=L(^^{UFGA<4WGFVae9u~^_+IUku*TlrOxxpK`ApIL~U<~ms( zMq$VK_%@qtJ*8^{G3}q|4Pp;dO+%ByaK3lNw>4E_3Z92_v?yUWsN0WHQ@}vE>Q2aL z#9ROkdt8Sm?+bb5*WvjmI!%dXag!@h2uZN)5VtAex$0?tqRe2XLFgJ`;{;XFwB~yHvt(fq)ZnE18V{DL9IS`7pdP>=0 zWPAkR0(~RV;;C5ltj6mrVU6`-VazGv?d)^BZ(@zBQq|I)HIJaALp&@#$Ekdzk{pU_A(_Q^=T#_(LIUgbdO8u))l`XJuuON6k$Fb9Y6m}r zREYlDlpAAy?tz%Y?84ulLU2~aXj`v&u&M&N++`M$7@jr!VjHeAs-p*!(8OqZO_-(L zgBoTr-D|kvB0jL;^+Iz}07aEa(PrVCcROPw#X49um0f!DW6+izJ!n*T;4+JrLK#4` z{6s7u9qpPs+Qi-nVXD|0C8UeJ*9v3Y={P-Cei<;x@?x*qVN7&H3Wut%%kVa5DL0}= zu2;!brt7cRNdr+XP%t7_xm8blYVCjqyianrx6=p1;)r4Q;$$h8dvG@^S21Ff+vtuB z+7Gvyy7GGuJ_AIK(-c!fN~%V z6nmnCa7(?|6Cr>G!hm075|^AE%`OAVj})$}39YtXticN)CS_h+GYI}r-iok{^!psA zE%kF|2YCa-nlI-^i|_l=5>AxGRwmsJGBAV&fJkh(Jrz$$`~bY|MAPIud|X+0li_wY zXetO$buvvLH{<0-W1xt&x8@qGH@|mH9Q_ab9L)L#{6QFAGthZZ+AsYm7$%v7*n}Tf;N7Z%DFv-_ zXDszdH)%By>CS_~nMnhL^WM)GmDhw=4yNIPS`$J<*BzJuRXuon9?)s|RS?Ac6K~RP z-PFCG&{`T1aLx$6j1K5xD$C{GG>GH6eBPtlae`>N*bcbaUwyQe%oU;%9H!D6q~jp4 zT|T!>yFfe&KA8ov8=W_O-XEc(Zs87}dyU>F9sl{RPe5<6j+Y3aunQnC2#GnIjTYl$}gzJ znI>U$C2)otD}ggy=Q`-PHwEK`As9IjBg0(>1#e|fvg7NTXpD@;$N>;(gh*r{DJ;Gl zaw z3c5~55HWk^bPd{?8dww?pRQBO|q!@rKJYpmx2{)W~10Be;)@woJ#dR0%u zfiT67qV?cz&e7pL2*s-Pa;W!kb}f1Z(yn%`3GYb3O>Hq5FAL#npGFsiuL38lg0^q{ z?4Sem{Pd=JfCI z9J}L8@E)9GKn9kX*+1{!E+B_vHoy_qcnkh3WxV53&1MTyBb-YEEsG6`Tc=}E12K^!jdvoS8$w+7s zfoXcJdbO8OQEar=t9K&TvyFKS8rUT}`)3OGUq~@U3%{d&+<@0F>w+ZJ>FBUxQk(4& z{F`#uYRg^@b3t}M182_h?p8mGzN7s=yrVd1l}*N5ID!znO7*b)+`b0s(gKB)*FfT% z?foX`U7FhEbZiz$0cMQlB&b4Cc8BaxWNQaH+#1GPd;jw#`UoMBjbsKk)ava(5^5ji9$I^jgg zJ1gOP#SW=KIy%Msy4IA@pvB$(6pmvZ@-F2j7DmcbL?A5URdsmzqSE(AB#^r0bnI`j zx+8@rg(4r!Qj(v-hX7>#99R8Z;c7jli}i*&{KcDYTnF8G2FE8V`SZ?K$a75j7KW@r ziC~veQv=#q47*&1+L2dFK&*${hB$oFEn&Zu-#V-GTD)S1cUpA*t8`>*tKNDjVSmY2 zRZYlP#ZP_wt9=m?-EGM_@-#JGP#Z4|Uxn82N)*1V$85RE>Zci#{CuWE=y%5N^&V2} z($P3~(b_?~!<*BMvh`Szlpb1RZL3Z<)mTqer-y52|J<`9cVj>pTqK7SNqVs{gly3u zV4RiO;=82KDLQHhyE=s5fZn4@0pPf94K=b7{+hl;zpy{}d_}c*^HGtj9_OGFQq<38 zx|L|XUcGq$!ywH68skyEc{(7{%30BJdL%yc!#m$aC+Bp-kStR|55M+J_*!X7=;GJ? zo%|f7Pd(4!Y`yajXz}(*pg;W!6B>#v2dhrw1`BZLHBt)w4`4NQm{yDV zB&+G5=N~XA!S6{q@Z6KzIUe0*8oI+#1;QKP$|7mj^BX?Wae;R{cIapiMb3;#Anqe& zb_k=%S>2}ae`2c#@A^HS!p<~FQTBc(MqEC=+6qaD$#{y07ZWfW9pADXk-9JbAoWTu z@vZSk8%~G2H(kYXVO-+yR+=ZBQntc7Uq@uM$eEo9nO)L}@+hgry#aGcJyND(=azGv zZ3c6kr;{}YH>vlu@J@7!PUeWKX^mliB*1;p9cY5e-uVrOM=g%9j}!E9ntl9-K7M2$^&G)(Xb+F?O0dS1>m_SP?ZQw3n6QUVnVd_^ z?7m)H8;^8moBycl3rWZ-=Zy;O0#*M(2v zf!d`jAEw?%5E6!)+@Nb;dw~`yat$Kf!@{f!!v3_U{Mf(Pr>#ebT#wSHmcT=?xN;uNv&0{5v0zH!$2p z$xNd*d*FK_p+6ouK)q71GSA$BNo5Sw^(yk5qH!RlPdZ^8Q z6>_#AUz4H5`>`fvVhcM**8-10}YyN(>7tO_C-kx>?Y2#*0(QQ-GLQ53M# z7=TXBZhd>^Jm8Um%~GN2lw*I;4n_^p`Zt{O-?t*QfyO-%XI%VIHn+6nGoJ@$hW9&m zEz}<+8LK=~Ii(C}+QNnO(!eqt>c zgwQ;JV>{{ztJRVG&f(9YJkYBzA7VKYXs)^q>~>xLx&J+FSWa2T{2d8bAJbvSn#s59 zOEL=YB=N$oB$x13<(gY|R|8l$JKzNi8$<{Hiz7_g?vV|G_6n|}^q zUu6DQ>Ww#5ox`ZXgqUXX+e9CxUp=8X=!z2s*a41t*DYo>FC z!avkAhM2{%jdYz9nP4`w8!GP~-59Ob=i7e%*Nz&#uDPl*7Xr`2w5zR^UpTt)d+$En zCq(gGI~R79c4|HJpyzLR6>ZJ{7|U}3xz_u+wui;8c$U*l{EieIQIzxny9ivKec5f( z$>}5cos~5l-Y`^V!4=;meFCde!`BA+@sTyoySG9Ti1}mF_?_!9Ta~qwBQM41gvK?e zSd~}NHdfGtm=+0L;dczeL-4JV4h0zJ-A`lXArRPvwP(dZILjo5i~^)`17IBtsZ8Wn zhZTtx27`7B;;YTZQ-CM~aXvXMMx3Nu85y|s&s*aw8L1MYoO)SKiIzFqC-IF}E?o4} zBkwFgq^3iLkyVp&tvPEQd&Cx>r^oO8lamvSrx1pxj{pt4b3D2+1ZtTAK;nGk;TP)o zW1tRUxV-YRJZEH4)gJcTo^~|WcqMLfhRG4B;w6I=AuMh9(!lR{=juk#oZTsBn51Yna~`KnILk@P5jg{fH3X}K$$2A8&g)@vUYjcJk5;cr{5&wIo-{;U z#z=X(S$68NRnVlII4ML-;2a^)vW#KPM8?u3g#^t;IvXgb#j&e$*;~iCucT};PD{<|$(*>G%4YHKkZKn$x>EH{S_tFPS7~+R6*x`44 z*zj$%B-4rQr;yVG?ORrLjz_~oI~gGeJ-CrcT!we{*EvKxp$@!Fj=}~S_l2QSQr87ZLJNq|&T!-|myEoV*vus!ge+t` zmyphRrx(qt$?Zc(WS^w-uL6AaV4Nbu5vE(zXa&SQq!r$U^0nmxHftg^OSq?MZ@YT( zT)4w^0j4hQJ(={6=6}Ei4P>2F={YG(e2WC-osGj0lyGB+!uU384L<^ z)TAIY~ zbm2yt?8Z&Bgcf0pJFUR>p#83I@f6G!lrz$M z%w4692)_=Wc&NHWqvFIvaO{MIJr#>>IY8cM$*1t|1p|2tdoGcqv$n>2{`@mB4v;dB zURK>>=mb$tRzw%ns?rVqQqUJg(L-E(xMq< zg3*nF9BKX=E>y?R2v$eHJ9GezxA#+g6ToV!nWb}{lp2KXmAzwy`zw3ng(;Q2MZEJ_ zq|;tI?|cfkfOSa_RgC->V&wku^Ojo_C&s z#H6vj3(q>pQP@j&s;=%by9x!`5&%(?rE{mXCWZ6s8*q}RhNy#0nHffiyrg*4ckq+i za;`(wd7bE=L7XTVk1z4-bv~TH(e7%TK2j8&)k6G?74orpL)>Z4CxxvX#_wwsl>!g} zs=mY}KD>pFcCpq`{&?P1O;QauL?=Yh-16X^Yk`wsYyinGr#%G`=B4x0o)d2 z)kI+}Y8@fozlgVDFDpF_xaXW20%a>#`4;ld4So#^_tHES@L7|p0}g}Lhq8w)yM#mkBE(W&v87<_81<(`Vc~%e=+h@X3`?$7}$R= zLAeJM8?QH8m9=3-Sq&N(@;=TX$We4d!TYc%R9zx2nieADsxx$&T4WERVs)rI9QnM| z*wABWI2|Xp^9}7W;U~fyX)2(7vAqIDD7>-iOL}VS{VF^mvQ?GfE%n|vnKJPYQn&mE z?K|m71rq0Xo>eW*LfUR*(QoeqJ*`Gp9g>z>)$=n%toU*FnsAV*`-$?k*p()(l78#( zQ3bj?17?U*+X@;|*#nb~p4yGVni8YvuCp9=<#Ck%R=CQ<+l0H6!I$yDOWK$dhG3LY z>>eb9`+ZV_Y~(9`RA+C8bi&)NtVH&AfC=YDgzeZ;*>jsPKujjnvg2j1Th)ymo!+%s z%3HmvIojb0x+ua7!El#eFv5U&J!v%| z=@4-aBF=li%d#&(KechtgxPu8k!`Vae3OxTi=0IwQMcL0{7A3{jBX~##9pxxyz4l} z&So}n{_A=7(7m=XvK}wB7Ioqss=}CBghmd_tOq(_nr|Y;k<*GY8hPR@ zx^C4}9HGQR$7d^V9P@8S00&dsAOycwP>Z0Zdb@1OmuK*eV1+2t+?1l@W_q9Zjc35e zo32%Qc;jvj`ij2tDbgU%fj>Spn-BX~fD(v)vbW1*(d+nO?TYBlqqnz??= zb*PIObm6ak!7@x!s2R(6TxigG3DFRH3~%DY-DfeG)>%h59pN2@jYUR`{)~6)SSOe~ zXz@YkBPz?0>tZ!{DO#9hJrIcf1c||?t0-!=wvd{TeL$bxA|}()<%mmU_aH|5tSw?$ zOIUfkxa2_P6z{ue{-2>dR>_1|N4upSB(BZ%K1T~XZ*HK2;D@r5b4O85kxLZ%wl(lFoWjBJ;geu>l`B#ScJp- zwYnF&M9r-U%}5&SxWrnUqx61B0v?{NupdRpbB^F{)?3QK3nccwD94h7?AN9?soK;N zs)v|H5y~s=(1~5|qTAOT-YEk><|-E7RM~(YW1dx){iMJk;uCMoL>iN z(+)+Q!)XI+kn1GC9Y|`!!Z0n@F2*z~=(#dnv!wjaW~&le848JSs36>BUAb;YXO4E0 z#J#72Nf-3qnvj`EV>BOJYnPm43xo^D?#TKM@5 z_xb$n3LmmyCb{4Fr{P(RI6{Ue(Ki{Ziq9Wc!2mX_ZSa>J08rKDt1bAm;@Kz2T92ky}6Nh8>%dY$HXpmzu zHQ}gqynH6!5QUZuYbS2hb|#~>oZs*slHFRAlj<;QEtX8F!@qJY!b?mutQqW1Gu0V> z18~LPYcWaAGmII|iedArPSe)Zneh0d3Fpc~q;s`smaoIjirlOjwE_705StRB0MGeC zNBz>}cY$sc545mQ-D(p8dm_33wFzM=hF}yuuVBBTutjusMiXI#=o}DXa(x@C0hDK* ze;pX4N#l|uQYzED#XW-_rg!B)f>%jmfUAC07>zZIRg2GiiEZr72+V%P_Y>Mu@?g>* z5k^+tb(?naI?|oL88>QeHn1lTa8_UjXxt1o3%p7Y?H|roX+iqRF-W_4p>iX$gX7tn zR&US7Y*p6*iHa=$t6=t#T|}@lPFU508xz9sK&Zvv+ki8kWt?#&a7L&|7zR;4v8X{1 z6@fN+%SrVH6fqgiA^H_(?TA+Gagh)u=WF%m1mRdwRq`ZA%lh7N(0h=1X30@;5JMuk z*bpH#9z8vym}8OfeYBmu`GW55{OhQSnZ8yv-P|16VG{cWWK~@6&rV>Dw#&{pMms|T zd&ZQ!N2{XkXuH%Xo{enyHYDMb()nUC7lVSe>{^7CINSgcLY&(vD+{G2#w|6 zX*t?6bwr~I$sO~Q&G*(dRJ`tIO?$1}v|zd;H`35{cJptpA^AMs!!M533Y zJx9;XaF_Z;6Q_T+J=Kcw_(7vOaCYG+F^A8X|_ zXeOwUZDe$~IB_&Y9sOEzp(Ay}StI{WQ+0#98NZJ9*f|`wR=qI>x&Cz9joUF^84u#l zEcT3r^H7L}OoYk61?HyI(BKI}(*3sKYoigN>HlOxf|{Xt0n$ta0)oYQKahS!m9VKJ z_BWgfvsdCG^uz!x|KKm@M^ytQFv=oRyPtlQdp`}L2^DgYT!{sWMsY9zkwwYeR^C|= z!d`>pooTp|pd7IGC=+{JXpffNh(O7WNUppGM6~E%X|lM>;KZ{X&Trs5s1~RqMe5^S zt!M-bb=FtA&HxzU8mcDeDD#kytlW#Sw+Z?T?}?^Rywpe=ykBrMc{8RZZp7+%F$;w~ z%6+ukrhJ6P)A|4kX7bfBx}dfqV{$)1?BYS2Qb@V+f}ZcWD>J3}ZX~Gg7@PYqn4jO7 zZqQMbxEsGYN1CkHTz191M0UI@lPH)ZJ0VK#6z}>S#MZd+L%Jvt)m9Jm6r#O$M|vo~ z@BTPbRbw?$oBJ4~HBF8)aWitI&8WAV?w5jN-FAuxeR)-~h+X>*N@6p_FjI}4gzpDoZ?^mgPST#5}8&BEfSWIg8 z_IhtCizYK&;}oC_p~v&p&sQ?uqo<4HwL#QS>1dJH;^0v#6D-&HXts)b@vo78@Z)3NBdr8e~+kaYw)Oxf%Vb?IoBrNbTc&_vG0@&B*QMZ|k zNBRO94=C6F?4wuLairYzBMg!^<0O(cBMo>KAZCAsiFxjWKu&-jp<*Mh@;0r)4 zL=(Of?_?Ca4ZL#?1wwh}zbG(}cfL%40laf71tNLppCLexeDcmGLU60caXkoB7nwj6 zV>t*Sm5I|HSdv=sV4C$UTX71#w8DQzVwB||2vVO{mNfv7L}Dt|cZ($JYy8f7{+;@| z(S0x}cVsv4?;O#!){TWP$k1E_Q*vjkLoa6^+r?3w<9*2%SLC|D@3c17VHCgf6HITT zbjfDmcVr#_d7Cnqy~n@)ek~j8gViDJl|EGM|>L+iEhmy3>K_ z$k|W3?~m5i#z9Jai!?kMb40~Z<1!nbk*u38UUJ()q}X8;Wo>|_)Q!Zji>xIFi}1N& zyfme<>=d_plx#gEw)4;j$a}zYFrkrme~Mc?uV=p`Tem?^v2mblz+V$BkL`8iDaH(c zyCEa-N~sOn$M4L34QRGe2(uid&8MQOve&o}#}9H8qH9d%R;dj!hhGJiVtsBc7TVsa9Wv$d-%y4i?9jzr891apDkfaJ=u@Ei7`wu5bvqcE9& zy+!(9%y7iOstC6sE*ZIRc?#a)j@Mg!@XXuTo4N@Ez9W7=u%HHg6r$YDT?l2K;>Fy9 z%D2Z=U4wU?qXCy=v692xVvinHU&zWfVZ|N;^XW?@H1KXTWZ;C}@GpuavUL9qSur-R z78{+C&|F82e-bZ(uEp-lv6w0~jETYtA$}Wj^fVD#b;Hp!WAeN7u}JYun?=dc=jbBY za7O&+Bo$6k?t=EB_?O?wBtW{mGa&qF0_v0R^#zP3ybStyV_;RX)O))EE0*Pmw?oeE z^nNi4MFkIDNI0jUfeRP{(^jTZICuM6Z z-bmmVIWPr8=N25BVl&;$8%q~qm_b=_;aRwv>|@R&yz>kOtzRVZY**5GL};z-`AYcU zW$dQofLK=V6D(rc0nRZ-?X%^GZOVd!Oytu~@g8fUv~iXTQ^dZIid&%6xKlRe z7Rccqc6YVtFE|qm-f~ct05WhN-l2P-$$2;C;KZ^P&SA4-7;j&B-~-KT_c9$RZLP6> zBxRebLqL-tX#-e|0<7_DLwhv8BmWCsV_*QKSR7E#1ajz9;rSTW=kia!id>G#V1A*B zvMDK`s?YX3fuxI#+3zph_`a% zELS{uy|qf59`shxrqZ>VL^rG^`y(KlAc-zIV02^qgFghxb!U856A&|6Z+`MThyc$- z3vJ%d{)>&}PQSIeo2~bwV713Le21d}1^BXGQ;AeVK}MD9SDdhOTanM1$*2;o=swnr zD)m*#55E}7J#@8>bG+q!izca9W*U%HF&LUC8#a%w4>E;J%&SM9yKpP2c!?9SR^GXf z1pV<4S9J+%;5XFKB5D$q$Y(Al3y|PmiwOxW($RM@P#*4nhHD4)&>Lds+C#Zqw*hr% z&X%J*a!k7@b3BaY2CGR?nJ8_W?W^sy6gY-agx9DY9pJiia>a@^Yp z&(1+xr54hc0gbC5SHtNrsbvOy?82^QcgV5tY{eD0Rcfdm2BC|sBN}SsD5}9dELyg9 zs0HPyd03)JKRlYq#~pK>*1Z|Z@+1B=gLnQ7#RQGn_vln+Uw(ky59?BN0cL-QjMa!y z=Bzi`-1pgexN7S;VJJbG!0yU-gjbfG1NC@{x4kKhQ}6VYvz0^|Azn;J za8s#EH{xyYo$T62Ilr@NKiVisd$r;&g}94s;=-NUnsF;ZzL_RD=Xm%^1_wpRux(Rj zf9dnh5feFKy*u56Z_X5KFyyAa1sH4AkV_*bW(tbXj(@aja@rt(y#inzU%K9bE>7@b9J#t{Wdi*8|mJ z5}vOhdv`QB#YSPArCz=_GQ35O8jaS~cJWKI0F~L4X&?D~Tcc5dZ4ZG_an?5*F}oRJ zgtz&3je{ZJt*ynXQ?e6{SdH{m9e%Ev!!|!^u`WFRive_7aO|~ zaE>JbgbEbI*-g>h#(q%o)B6$(J!Z!b9D3myI|i>9_Kp39{mVlz?E8#^Ahr&nLl~afYR${P^c3Zbp7ZxYoa$s!0;X=C`6X%lk@r_IK|v~dhm`_r!%?rK8U zKh=I~CnVG{1WI;9U|-otX}zX{;&OI$j_XL4oQXTP!c|2Vb(KEpoO^}NF|bA_HiX<) zt#^EjjN*-Zyj^tY;tdXceo;2_RdC!&nv8CGG1F#8TNwyxDaykiXhK?h6}_i>Uq+V2 zaZDBE$kD9WuH5O*wVS;hT4%-qdzT)dt;0DdD&cjA5+;cL$^Qfki7YTJ?Cmg2dc02S z7|gzaYxzCOjc8Z1cFmHryZ9YxF#BY@SV#I4?>d=ofT`Fdri z%y^i(2W0CpDZAyFgzOf{`i_)c&p)}H7VCP5SreXMJ-~}7lF69|#y~Zhr$}G|KqN9r zU9hkQ&(U~L1oI)XRh{-y<^e`EmXqp?Z(*c!{2-pgKBcpo;;oT7qpD*Eq%FOpeMnh{ zs<2=tR@a+#dc_{)Ft)s zFaG%7dtNb3?X?klU3&KzhXN&!e$A-;g5)?Q!~d%O`fxw^|aj|I(3%q9T2hjwqq707CKxT1M@*@uKI@S!W*r8 z+*YK4Zpb80ybOP5^3K~sg2XwKrn|qso8%SVbpgl{%B7$reNsnfLMszMw0oCHfzsyA zv*<;Qe`~^>2dmv)2qDX8uqnj^WUgaSMZn3?MBo zgE^h5?7YM_4tZBXKptFmL(OKdA1y&r)u3PKoiYule2+|ctRd`4G_cZMOhGk1!ZQlj z$Rp*Zizg&%i+0KX9(1bo;9b&c42r0!d+^34de12*7>_>yJv2GT(Q^(SbiMPb?#U?4 zS^U;=GRGtJ!Ki~`;qV063WlR>N3W^)h~&h*_}9cgiB8%tbDRN)8DBkeU04VpNSP}i&9b)xEkc9NV#~Mg!IuIN(RyY z1D*B`@WCT!e>L4sa-rfYvA4o=m#^P`!tH(P2|ZjafT`S5{_D%c#P@!6nea=%>dR*V z+d{yqaI&O*gzimCk0;kbF^Iw_13lkX=-j6rrY#AGR3eX0XQ3>L?@ zX5;;;?BLzr&&|U7H-K{;^K*IUB~;qaT;FiUSZsESm~Nw7 z442HZW79U=+jgQBc;)swo?4~3i)+gFnu%ccTQo|%>oY|6c5+cr4F!A1P@rz7zd(ip zXRx8*N!3trz4N4S6~Ax4O&LhQW;#rjy<{ZlKqEoG0m6-;#3;67ZlN7>jDZoeDR=+O zw#>Co%;Kx@VWP9XGf@}rh&|uz$ZmImvE;JNe@0m?P^!?*Qsv2(~Q0=rQK0bt-0SsND z-G8NcJ95X)e!JpVVdn=iJBBb^t*$(Vnpi_nssSp!lGSfloB)=$;;#77Ad|uMzqTJ9 z8MJ;~AJ_^1B*-q%5^NWsjj?{4-yhdrvG@HKAJHb;#5eL!zKfQClPHAHg^z6e`__N? zx4*vv9qAX_-7CvnCmjpqSZ$lTRomuvZ^wP%897SoC%?W)jc-J#o79rSBn*&=6g?kb zR!_YJW|OA@;_&t>|7NrD7TbqjAGBID_1|emm1raJPP}-pn5&I$M*-f%?}#$DqaJ5d z%kB+K83W<_UDP*lK9}e`3*+AzpB!cEoU&3^$Ej^)i%^8_$cCWcR zNi&k^R3;sIa(0M0HND1e7H@cDf;l{M$!BdvB z#^d>_FYs_e42)B442@Dd_RQL0D~|MS$FLGfxA7_nyFPIXM(x9~;l-P2X~EHx%Q~jG zT^;_uW+|WIdso&roBfY?9#VQ!dr0X?JYABo7qjxN|1jR4;OeALH0SHLDBt7Zd8}q? z`Uns@5YrmeD)dL87-&s`i$PgWybOyaZbq~_R!@Od+84^LyvjZKPljX+uDGT-6#3yk zyc~oO1$2` zN!>+btQ}K}>X6`n?Mf5%+3r>{;-hF&u@5cW{Q8IxErzGtAXg3udT-tB8cmYdAx_;z z*|SHJypFrKpkq*${jZ4U5pOUbsPfz_`->P6twm&H*G+XGV4-_QqBWi^_PE2S)b>?G((TrGR_|0T-b~b=M*E_4lG*##Dj1 zA$bz9{VEs+b4L`SMD;!(e|DR){B_26KTww935>a<@&q@KHbIo;xBV^xM{#lmk9+)U zOyDcONgek%Eh1JO_psMg7uP5M#NPd&A$px~n2c}3@axg5$g|4tNg)$g#O6v}N88;` zt2n`-OK1XtrR;%SxSy4{=8e%pN>%_{xr*;nU>kl2KH<<6IHQiV9bl;hK>`TU39m=1 z#QQJSfEub?b?D-Et_^{-N9*I{;aEuAALrf_jXqTgM+HlNyz)DJkbVFCG}=O*(z z9|&=$=@QNND)dFfGe%IP4Q9#nD@(Qu7@2nD+_m#9m!ROaFGdpT_ok?m@8= zisMA{?#=QEe;KW;rm|FY7Gv37*2AaE9BMtf@kq@xu?4dQ zE5NRJc`Hq4>$#fDbAC)$Iz=iNg7j;Sbh~JHSn$s6v#77@eTaWjKLJ^Uaga?UA*gBR0PT+eQYz2l6aTIQDmQ^ zvSCSAh)N$v3%e4_x_A-yQMEbPbM)IfkN0)Z;WoKuI$!TG@H-l&dKQzB5Zb_ubzSkjS}g<@ zX7-&}o!UBel2T>V%J^2dGmNXe&g z{_qTwXS-&`Op%%z`mQe$D*(&;9cDS#WI34d1Hb+bWU*_l@HgQEzb+YB81gasfk?y(ADlszd%y2hO_7)pO_BH;ox*GIkh67rOR{LYn;V!J^<5%U(| z9+;2viOtc{IdLxrIq#t+J(q){U;20G&1NiHJ!F=+m&RbGFd78&gIDfbV-e-0MtsVO z^6T@wF9P^l*25Y?M|CtvfwXS`dL<4r(~IR?gIoa^JVma$lz|+R=|)3M6CBAvFmV06 zX@b;7wrl)-kaqZGuy%+SA%bL#orn3|`5D+^@HfyHi zR!#lP2addxo&lq=AWfgSzDoq&^V;R0>u5Li_4BH6_4PAauIR6ZKB8Vc!~|)#YwRzC zrN6x47eO3h+S^xFlY0y9Q!(qLDj1#JrbiHe5-oJwwmB+-WNtzh7y8?yxlip(+|BFvmY!wBb!a(hwJ#0HxQXeQeaWv#< zHriuCI2L+X-2_!;y^aW<7sGBxfu%!D*p<>Sj`Qxy7LzfJhpv?`m!q(lJRHHuv6xey z{DC2DYC*S(8^lOt~VUDDet{T7^y{| zZkw*5-6+Lk*oe8kP1<8I_A^b@v3GGdBT3XELMICTPRVt*Up=laHd9gtv)j;GG6N2Q3*zjg6V@eQ>}tQ}%97SOF&pU_b8jkii`-rLmjlkPXLmGqc3 zJ1@cBJ;*SuJJ3)cz3#v$ToC2yCaH1Ve;OL1*Zs$GlHc~h084$tmjfD34+w7wKOs+# zw478^!%sALVrPf9sE#<9rYu?KhlFm!_Zlom)V%z*#-Wyz4edi4JmKLd!dqr*&O35S zWT4FTpiZhrg8z-vP|28;0G2>$zyIB7s05vcR{qjysP1@Y+D$VaF9Ml{ITky2)4^>O~6tw+D5U0|sd)?TQ>`>eLr zZ?4^izChM?y%Ax^Ws#Eg6yUrj^Aznl|Fiq(w-+1C9PRhCT7a@TwG(JxnxT`p89ucp zvwcL@Q3X?vv0Hzy*rf09IgZKs@V9f?h;vf5qFn7mv)ERY@L$4iy*?a&vTVv)5IDEa zKnJb^aWJcI=XcaMwChF1-k=!dnD-W=^P1s3JNk8XV8dgT81+STsui#h)yG^zpQwJJ-iGY$a+bs1eb<7BFu_+sY zHtSlkKIH(aulVl)tl=AH9DrlC7uL z9pHD?*Bu~F)Xt!4(S_)QM%?w`Ei-J&)mUu{Rdk4)thiWq=`20yQ;V*2Du8BVJ7ho) zJ%a3sVX77mb1)w8jU>dwOy5Mzui#stELc}b)i^P>JP=z=u}@$uzS3E@k-j&f0}0g_ z?TfWl9!ZdZ^1L)LI;RM5=6pM)Q<1vcxSoso)s=tjPKw*C* z(b(Y2IfSIVE3q_-9D$0XeG@T89gAyUqPyX+k?8$HnWOO{G!My~YU@w+p;s3<1Z!7V zD%*hNDt~8-M6+O%!$5rXt&H)ljGke$T*#4!pF%>WE2q1ZqrIvli*ShTjV>(a^m9!^ zRz{A=@4VL0m6N3;Lc?dywkguz*@bWYw%NqanA-XsHy6*dlMaMfM zbnN`f6CO5u_$S+OS=mOdxr`2k*IaD7k@>&0L^o3uN{HlMQf#1ZEAwz0Hx`q`M#D^y zeJ~beYK)of7+F*sLM?&@iuGY)d$)V}Ww+rf9bH~ghnc(^ij8*MK~!P^R915-3PJ$PX6XZ|VmV?29?9RqunWt)GbC-5JKYR1Yq!kd%k_dS-U{ z2qR5~K}dq0zo4p%5n^{A@B9?{m?8+ZPZR&>;CDpJQC%q1w7L!w^KW2jvvK zhU@+7K51x-UiXRAV)2EyN+-g5sFAX@L#)@MA;j9Dw|0$z<>5htSU;c+VUz1Mz<`B{ zLGgaTDuQY<6-Rn)5sZ}OpuY+^wsquwHN-i;^if)}kiBI(sIQ+D}|AYGei_=^%+g==>G05L2)ur?+@7T)*& zpUGb%UDj?E(xwDxOXPF75bsLcMy_)vM&c&cwK5&P-B?TakHiMmppHX)Bdf~$G|(e) z=x*w3Wpd%xP}+fLXb-9D@=rrN0pzU-+KtdG&Mlm(e&p3@>PL)BX=;VZX;~S0`1q9S z6CqolSa(2d=ZnN{gTo_lM_o+n()!<2w-wjl9oDUNZ>bJ}C}%K#K|j}(-RLstUk$@s z>I|{Smp3vd{SuW}6DU#lDg#h~v!&<3k3qNu6>#0(#31CSR5viaI zp-p;x+F?cPxvu=YrM|8nqHDKO)8aq|a2wu3re4>nc`bTkX%E5siT@B;Wik>eu&%xS zGlf$2DLn{7*=_y27=6m87u7q$HdH}grL~*619$Px-;=vpmz)|cKN!OVwpv7xY>`(R zBz$nLgLyEL`{FTlUu@Of7eD(+b6<4f6?}OhWb{ z$yzUEA6S_pXCHvC5@Uk3rSuMR<{Vzm1}}L8tDQh z)uR!8*ih=m8yl=2sfSVi{s;eIl#l4HhIV&ZXHzJbt1P4U^zxYV1v_OF>?;^S1<+CO zQJ76hxyox7)rX86hk_5L1=-QAk_(RoZ2%N9Io^9#&Ih%nOUiG@v!O@j%#QH=QsyZk z6qY=Yw8Yn6$?a4!;zG{@BR<&7aOx{ zry@uDimnK~T!$a(3uoC4?Ge&fMta(ZMKk_XI}$#!X);T(7_J?S^^sLjip4OqAjaZZ zmfEodozAsLv117g-J1;^X|nd}XyjZcSB;t`_WFdu#X7gO*QUJjZ}Q)pHB;!&uE8f! zy|S#A^BzZU_ReTAxgG6cLR|39cmSsd_ZLUFtz9-{A;F56Im_`0%=Mz=>t!Jn{yyhu zt}Oe}BnTVG>6_cHo5Ild6v4KP1kxSt1+V!O#%?^r{jXncsQ*r$x~ zG1`BNctD*Qae^p-Ww)3uyws8t|H77-X3ccSc;0jZtxD(X2xEvDMU18^7}Z;@6gQ2R z)*-flpeX=qe7jGbqj#hEWf?|>P#r6%j*ywNc-M{OIfapOpX#F=jk)p0ky>nHb^-yH zz7lSzO0L`v)qv_OTqVb1nBTFsa&rimCearf(IC-|QO!omi!C($i*CdIkG3W9G`^NP zxfr^;&DQJ^dbG`tRcc7N8Mp+y8BRP9DTnzcyS9nVhUt#;xQl1q#ycCyhte!_lB=>u zb;uFdY}v*+Les_G=oRM@wqU%p#R-2)LnZnm9|v*GcF3il?zoV!ow96y0{*TDmFP>( zG|QVQdQh`#aSz+9s6Kx5MGPly!K|g%%9FQji>Fk+`gUkY`e8~=I9KwOJbAl6X(%M= zDJ(&(<>d5c*VAeT-DNMi-Os{@5T(Cfx*A`DZ-DNdgFag>$k)TS>m*>*2&x}MzWXVa^O4*Gqx_H8JA;FD3ZFX(aYS`OB-_<=n`X_B(PTvcC8nV~;(Qa2m z{F8Xuf$Ty^@_$AXQjoGr0TKKRZ>#_^0Kl&oZq{y2wYXT)RP%9?O z^fiXEO-+~RE8!^Xfct*ew$d8_t%s?+tYbx2@agdsNZf+(NHtUTGfx4kR7*&U1nNr1ilbksWbV1P2vWLlTDzOctfmPe6K)w~i6+Jv!l`sldTTQd@=>qs+>8RP>|RARVJRu5 zO}f9$z|gN#K`bP^Ax`)w(Gk0b!ne?%;Pb0gvbxFbbR)?mG)ol0uf=PRny4`gWE$@= z?&bYPh>eVNu7=`|dK0%GeoAkaSr0Z-y9f`G;kfkBaf5yf!#rjyInIFIt6mG{IwzPc zpC9(R&N;f-zC454Zuxxbh%Sc6L??A!u&@rsQ;vR_eQwNmWdX-WJ~z^cxEKHWc-MHm z6rRyJ%^*h4{08o4dK79ePFfJ$#l4uQ6O8>#;&ND`HM(>pyFCBT_5X?<-B=`tRTI{V z*jT{?_Ot-1$5^A~%($AERF*1d#}vtiH1F|Uq@=zk1D#}=E%mBqij{yeydNbDfTYlo#U{;jt6w{ABx`Sqf1UDe9o_Z z661C{uf=L_w4Rc44eq2yR%+$@m>Kdx{@!TbiSILuy*%$60|6{MAgC&Bp zWp`rpKld3CSsKcmXu7W|?8IYhvQft>o(~ZeTdrHCwD?_B?kT}NL84sT= z(g(bV;v7jTW-_`cHEZVqylH9GRrs3@DPntP(+-EfKUi2lp%W7l5lChbHVdg*PGS@4UN}SAH3}0UBhfuj;HsKM|*^5|$fW zcv6z_Ih*qDc5USR@!=Hjqxj^ZcqC+qni57S7zHWU2d6Mt*Hele(O}2(Cm5`EAWc%!L4z`{_NqVXk_IRfUF+5~YaU!^^+7<8az>lG@EMjqL3vw0aj` z=ZAvnm|Y2FP^Okg25^Sgv`$us+D91G-t(>MjKmk~iIT-WgPcq(ZQT#(r`|2LUoW0D z8nMX@-y0!Wa&;o(Ubj0aIfat+l1rha^=c9?)*BkWH;88qReQ0}b+}{b>cTwhMYQ$p z#r)Ut>sQdWUE&r@Djg&Ch4Aafpj;HQg@Zmy-RWX`Si|>v@vNTZf%V(~T`u`p>>a!^ zPBu_BkU|Fo&J3z!&k7}gd$e>F4=Envrqp13pE|GNTXJ!|xK81tH8OQkPrZ>`=V@id zzqQ23cRD%bVhNi$5>vWqfxfH?bfpCvyMF-L{bo!Z(Zd*i3~<(2I%LB9&RK?|-vcp9 zu{fOk`j4qr0{ASoF-6)!uiaA|tDRL?^1RahgU|PybmiNQZFg@$Ahi#N<0b-)Cu3A~ zSWNlmoZ4w&aDYDiuQC2AmDSFJu-WnTX(LZFHl z#6I1M8CZorxQY<*Y=~CHGyW=g=k4Us%*GU(KTX=ihC5JEyC({}bN+{Zb?Kn4XOu63 zBUKjrtp`1Ql>*dzXm}K9rWz|&hG=llzsIEx1jsK--Ik= zNHSt85~wErNyMG&LiubBAv0<*KEa3?%a+MxZIW8$;TSs#`xSqbgdTQ$Fw2%3|GBcP zk{uVsymXatBtZ0I=CvUE%B_JH%zy1lVdWa!LknuaSf3g zD}B_n5ZL_|jOH8sSxf!bmDMe~x5F2OS^Pi>ODw)YG0~^y6p?QddriFa9pYgo{>eX3 zAWTS=CvGKW0pUOTp{@1};E=k*4eh41@HVmMfg;HX!ovWN7BNHlV0VVD+4!t#pQ*#5 zVlw`Xs|ZuKSnOy+wQq$ob}ZOzq-4a$#0pTl>ggTJ$Yz5JQ_W%Ku$1I)@U+Tz-hE_E4=J?_Yfc#rsq(M!D+CYo^v=$h($gpkD#Ts%1o>fjLO`(PtA3=_}~s z0LrBKwh%;o;+Z1pnWkTVFnU`!njpi`;P_TJnKNGvrw5~73J-cP8b1&krqyCT|AWy) zx^Lr{y&D0|%91DU!o2w=satygtmQ~`>(`a8ll-Pe>C?AqEWa@ZrAWgu_iU*PX((`y zYFzCBQsnAMBkGWv<>7}QSlfc%nRqP|ZrtRbvRsh5LASWrI-+evPk5s{CPD(;z@(3P z6F8kdW_vp-TKlS7;pb>$hOS5K<3UTw@07|qrBBQETP~;@?ou0G_tZJ!M0g|6G)PGR ziQrTb&jd*32!eFX56RkP>2 z0F?M{n?LdF+{;rc!n1NHgLg*`q$v1cnAil1^F4VL zXT4|WU990)y?ExHCJ{4MQYGo}ZMI_0EwXA(Jx;E(V(+Ai3Dow6c0EC@i`#+Bde{z? z)jmeHkC`Cd>&ts~5#q=zlA4$giFmax`>KANdHuZZ_qg&oOn?*R^~do1hFBKOxzqJP zsB?Jdt08!X?oW@Qy;(n#yqPN^0`ATB7yqWi{YK>uEZUSe#?d}Y`8^;c7q`anmA4#B zt!W;@s-^seurTIKKa(vY&}I%28T!;1lP%~8g@}Amum~B(;iqSc8Vb(O94fSQ7 za@ALS3M!7FyjF$^cjOI#!aB9EaF=xaih|c+L9bfyBxZ8xo5ElU5B2;2O=kz!zWJGs zjmBarvHhMJLv(f66z_Y$*ZN1%SZ}1)wGfq_A2b5VM&F}6&qo^NSTEDVMz~}! zq>-C93xejZjKXwK3}#h$Js0pnNsS<1Sjo<<3lU?i?P7PB)KqTVNB1RXw>KHC8tKXY z8Q1nAIm$Eu7b5qA#?G%RLGX0e3lY-$5axH(YY&?Q=gQi%b)Al*yk=*;BWjv3So;vp z)mTliM0{Pz+#qr3R~LVvLsa%~<4N=Uq~mgylkNv~mU<}Seo+5LCHz|IcblQEgb9tj z^Dj(a4_(n|sV{P!6dtQ?Bk)oilqy2$;_FJ;8a==o1EmL8;|NU18_o1s zw}l2gTF~e7Dn;_cjRwzrZ7l^p;x1G6-O{P!F29Xik>|D*RVAB&L0vn$s2OHkd6V^o z4nn8yfY9d#2(?dy(CIJ;b=(P|uk`}TFYFx?4q7c<3c&Ottk#|Sa~(o)r(U+%M?!>s1pHAZ00DZR zu9=+eQFHaFxqi0UZ-fYM8wqQclVUQ)hrzl%)@_2QnI04WX#&jR+}) z;8;$&E;w4#t9tpHfmb%GKvo>`zJn~km{XpkeZ<+qc`7>9My=xa4Mz}m{6k$7v?F+` z7PEjLczkLdhP;PCY!r-QZ-sY9GZiPfagWb97E}MdXAXW!V(&G=5V7}K-i6nh?ny%K zfi7ix?#2*uzdb83gcsXa_2LCOpdit2#dTG^csqg+S=C!ldKg|av}X+FzKwF@$s^%x zRWIIm?QP$4E2p~S>{)}M0Z6OVXjSsWc4@-71K%z~Est=COU~C<;dVb|pE#D5TTBX? z4cVwZ`Llm=6&IAHnSe9dS3?Dr5X(#rb_Y(knXDinEj|+)DI*?W%X2 z&Qku3g3bO&@7XM6B0AfBtsOV0$rz9nCeJbO>rrEbg`f{~Hc0_5_x70KzN@!XxkQtxf$ch=hw zw%ZVABbhQJ{T3)N3$Nh*!e(!WV%}yz#8JEXEI_ze01*B}5a+?S_f@}0$>R7^*~co$w@Q2BTy$4hvr+U{&Xqxd*pnT01KzjWMnxO_d@9^}AwlLWfxD z*|)AHeLxZ@>PWaD1Qa)xl8n3)1OW~c4Z_=G8HnQJtLf z6-0rivmD?wS=%|KV=m0A+_C)5DK~&r?|BfVJwiX}^36sZCys0H^NfJc-5a2o$B4JB z{}m_Y-=Q#Za*ZYD;CqsG^Y@S@&t;@@L5QwOoSSKzD-06%M~i2p@tQ}K&MO}`=^UP# z^zbB|U`R4JJ}-`jY1N~?q}!ThpNEiA=OQWMUFOWCtdVNtcTOA#EUw{e1LJ*HoqZd5 zAIO6Ij<$v~CKelemLlrI_cxr0aElz*?Xw(7<~Up(9*Wc$ex5omD<6a`uyiIgtpw%` z5_^Q(Fg~GeMTq4B^dprn6U$L}pW47!JDvxGKRl=OkUP)pIq17YfQ5m(wHZw}d{~Zn z&k+Dth0VKDJ)dMb2+CZ-al8@T5nVR4CInvtz6AA!vEbR%gyU;G3<#UAwb4cpwpN)r zGUP=&%IPK20sd%5VQz7VvgJ`2e6#X=F)XKgHp|dEP?#+CPOZ2H{s?!XlVC==vIeq< zz4zsnf00{!pHlcJ5)^=%g|KD@4h9r+KASnu+f{sx_k&`yx4AfkzMm@A;V)g}7r4~i zYLYW!KtqQ$F$LO4UXz`2ew?j1Q_(+)n%{-Fyz`H!^~EW@i-6P&BXKYQA3mjnT@jL4 z)+y~T8!tP$GO{f731teusos{OTx6-0iJTCc4u2gzm5=*4L6<%y124TiMrWluv7)~e z<3sI<+$>>q@zu)aMYzd{o4R@K$%Ax~H4>JC`vTL^^Jp<*%=<~P*|R8c#{eG9B7-UP zW=|S6_*UgUNS~Is`X>;s?tx%JpL8y->}Ihyyxdxho`YS=2-ZkjmR$xK1{^=kZnWuL zb6f*rRDnP%B7$0R)=uO0jUAVoH-9?ph`Iq*H3Dq%HbU=1^&1{C6yNp_WLu$8lZg=iR&Sc8aUyj&L7Y;9%k)Q4y$vNt;j z&V>|%!7)(u4JZ$Pz-K)Kfr^V_a%(TT6;v-W;VN*9KAat4fe+V>@S(JX`h2u=E1Xx0 zsE}7>D6fX%y%McL63Z}Aj>Qb1_HQ^IiW+^ZzjogFSBROBYv;Ov3+`Kxj zINkACcmlur=|Cx07L-y3K7toJbgAj9tU*w3gme(jLm@|=Jq2OUouwiVGc+5q2w}z8 zqK0Pj&XcgDiG9($vx)+9c;_k#4CI|Z8ZfYecX}ug#yeXmpy!>B8MLRK2ciwRFM@YI zgllPE6z`lyfl&5}oS1wF8|sLvHSJ@sM5ARy>?;(m5&IVKuD4K+2UR^r`LLLD{9JTH zxWL{jJP=rfW$U40ovzy^=g0UQk;*l6TE7Jy3$qW%nJqPjh-!UWx)Ytl9i17(xI!zF zA~?qvO?ob!zORBQl*8)6H-y9kF`jR6pR2P7o1oZ$LW_09Ak@X!KLZq0?Z>~hi=X7U ztU7$F-u@KKxLVU%j;ni?wn(&h;uYU~zhC{+Xn?yQb!Aj0#c_^{VxO*jylidJTMx+D zG4YKvVA{9nx@^jJ7^NJT(Jjg=5E|n+pmW3k2u~iAr+t$qy}2nJWT{5%Ij8G}X4zPi z&{EQiY@lyB?-J17(iB7mcu|XeD~^6a9w4;XT+HDjtvvGx_Okev;_I{Q&*SoUA2r&{ z?+djlpDfVs4ZNE}f9`La@Bm~CI=N3YkxT0_n<8B6^JS1swP)yBJd18`$7*_V0f#FW z;+?C1y^L4(0lak!n7j_4z6X#F5S)+<$U?LNGS!4u>od~vb$XV-1qm<3vA{4D*61eJD4U?MvZ~8E9;h!Gfjm^emTD$kr&5Vo+kp%zTt&yPj8T&g}aaRt$ zw!Kd5TPTbW`<4mA#J&fFp}1+$cdeA&!aLD=Aok7oe0e!&Uh>XRni0!*=V#cjeC$^* z-mj-jU^gZJ7^sM?YG`)D#Q-m1z$}BX75imtOnjS-$mwA~)0q4gUuJZCK2lU-S@as+ z17g`BkQ|`-Vtr(SHM&gi%RJ<7fpClc2)2KG4qfTsa~$$HqLu7R=o5hASX>U9EqI3< zq~}70;>cpXQq+yw3DiBt+JbZq)r<8ZHs$z2#8gWYXKqRXY2F$W;2%#(K!Ts$WC*USu7>_w>|3_I@HRITX42xW4R=ON(3mfyL)-2Ee)=cN#+G z-J!ifBPE1R@r2>QN$5q4AD8@wb3Ls2PZZPDluFr!el33;&&N~{zLWxDu0tTD z_$t6iTLI0lk70~+?dBg~knebia{Tp@nqvn(ojVbi40{Tu4|&KB?5_o^!Ba2M=88I5 zJXyHee{(i5)9nR8eSuY`nj^lYl2#9EeDT%AarUhUd`A`|bak$o~v z%1_V08>hYh3|@Er%w*feexKDB{jc<*qHj5A@u@c{kS%^H9+fy}Q(S9R=JmT8>$#*T z;dD~Cu+rrdOqFHlB82%CA4WecOc%>KImbP&lfv+%2w?z-V`aOl4R@q_|J7u;1Np4f zMw~Y3MqyBrS$GyQJo+9(wGZnbNTIjA?+Rlf6v)J2ZnEz2a4yYX@ zvDr@<(ImDOs(ZFivpG0Xc4d)e1?>D$09Kw*FQ=PcMk|+N3TR8$yp0(%WNSywAicG# zIy%*7?Q$M*ob^5}wucB$5gb^aw#~zH88%;*fzFU=;OM-@^l~dZLF=!%)L=Sn%A~mn3v?(2K2dv!Jphy_S=-kP)mBAALhG6t&Bg=j;!G%YmEyjg zxE4Lm71u#wgq+DK5)B{k{l zmB#dHowuV&H^=)$%@B85YkE@n%Ihr`ptPur2OYjM^*+<2tMqPa(%t2KvPt)_w*~>bnsg>_Ws@$FUN&#iMR=Ce zZxr1i+@v#m=FxAsCyyy1X!pxf7UkpdgE+}BGD{ieQ{OfVGBNK?c^mpUfZx{u{VZFV z1Wj4|6Er2{;k2fZ!nA~QC8wK0xY@ff8))cFA#>cRscF?A&|At<>OIK<4||U^beqKX zFbInE;~`6ROMSNiBGTYf{4%UMB+Z>V-g}@r!uyiHdG9|QXkJlJ^N2}D+N&dBE>@x4 zgD(C*==mX&`euIv6YQ8^7^$4fRA;cg4Wl1aqu=vKb5Rg|mm2+=Ke~mj2KISa>XnCs zqbUv-xJ?vIwhXs5lGeNsF0h3!b_&oByq5>arrF48cVoq5Dc{e=OQps}q3?xrW_d*5 z^kDHwJg#)WhAs+EF#WKan2M@JCpw(c@v^wegi)Xlfht&;0P26L)Dqv8Xmzep#J=!y z<39A?wH!%wR7+iDdf8f?&{%e@xMuSKPKcDlq{*8Ph|PvH?-vx_4x0su;K{1(KR3w-|KNgqGC%R(~x;VP>>KB-H4WM)J;9;Dt$} zgj9ZCi|e4n_;w|#ve;p_O;=sb?M2@g{0tZ_nkl)d64SU{_?O87){VAwd&+LKsH3JU z?dSJ#qA~(k-tPRB?*a+hFY5fTS@}`k+FEQ7o_{VMI<+$Cy~)^@I*b#-m9IAb5g}z0$;$uLVO3x@TkOzMiWF&$7pDP`4*!v`_Fdb{$H=rW+ zS1U~S;3ZD~7d7&)zwf#F|ETr4!=4ZSkD3+#zI51A^}hnf@P!w1l*ipyne;Jbkz&<6 zhL16iK>~FQnr#_b#k_6JQy0+ur@3`zb|kuHOUG?lW{H zPJ2G-YJNRF{6fW_|6dhP!Q#*L{@)7uTJaj>hbiD)o%p(Rm_frpb4pU(1^ejS!4WF;o z`>dPLD8RdplD_t|=Yt-#ORoCe7FaIIhxc!@BT6m z4rx+iN7)3tTbeME)(}rm-~W=>UGpym z20Z7|uMBwVC2hcmB_#?`NrjFtf!sgy#N$IkF#01d;pqP-&HMiUT;UAz7$7QXwOmLF z=hxo}B=Bfc1i$Y{x?2cor4$edJ3Yhyk7zpluqWiw{{|V-;fseopMpkO`M55Lciq%Y z-FDUs;YoKpe)7x!AW3)guG=BrW4**y>{P<`g9?iKQbX{H&9BFkQ?P2cck8$eRiyDu zL1RZli1+(cPZHfCQms>y_$S9)B$3k0-CP zt3AD_sFyv#l@E}uPc%<<)sF`%$?$YQbZU(bL~KOwCqZ8;E_j-;OjC%~oLv`!9%#jr zs@7;Jvy*NFIL9?ve}w;j&|Xkf-(zbgQK?Bcszz_|S&pRQ)e~og@%%pP8BsBd-Ff_a zd^im{p4X$}YV_5*RMJlolbAD9>IRgyR+jz13B%;fP7N%pgy5!D>i}?N*^iuHma;*C zK#eKUs-Q%t$l1|Sh`|q+jwc~WjYoMi{>v| zCj4gS;UD(<{zXe2tDx)9ibBD$T0g=6Sz_V`;d3L_v#ARLH>eB9_!3l_r9So&`PH-GW6 z1&=OXR4N2Q=8Svqw{i;#1pJSE+g`U5jEQ|~v=o_g<; zsrSQw59LF`(xOF=6fIg@7!+j9Hcu{`?|8%vgI8!?RB9F+KmrTR%ZiF3ek*Bd1h)_q zxQ8GDK5v7}X2D))Ug*z0jw>x#0Zm@GsC4<_g4G4fmvEC=(41<^G^gGBK$dyY(juG- z!lGqM$8j?XOAAY|M0RPxd}!K?)J&}~OcTyLY9WyK0%2K6skxw}&|J7=e&ND}g$u_; zaH-ZAnNvB6V1Lb752a4ATINnPPqyaVn{MTb3KlP(kKy517MBmH&?cbKyUe3XBXAm+IeX#a<@5*hL2ABvwk`FchpaQEo5zhC zm(ST3L5G(vDO~E{7A;v`y1ZoB{6daR8})};Y@VGuV+t!V8|Q&aqqhPEAX}+`_0sg> z7A;d@uwV1!8TaNO=?klu7XqIu6y}!#CV(9AciHm7rT7bMfp`YD#Lu6oS1?GoE?bUc z$uTCvA=@ZeAS_x@2*Ks}pZTE&=29i*j2RDPvLH9oGLo|{T~M-mxlp)}p<~IyiS*4e zhNAvl**g}ybeUu6V~dve|GF}(`QD`hG^${+c{xm;5~Ss&%T`idK)M`7vcKlp8TY47 z$%l^;VQJw??bmEeo$-+Qp_w)-5*Gx4GAIpkr!EbDl9FfLAlGEQvkex!Vz3aVZlQBV@{nsc?N_C-!tgPyn30V#LNhB zoJz_X72gGcrT9~V`SFi=Hk%8;6eg$Lo0^rMPti-4ttc#6RJ5AS*21C!7*uGr1IQ^E zeoL1Z7B0lV@?|9yfjyxFVcBwuFDO|gtj0x+Tec9U-wI$|Fpn3(0#{hNpa5`FxN5=T zf+gHi2h7Zp(nXIfm%&Zpin;r^Y25wL#U+c7)naEH zz^#7oScW_pz_5_8Xz{{AuGGE=p<}BS7nIr|RKh*R&EoR76##X~k^&AGNhu;?(Xxdc z@cja;@AnR1DmaY*eWkcWaTyja<906N4lZK?mobscxRcBH9hWhQ%SeEv@xY1~7ebw| z_&-{>TKzH4hN(8qI+IN_2*4ajw-+vWbkWjB%!`W5H;gL1!Cbfs7Qa#;K{Ko+Fcp?A z15Pxo(UL-t z4{^!?_bn`?IS7DY{=iIMX2t^XY7k0b5TV8Un;gMK*@(#$EM09z0uh#(k+NYrVXJ`f z0L3topwfaRg$RCuqoj0M$*l#9IZ@2w!h(`pOBbMkg5NYhfEB>3g|?yeM+JebgezkS zD7Jz@ovT5mC6w$GYFZ>?TZ$aa#Mld!U|a$h_Nj$ zSPCr3hTLzVdHJGMuxL+Tw)9pKam^FPns1+Yr;4tmCf2Q2_= z8gw-O3i;kvvS_J5qG3SV285B!2O=h=_$ASCztj)XaOuKK4`kg&&AL?;y+P_-U^m+f zO3m|uY)H`q?Ej*CpnfEF7{1c~O|5}{_ajkI@!-m-nEBo!v&tQi3}C5Tu&8KJ;li<) zpS8%m2$&*dSTuj}>aoz|LP#lL*uZ|N&~Q;K0MVMjs8nVKDIW0jU)zTNCNC-~LV0E> z>3erEq6AGie{3N5-}FDVd;!A|$U?x2fWZFOfYO&O!Ns{hMlI%*iv&BxkrD(vXQ3+W zGcAEC#a^yJS*H{iBw$Ow*4cD>!O}+AYQ%4`O zFIoyS7I=bMAJE;ev=uY@D}^|$l68;iMp&#sg#hJl$e40l+LRl{nKPzPchaViHdMNL z$^2!D7cEdTBDaMGrv6&Id5HtC1M?M@BXus60^yLJI@XMO=vXtUpkvLbeU1fg2_&uR zoR9%E*|Fw;DvM?jRhQMG8IT}tR{LN8{rapH^aE!aEp?2v+HvMI2Vh|7;?*jFl|a)A zX?UoM<^=^yu`7OTrcD`Vo(W@u@oa53-@r8E8*p?1X^YsPtt(!@R1Tw z9@Np&Xz_k+Q7{9iQP~J6&yEtt!I0$t>m2?Fftu2@ z?&=@^08PxG>}tH_&R@^3#!tR7-VfglqQfJC{jMwF{CfQ4E8+wFo4zcd&KD8-aDvc! zpb0wv^`F^#Z&s=~vvA3>lGO|fX=+6U3xc`fZv!!Fx>F`-m;Zmt-@j}n@V_P009Za9 z)5e;!aq(57K)_w5(%l04qUFdPQOJm=tN<2tOtk?sh*3=jt*2zsasjY_2E|3lf|e`+ zkqkt8pyHxsMBt-UzK2uy&IyR(_}}2tFmh-kFsBuO)~Hr>7aL7;o_WHQH1il{FpFTr ze7`zOzmkPn)2^sSv&pQq1FM-bBRdU<9Hs=Lb=AnB%4%j(t<0##t4rvT0>H^qMD$po z?*)q;3vnu;^b3;-$eY&M<@VL3ixz;?H6u0CtV+j$mD);51_H}M$=hrhH!)znQJJ^j z>{A3IYtBrYfsy!7RmOdrA3a#zsUo#K(vM88mC8oK}=XPEn$lhVTdTL zJ*C6|m;2j9LenzMz_wx$)Ty=#WtsxD&%e%!%o#z(D$s06{bR-tX5jlT=}UoD2(_wg znSR^U+p^5bARgRC{~t@s)GslHImVK0h4Wj(m~F{CJALN;_fDT|onfA8O`UAcvZg=u zzzoz?A40cim%hUo{Qe*wxD^9xGJf=ex8 zBdReYhp-Is3z--DWwEJfwhT5jVXesVaT={CSVX4UWmwr*vJRUyOYm~ytBz76DE!t| ziLqwKQnY$d)0xc77I$VlG|$saq&JTX=1*uAg?ZMG3JT_fMrEGwShN_lSTh+^mM#Rb zXF7=1IH$B2MD@~}K}oZN=t80?mg`?9!D2IFrAY?J6WB~(g8D~MT zmT?oV5Mu-yMx?j-n!dFHX2dc_sX5)2t#KQqSk^X}lgnTN3M7dwUbbxcZ}yPnCzyk1 zpC;A2V9CNorH?X^icMqM{?Vjes(%p*gwz~u>J}`AJ^@Dr5=I0A2Zg1;FWoZ;WJOWA z{Ze)L{vY+H+=3Fc+uYTU92VXT@*z{~QL+S~SEUzqjq=Yc|8j;?pdcck*D%&1fbtT= zX#$D_gL6C1XP~dralb8}Xy&BZXmUlh;%3-h(bj%5ZR zW%A@1kb61&m;S)?jC-eOPD3K6Dr&0q7ioC_dCw+}| zh2XyLIWu$b-OWERef_=n`Q+l+J9qBPnKLtI&YW}R+(<*0tyX|8RZ%H@PE^tnrB0s&L@-*S6huOglL*r zjXSE5YUdQD0;;ySn3Iv_ln6!BQ z>w6qDLGcBhgV1;9n8^4>dQFF86=lZxa=>aV9D~pu%wRdh*iEnmDpx>7rqP*~pC`}% zdZU!!Wa zU%{y$^&?-@dH>S5BRrNYd<;m3S;46Cj;$kDjSz$`f9bkouMdHn>iQHm}r`LbvK#v$S-JlG#V@Q@?}dw zFMJ?ZwPovSiev|!&YxiO%9TVJ8$rtuD4QYAOf$0040UWu zNp*5cNj^EHgg!;h;8l=eouy{Pkr*>8CU*FmpfuBHa{^5?YI+X^?~uz_mF-iV>viR0 zyIjonc|?bA6?z(HvlW*!cvMSdoOXNn3CwdV^-I)FF42hRH{dEUrN;5e_0{ zp5$Zn)}9_}qb2}#R!+waiji(Giwm6A5p~Y2E^n-0!~<{f^t`0adsEUm~P z1E`WTDB|L^B!t+Muspz07+_jS(169{YNhy_W>S-aW+Cah(m<>x?Po!W-s*X0!Q|DQ zFJ-&jiu$L2;-s9(Q!bc#;YAluo1T|nQ0Sk1`JA~|Tsg03{(|DGt}aE+I0MF#bu-qBH2fP6I&_kElPSnhgEMlBW{2DhFWQSEUKuHkh^XCZ3g0)<+% z=!rf}1!t4#6r3=q1k!>>btgt_U1d-lO|*p&LJ~B%OK=bF5Q4jBaT46!{R{5yvbeju zyR$efu8aG^VvqNJzpC!Kb-KHzW@@^+duq--=Q4KKu@QU2q%KNcuIZLfc>Z_nJ^3Hz z1bmKq(eZFu%yjXpM#Wkx|8Q;*;eB{f%L@on;{dCes`1f>5QuaX+_A^**nu{GbzWAO zA>rp;S^JFnFC*%Iv;M0a+lQsA4o%Pz7??yVV*tiQ5EYO*%cb>3|z401#B@-YYd)jT)C^jSS7broqi5qbVKihVo-F6+Os}O3|HvBjb4Ddr9Po- z87Pb;DmGmFM|}$#64SMSw$*7%6Q9e!!dOjghOr+h-d(a|(m%6B5i9cvOUn|3={4wE zalgx5{V_mDZlFa!89HQ2ij{hi zjL_X7$>O0#xHQ3Zs=I(7tuJ-&uqD%CGLkud`Uhj)S(OfuM}SYoZ8@V>7ejZaZcT+eJ=8ff zw@T$O2xdm&#X$PPaGo@@LU9J9b`mXdH z4~owzoheFv>FT>r4OnIO8%6OO)s-f+NxIZO;q^%p{t5x3D#m^?@|DR2%M{8ES6Udw zY{;mPG${1KWd&WWI-V+G)h)1F7S!8nIwhx>IWj6Ti)#V*BZZecp1q$?>Dlg7B^)mS z9#QlN)z)u84Izo`alviz3KkiE>IuEHnGvloM}y}z)X8z=+F<<5SeA5h4lPc?^alf( z3dP?}#;k~>M?PvYjr5x`)wKrsmI76l{$}AMPVAfU;aVq^;9He858kTT;6}?%5H*;N zPu7j5b$m5xe$t|y!fz1%H81{UzBk>GG@G3vgFcH5)N?)?UOXVQ6$ST#4G+iPGf*2^ZkcgP8|MUU!gT`%W34; zHtBfaQDS?*X;!@H9~*g>AYDjVN}JG%TC&p(-XCCvovuy2b+rl$y#dxZhU1blfArUf zgb4r50m|4~?}l~<=|7O?R^%1#J@=j+M8G&L0ga~WDHQLTL+T5Qwtb_wD%-zhnL`%c z@8fE>?`HzU!DlKYn|BlUjVJL}+IymS6nY*UwpC|x!+Ch+KPEp*)mG;h6~|NglWS8n z_`ZQehBLXwnKYF-J^nR@Eod;P&1|O?b(NV6Q`}P>lfP>*|Af!AwJMh0{`K|wdx0VNy} zf6A95g)mi~Y71l%A|96VkyRmil4Y07)=SQ*PBg9+gRL#wM*j}2fcJR81-+Bp#RiE9 z2{wuuBCX9B_D2TGUs9G5Gypmq0oESpB-%IJ#4G~(22%T|hTD$3%C zMbm8@TNtuDj@&gkrQplyG{jgdVi&9(`_$ZnaB>*P7|sgNR5V!C)00}QJJB%$%4X)R z)z*2blG=Z&C&c>Vj+P!L|VksRv9VwRKH1HV*}5ylc{zCrJ_0qbH&+FRjrk!2K-m~ zSa}wu^V$4}(E_hY=tLH0{{}aN23Jl1JtyJ0t8exp;FFx285WpdWLA@eu0~f{PLt?! zUFMO2)I<}ueKI{KU@Xb{u3iP!m>6gxmL%ioqG1T0p}h!$g(wm@%MX zIJNO$xbG?!iYjkCtVU}BvEeA2L=8X!Q=o-29r=DW+w0WiFZqv5(q2( zR+7_;;l9_x)b|L#+po%EV5^wWL*LdskH5DU|A^U&<58|g518)OP4)KMibTmyZ)~-E z#>^^N!pHbNiI}bLy>n>!I41mIuq1J7vCe04w;T-d`)cLGQCzFYD9n`M$UXT$A6f1| zmyT&#(Ad7@Dk%pyr*!gdFOA$txshNreB9|ec%B>&zm;oQZ&JUY=c|S0C--0mA|jVS zWuwC9s-AG#GDA@{+|}3 zo0lF|N4Qk{Y<2V;aMZp)CxgNE^4`Xow1~{}=mwMKvtQPOQOXEv7z~i_oH0pG4T`IY zY0{mfKdrVCoc=o)Y7@qlu>jMO77*8B!|;MICJ(033rtn2xp7)KYX8!Tkg)-fERXqM z3CnFO1RD%5`}}ljq~$|FKHPOdpZ0YesqRshMPWwB_0Z-YzaK!g6S0EbMZGS*C>1_S zf?Jlr9ETVzZR{BnsSqd{YQ6qwK)A~$ULG38M`vM%-XP~2C~7S!#s}{&mvDasw%B^G z{Pp~W{CVI>{Qcn25F}rFFob&&-TblaIsQ=o;ni;Gjw%;@*NP=R!2x#~NnbZpL>99p>Zv9RtVjQSCC`#e6Ywgy_u zU~x`XT;TwI@vGqPQYC<l~N%)Lflx;EVb&D;H*3;jxt;K&9EuDHViqK9B;zW-iHI}luJXdS|y)^ zwg?6^n9BQipMDTb^NLJVu(+houOxP|$ER*^Ew+F@zjFKy>0rz*kMomb;eAxjGH#>y zEpCa9nm(32XukSrr<@L>LPJG5+SZ)={a=HRW?47mnaiYF`{uPQjOCA`if{Fq&DMQ& zA`+tK?cJ9!!OzN4Uwpglav_ae~Br<9(!2;p3TQ+ z(qJ;;tgMnBvT`qJ1Ks%7+>R*GlH^F~^3V~`fpoDCg{{>#5?%!1@^B3Zo07v^$*|MFo7t-K&$1^F)+LtxkR(WTM4g zTpH)#Ia%X_NiyNfHN;oe)u9|EG;+PHPLe|wt+XYnb9%=3olB+L%d`G!)X=pastf-i z@l{Xd(HQgP8oBti$Mf;{Icn%9lmAR<**1Bqu&0HM10Png5>Cn`5+OSeX+Vsrfr1PYe$paMH-7n9f{$*OriUUi^N^T|ITi_@_zL@7}rd@dE%; zs4$|#{qh0C`Tz#5L0Jk>pwHdX!P`Vq$~#MmymW?3z#*D#L65hYiq$W>JGLS7*)&on zh+ctJRr3%RXGA(+rxh`;ggoZe9o1(jB*TK`5-;}_<#%q*I^ZWt`Qf*n^q)pp)~Zh5 z0zWD#PzdCN>K##jrk2eLnB<)wMv=Rj$cSv|B$hkSUHpaXv;>`mrlc=|cyYiz2!`zuaxmnM5R*+9Km9VJ)AA6epK@n2{1}3B%&bfbnOm*+u?sOm22CTreN3FXi#Vhq+!w`Pho@!L|IlXZ51BYK7({KN607?R|_H zbneIX2?0GKxcrCY7=U+A3SKJ>*;sD{_@y?AkrO8kR0_~x%{RjiZZX8xpcaB+Km49P zZ06_9ZOZHxEw>`2624453>j`0jZRBs2&ZH0!m=~AE_~(>(W^`%-m92w6*gD#!!DUH zP~Ga86e8cDRix7Yg!FX5Ht<@#Y%c+J@~hdxAOE&TDz&m36!aAu-@;whP22Q>6EIW5 zf9``Z-3+Gou3I@WVS#Ly+4HxKWSZg|9*JL-f(^vSZf|a zUqUtD5RXSVK=A#_dpaWUBrL`Wrk(3ncbi@^Iw3&@NAk0NP=p0%0bZkahsA1;@A#2Q z*B*avJ}zvYwlno&9j_xD+iuvUq=nszLD%+U_&4%*pG3u& zUM-oFH@=_`37-3`=v zUo|j()v{@*9i4p<(!T}&Ew35NDM)BIE*oQvXDL${?CU1+Wo58qy!;?I~4hM_nL~qGR$i&{;Cl(+LrkGeGA}k zP$DRsA%Jove11Zj@_CZM1(rhX)9P-!Nz|^mTdhMH zccFxoeB%e_?Vc?-MQz)!J@QF@Q~`t4h;%j9uUE@9?p_$?e>-1~HMrT0?kfT7yOP;K zP4xH-iKtS-eTwzPhD1zXg?5v_d<4x+DSY!2gIN?5n?+$Z(MFT|VB7C%lKg;U|uZ#6eL^U$%0h=swQ!JfxbiAq zoj{f5kB|jGG+qLEyKG!lnp?D{_`AW?)Ce}C11plTsd1P#co_#ea|zG&of`>;(G`|cAR^Y_Qf%|*v&vS}c=yjfkDj#doFYU}xeAOUvh;r4DlufVS07?nn)P`v7;4u1>x=+d)(C9aVT6Yu}vuIzf3vUJuDxLp3 zssRNVcFS(k$coc~Ha#_}O5c8Yu3eyBkwo?~cBh=w?z)V$@Sw!qiHWsrB<`@8~EYnv2beP(?408R5tc(A4LczUa0-O-=>#+Y*POP}Z( z9Nkg<{h|tOWe7pKIp;ja!LLkg#|smJw#VH2?2~t$Oxao4kMR_ZpBM3}^q)|$G=ed_ zHXIa=6kZT@8F?8!6s|)`l=Tn8AG9t0p9ov7JwEA}IHen*W(Z272+;=a6(DFDxkzz??$r}R%Q5`GWvD|`ez*O#eT z%$@t@eL>sDJPg1({eXnO4jlADHr(?m76sae*KlX78}hyWPZiU5GX!vW;P4k*d!jAc z=GD5}h_4s%xx= z-k`Sdz;S;#M|#G-6zF+E@`BP;qI;q;iZlYf{|+3|3gm3_4h0*QISv!<)S*R z9#=d4y<&XC?;pbw`>4a3%%&41M{ZXY^y-qOmu=X!XlD4L(BsP^dLWXpE)pWXu$~VL z17cz(s7V%Qr@G3*QaMPB@LUwIUs~ZGJ7>ESF1y@NuNvJ%1d2f@3DM4Z0s84;Fas`{ zFRN;~2=J0$>2EsxKf)oV&dQQin%~|9h`={u{zp%cs^)J)WeJ5Hb96aq zY*4aR4u43p-=5%0@R~HcVDOSO7YKE3gv~Lk?!nc?t1XM_9Pcg?CfS+)8a06b&Ml;~ zLP6>xzwqx5sck`N*Tb$SQ3$1x@|mrj9nWiy(>T-xjQ(bJ5gG5UJ45Pzx}0myZnK;0 zqc0uZz*3ZvC&)S=P4lz_ecl{BA=SS-;8HsO@>bX@_#|TBVY+10bw`%<)JKv6gHwqm}D58Z3Jt4HY227FUgR6q4zB!GzQOLlKhRMSMoD$znxw;vM9mZ zztce7JI3Y-0XM7H+y2?8w~njm$ZI6a&bbW%?T6QDQs3K!Zapp6eb%>?%iC(dyS~cJ z3I5F1mlp*-vaW3RAyepWXZ0K8z3%@serX>5t$)+M9z=lE){QIFyya!KeiHy$>jK?? z;|r4vd{>?dQP;^N4RlRyARubzm8(U$=%aJqfBD*Byf_@ge zD<(90>uKajy2~qHzbhs@dgtlr$h_+*PgwFa;wuj_yw`Yv__=h-mK|1f?szdP2^s$8 z4-fCbi7QmKA;1Kr8=N_pqVK@;z)~d?Y^rje8a|5lQjY!aO=3y>2^&YJ7q8l8!eSy0 znzH{C_R^Vhw}~9?8G06$hxwhUTT=Z?>rxyzZxf1}Xm9e?v0~~e{{j&IbMw~ii*9gz z?Dy(NyNS;J*1z<2cEZgh-(Q1BWWoEKXc=DG(zrD8T-qn@i|d?Db(ifcAG}r-Q1KQG zQZQ5q-nM!m>WA)UGG;)yKgrL?}~ zwS$)_?K%Sd+=uNho)wC;O|IhF2J)+!z|d=ECrlw{M!Dyj#_>+s z3e4Gq>`RZIYi$B9!i0g+?*^e(&8%T(Z$5KcRzdp?98SmBgnsel!}Cu#2rB|&v>!mg}C&(=EFq{9JuFAO!@=&JHdk>8!x^Ft=(F^8T znw7OUU1U9-TX)y*PSp?^_qt?JW=}cKwZtTeyK&5Nja+ih?H1p*oW8O?QigDSD@?Vq z%XreSoxujT)(W1H3-Y_DA+hIAX4`W!7{b`_vS@X5x4!_bO{6j?(Q`y>Ue9B@1x&F? z=IsNrj5xWj9k8{(w(_Xl^yCj21^F7{)hy*tpP8HM8^?I-J1<> zFYr1F3q-3B4$n_V8#N5Eoz?I1itf@8qae-OQ@DUHIl_EO41`P1>R>LHe@pm$%5<5v z_uH0p9g~vkeyyz9;)eKbRNj>@q6+0%e7J_T@ThS8&Qeq>OJ#XQEa%r*G)FGVM_?RuBNj#Pm0anj-#|CBj4Y%H`7oF#RAay`8{ zqa)s4UD#Iih*tg5&fh`*zQG=d1ZEBVavk}#=#6CiB4BDyh*)GG*9iF zku7m-f9La1xe;tr19N~udXyd_Rdf->zP+t>3}KgdEPtBoZ=_crIeo@HtH9h|FmocA znQPmQH6QXN7M6XwVl5RS&x`px65~K8=%rJ^ypYLvE9qTjvF8 ztJ2yX+9wdL_}B(F)Pk)u#oUlXx0b-Hm^2q93W`CGMDlh(Y z=v9(PsosyBavKY{lL!)=hb*;w4{_4Yxdd&Ju%`r%=(+KScZCmo(syvd-D@+BhAgpt8lWxZhFB{051i0b@~ z+Y3D{HU}Du`pw})Gvd^iroEguvt$(Qro&}q`_w+vSjZ+W)#5}a^_H1sL}03}tG)>6 z(fC#O%kBh*117?UKX#f`e$xAq_{6mr@#JbcGT6``=s{c;cS~Oc*=Pk;eqiSGBxHYQ8#OrJn9IlL52Ti!ssyF_#= zvlqrKA{@PrJkcnAl?V~aRkbUj!_i(m04ed{k=&ipMKzrvs^NRpj%~t+y(%@gg%R=C zhAPWhRqUn&xi#8KV=2^A7-$!T70J240}c_H5G0O_Es3j}LnqI_O?5E8=4Y^d)OoQ` zIraTEbH}j}sa12_T33q9EVp(lQrCKWd61{X%^Kl9MCrguYD(B%n`A|Z&K9*Ic>25i zTTy@7&6inGCMLcLmu3JObf}6cnQ8ICyQB3Mm=PhodaPu()&QK!@SWFCdZe9ogUfiE+L;PUG@dawg1@688mwH ztknF?smGeE;8(34(ET#z8lH<>RsP4XyK<_BwZeblU(3PHsV_y|2l1nHGNg6Kn`)!g z+IE;FjNi(BwWZjF(4pDQnRhJs74*UD@q*ZGm|U=-I!i@cin=Rl9SN^2^Zx~?zuKw$ zw&}f*cxM0MN@k=#06ak!%pb!gW_vkmyq&AlPuVY^_5@x(xm^qAz9L-M?F4K9Z|v%& zm^(hRc4SPcXXF45z1yXjxhV>iI5fiWt(-lmYDl;hjCiPIO45AMfB*SilE`_GRz)6} zYkT@7%Sf6vWQ)8n4rM=H{M7q*R}grHQGI*E7l8PPNqt|B|3uf9E&XUt&V@Fck?EX& zCmCa1%E6~dt_l{#igL*PHFXylE^}fz4vin_qf|aD9?SWls7$lrnq1S7 z_N%UP`NSRaWq)jy6p0YMle%^nBAAwkAZvnZr;|Ol*i9O;FZt1zE${Q5sCm`FskHmF zg>3uZHmyzT6m|v9YJT1IUy?PNAJ+$^L}+~bfab4`!`&=8#tFL)#6Qxa5A>g+!){K&D7rt)&*MIhUMshp*J^iU_^yWKSZ3t3EE(2H%uFd^ ziwcD4WMw<6%CD#jxR*z0C=phfMGq-k;%JRE0b-2NHj9vgJms?>7Z$=X3Hq|>g0n(i zsr{+rPLJr4XQPKAQL}s@1OvTz;Rc@b1!D8k2gLLdg5v03&1}mC20nXud(A)mmM$a9 zLm~x9A9{`!RtM$6;YhJ|#Dr6OFmq(P_nwQKDK^Y|{`$9L@O{GWTv-LZ`IJIpWWjWX zL#yYHGBmK$#>2LVBG3Hj$N{va@tQ^Jo_m5Lj?5VMx|KhF8`dWF=^MWA4OKBmVX3`F zAA?N1bw?_gE`|gY?XQ+pY_>M4D_1{f{;sxIzruXOH+acuq^s`AR}k{!7mhHmL=c&a z7%9s~;OT_+sj(Pj8=i=?@xgl0(9ruia8ua2_AmIvhMP$7RkIqoE}3FSwbYHasW$(= z+gp~-51McV@o<7lV;^MGnNS72FfSk}l?B*W=1tc>bS$)Xpc8v#1HaY9r{V`zGAe7V z!_NBhqetiqkq)<2rzgDK^2?ow6%0$MmYIv`J67siDFzy#= zS-pHKM=rT*nZvF4c5CEmjNZT)|7-Vdu2_>jiqNv)_gOWV_s421p|2PZ zWwZFW7dNQzz+=Ka$A&_Ss+c6%(8@is!Lc?+fOR`*ukt)6sDX=q2#?l8)y=&CJB<)( zWKE@Plb*x|TDzMKi-(tMmmVF3+W%{WR}6HlAC*Mp!PYv3$c#+ej*p|s1W!(8rvbZ6 zKXSM?>GtVa3bi;FR>ew>Ok0kRhwJR`O;%DGV2M1Djzg*TJX&M)Do?<977QAbE+Hd& zqC}DwnkF;c|I}+U^jj*t6Cr|J>evS;9XUy&^U5al?A%v1F0)6^5+~KE40xXicZMpT z_NcYPM;$;oHkZNB>>MAV$`eO%HBu-mt$JoK)aDV=Hq4g`ZfJVuO#Z_6%rNOWI4GEz zHkUT#a~?0TdVR|CyYIe)Pf@rd9G>2DPEKy22jf^soE`H%cF8>9DCcDx<0VzDhaBBm z$4eS~l1#lwdfiLW7-BC6sgaRRCr^` z9{!h;#KR#{thM$8C~p3ns5uKx$T;tjcEO$N2t2s7v!r81hW!-9rIc%2lB@^Ec5Yu# zTbBFpDW~h|G|3_Fc*_@J|0iIe4=R3zR&g=dF;O61XoKEf|DtMxPrz0-FBkn?cOtB; zmWxDa`qL5oE>l{*E@R~NtcTVYnTyfci>^Fa0i93cN8$iw3w|lN%LJ;`&)%Tw*-zbEo-uM(}{xZM2@v;T0Iv;&YaunZ$`H ztT9khAXCA|%arNdxjQ;}CCOZ0bUa|Lqs}*}#5A2}i%X}Vo_0SzRq95+vVYNQ=;V@J z5c$D6;car`5pGxm?B?wf{}02E${NdB{wiIRJb+nl2l$LW0YtbN)*N(y0PcE2jkA2- zTD-2wdf3Q0j8=lah^6yKSy0Sief8Qwet&*T)$a^)J$NDzS${fT^J#xt^Lc_kRw<#cOZxt8n_M)+VRGr3>!iE0FAd1J zeO+nW-1)INp+jWXX4qkZyUXk20yDLgO}qe(7yp}{sta}`c}gj}7~3S9QHaB&T&g$F zBWaOS-X**PxDd5DOwHlfcej>ADsi;bo|m2q6j3tTzX3ECe~&5N^mpHXG9~ehQ02E|m0bR;dbSL!oVw*jBs zJJt0E<5=%i!F)L7rnOFEPKU<)!%Y$Z`SB^{q}>YU3Jj4g?mOR7rKvfllzQu zvQtQwKV4Ni_7j)>?Yzz9TC)?D50X&$=8H{jSXE2MZL}el&#_(jOM2ISD$k|Oi~_QP zpv#G)Qh&dQV?^mgp+%t}&@y-9x}4a&3$~G$T_n#o@5G;2MbiC^hH_5M=*Gg++BQ<~ zK!^UiZLdQ%4v_TyLL;0_BS@nVUrnLD3cKw!84%89H^cs0gJh`U_{VR3lA#XK@l=)< zhvT4DRsPu(*OVh9OR0#oLoX+smuE{IpQGJhIGy?PI6PJ zZJ60d&iKsYWc2F|^X)akp3}6Z*ZK`6ZzgCwU@h9d{?G;yd-!?|y40Me()S9hpCpJ< za=qb6YWT{aM>=zjZ3u;;lL|g;w+DpW9~>v6`abo#J?_}Pb}No2c6+{-VQrL_YZwm>bKLa<%HV&luUf#m zd0aC83>E9*%xC(=g@5X6$E`(;d-{}W8SGqakhV^= zl*n0!TC>fsSe_;uV2{Xayl`ffMu4r$d<%!H;?dBpg^A5KVAQ$#iC?UF!(JC#=7NnZ ziFWO6ZWcEkcKy7fsw%yX?-|$7nN$k5SUirc7kZC_6!eeh(L9qq%lH9u*&8^W=Xaby zM;B1JywQebQP-9aS~sn1?P7`MntX*#Qf-q~ZBx?V>hJKzN!H(3zr0!wV@RnL#P6P% zI!qXfQ2nyX@?Wr1X9Jq}cm~UMP?pp_G^!}rowko{39$nWll}|91rVr1BJ0O;ybQVc zjppw5eAQ1DU@vk+fhNNd!a*dob(?4^ub#Ed5%NO%SNl7ImJlhSKeXj)zN+<$Yr`C! zcnlNCG8=6@cxH^HF`OU)vuE2ohSVL>UpnmBeEEh zrE5*C)R%?79SbdNc3f4bel=w&BBel^4I(;WM@w;KbL&^bn#-Ppf29b6T{@_E|Bh{S z512b1xy54Snj&oA8}IY=f6_785j>h$;FT+ea8Q`13pDv?%yD;iOz8V*T(7EvP3Z;; znuxX8|BFLgzq7?dMmSg3AM;F;t9jg~7qk{+XV_ z@0egywIq}ONA`sqhwu|O5036e&f*0Y{`3W8uz@e6%r#91_v7$6dgR1hB{+ItVeP~m zw3M@vlrzsGRFhiK!xAulLs<-I?^L{6_@tBEd0r!Nj#qF|l7F*4N zFLwtnQ|Qva;rX}e4NQEk68*YmTqf*)G;F;_*zf>~W(TjDnjh|wpn34KWzEdoT`8$TTA%|Qn7Oxys zR<14$9aNrJR=5-*_EN53B}M7bLt|#1#Zl}ut`vzd)IH}uISNvtlX9~fUCB*kC^Dk+ zO(Q=Bk(8sMlfpi|8P27MJe%7u%tUX^{IQ?zIS?{bI}33;TdB2-ahwpU6EtwzipzFu zLJE24L}GZ-{cn>N+*#Dz4DH|U^!toLx|{*z3OUg8G^T?A|B9e?ICqx%KCGv!9v7Dg z-^6WYH1-lNQL?g)y^WwVaIo(M31|i7V<-MYv8yhfdYtJwpqN!MTFBK;XHi`s0U_S= zF``Z$>pPbU!izn}FYqP{=A871+ROyddUyG{7nh+SbuRV&3-NoN`g1uGO|C#Th5w!{ zK83_hhWS$eiJJ_=wgLP~q96HO1cv9*1bZJ1pn`4|3>`hCSK5vqCmgCa4Qb!Vwf0y@ zjg&KA2)cRgZb~Kh_w&nZY_h-crJ;A?z^aX&EkkWTMx%U!o_#*Q$u?rIg!w(*Ru5Ki zSl`3G>9ezRUd&$3cV!fA{j&laR4bM;B@uPs*ELz3g*SeOF6%H@i+U*cc2YC=Zi_Tu zZ+;B|A8s;XlY+q*4^P#=O_3N(b?F;{>-qAtNiLQ%ErI5XZ2|YWBs^S2O~%E=J$(;3 zRwnr~ulBu%X8xkCZ)coVHfpy08DsR&egJ)PE#OG$ta}FNg^s=Jq;w{5z^o}hu9yE( zoKiSYFJ$K7v-QwUnEEtsi?!+3(H;eyS0ddg_O8FWv}D+l<^>%pPlgF>SL74Z za$dM!Ipuhy&;UX_Iie|3nAp&cr^bXDJu9&sAg%W748dVzTYi_XyYm!TYhjkNWaY09 z4#WIYy?UyJ>XPh=%Pz*;uGN$}`A*a($zPXi>nwH4<$KP8s`}Ji!9+SdV=jOR`rtwg;(kZk3~Hk>P1WH2WAy@7#wsBm ziUfMVklJ<$RuYk+EwF=Tu3LNRey5v#fh>kWqzw1!Mv|01Pu(;@z94UkN=O?03263s z$QeRuYb}JssS$C1S`;XU{?}`9@?96`f@zk_&8S?B9?;M}B?%)!ZT~x)Ed!ZeSUDX~ zNnukv0@}@YD>5&zpR6ppTtGXf*(#q4?($v+xDh zd^Ww`9nYP|NOaS0&c7PFgMPi;vuxTSqdqqc1TD8$tZvc|#UMSkPk^`@s1ESLuR+WP9Jx^!-rn1XkC+`1{$? zwvspZ7f)hgxnhR_Z{@FP;GU<+!=Ioo8_c&PXatu7O_`TUX~fcx=mnxw5&!~#amt98vv5f{aUGTWI+5%=wn;|EMl`iI0cY3FxIJT#XQ-?ad0j`anBEI@p6 z=_7$yQ9|pY?>7X9-7tY@6gVZHQ%*WrC~X2*=Q~#CV&a9(A;r=+-fMHLT6&mk zy9-}m_#s47@jJ><_iqUl zTblyb4$(3;l;T5DkcckLV3s6$d{=lC5Rzmk;h9}M!y0$sQsC+G#(zZudaAg761LDk z)UiJ25$6}1Mu_1kihX@I;GH)RF}lTv9J#0lS~Unq@vp8szhoT19>G`Xubn|V-Klqi zoxgjRs%d;?-)#j=JOAApOcy>8bOtwMs$TnvK#)tHlL=nvAQ$@8e*GzTP6Z<0J4cRu z3i6GCw)yYR)bCWdYy*1YhL~07Jujs{$N4(7V)GEZk;VGney5LzuT(1fZ_jC7xXZQ* zC!a7Ld6py4AO=GEhqI4wXObRt%J3-S3<$6dbC=vyzU{Q9z!tF zFU8Hw)}ECQ_O)oj=1D(sB0Rg!KTP8EBbia%eX|5fwHG{u*G}sdQxB8&NxK1)Im|0Yl z&j#yr_^p_6H|@k-dcw$C6tx#;?0LaEA?j+9{AGuj{yy#GMjGhAy|Ss>op?LXSl|xc zgVKO5z`6{ZRFDu{Ifs}pB0oJBb!`k5+oj(KR;;3CS>w-nyNzR(q^H||hs()(Uu3JN zD>cM+hRFDdKZ>jm_(k3>waMN2@lRSbDZjK%n#*|N2^pwOz8r8S9Z*^WtBS6#&uCsq z7l4QxH!?7~{X=1_&8z~V!z(C*RThoU)OGwu&VhR2lgLk4kX3;>*OiDUKbT=KW#UDj zJWEI^+%E0%A8j8e2U_tV-r?XG#iPeJl75pbpHu%*hX~vuq9jsV2Fy zYsv0^Tsk^-K+M&>VpmGqR1S&pDvt3=iFra4KF5Q#80_u(*2X+h3!fuDgD&tyiw#h+ z)_w7wcaE+(^i!(uo+p;}&AM4c5L_QkYQ|Xu^nAHbug5)745nBw#~%@W`7UlN&ATX` ztrs9><%%LYF0lO$oswbxIiTxVam|sri0UrCB8GpJV?phN5Dt3A8Axj%q_OWPnlv7y z!GfpJs;j&2D3c`TmNfqF7&|)Q=vdwDH*I@8|9aNKm*PXt#dVk24<^2LPqzP1JEGR{ zLKcVf6dAUu5~ZjEwTm^{8sr9}DtCjwU#TySw&gkxh4HF%m0ssT|9BdWn$xY3IrW$C zdP3H8m3?LTv13e1DU_~v8n0BEkcv8>W?3a?o!Bw@+IWj-?=ys&IHvf!XNZJAfv1f| z6JkC$`~-VvcrJcl+jo#pMqRdxdOWCukXhaIw)EduD0`}<)e`skH&@U)*dK# zD%v~h3BPg|a$UfM!wV)F0UZL=a|GeF0KFP3-4RFDCK@fq*Bx%)szj~Y0Hlc_+lOAq zH*D%VL}5W^2uGAxlE=x8O1HKb-(}}FF?GKQbAKi4_}@~!3ihhIf^^TVV9Jv*N_DQ( zx%fzdQOg909j$MU?nJ-VTei8n=TQ}zhU{bdUc3*Su^7tkd<(B%J=FCOD( z8G8g$j7bkMo7V9!^Aa@`w&P3!BdI|9uJ@J*DS53$9J7bkk@vLoK^2|!6~Moq-e7r4 zH3{2OcH|)9_O4{WxG(9Ds>U{Fl9XHuooPFFsT6mDqfK)-q4n9v=uL;;%cC1VNmw1? zy6jCmn4cKB7nJH=h}9x~X~>*eX^7h#=a7gEjTY^kD2$h8W8Ww{^ygQMu6g9Cqc0{l zQY}vY&%Tyr@AUP4_RQ;iZs)$)@uyab-TbC!cRp<5iZbCp^N;kZ%2e^Cw&(O%?McpYRx2yMaihpXb6mkz-S22KLpa4isqExWM~j$006XyRlWcK literal 0 HcmV?d00001 diff --git a/extra/hd20M.img.gz b/extra/hd20M.img.gz new file mode 100644 index 0000000000000000000000000000000000000000..d2ff4775dc228c78914767391e8bab969057b503 GIT binary patch literal 21877 zcmeIwYfw{X8VB%L1hmSf(?SG6R-oc=6;v|evH^jjsEtKLBoOKl8Ypr{ESL~61#FQ; z%FVD`++v`DA*+Hcw*<fD1B^~A#cj_~=ePtaMmpsgfBf_|{Y6EKNof{0yvZ=I%EFOzF~peN zPwKG^AohRfYkA##lSzf4uHqRk7K~kL?0+fFh-^>)Vl1Ldm7P;k zqDmvzEG}Anrn5PRANJMfGYjd?oLZg}x675mU!RohArmZN zT^%Z1c6g>(r@w#bMI%96AwFQ9#an;))Q?NHW2c6jJJlSWA1eH$@7}!7DnDw7$D%7G zhYr@{OR%TKeSQ57JI~On(!IBg5~<&o*gCGBz&Ykgj7{}C)|`+Ndv|X379ff!cVvE? z=oQ}h=+i;Y=AWJ6cfW*j+2NcqiTUqt^a~9<;%=tSh0Z4K_x@gT0CVB4*%AIE&tsx= zj(JNpcR8Cxh^$_1rs6S`w%LfNYR+H*Sz2E&4=vrY?<1O~O`OS8GENS>-6!AES*c!p zcjRta^sQ2Qdv@$u-umI}^p{v zr_kHe*)MMG%{}ZFFBPq|G*T*e&mdilC5_4ZdYO(AyN23%9wlq4jk(QQQ17>iXSdvN2% zx}-!rwyV27Zqz+0slDZW%Fczz5aV9wh?aki91Gu=aP6pXZvb7qk#^^S)Mh{I&HeI zIg!s#pHmYNnxt*kW92?oaFRSzS-_f1maE#oQzTY1mXt;1Nkt6J>l(Gj=+KOUra0d;fjS~hJ96RIDx*BcVrj47>xDIU{ z!x&7&P|UP}zP4Ylb^sY2VE71~*oW559z^R{-=G^?(H<5Y^zoi{%M!XJ^=mY)x{1am z+SZL|a72417Fx0VL?ox2)44mwESBnRpL(L%cxGg>D)I$CGsB6P#*^`kU8)z9BKp;Z zpoW(5m*?i=%D;9^bnF*IG_1Tp_$SJ7nbIhC(%+mE{SVaCg%kxp!;I7>I$6xM5&829 zX;|f7Q$17dmi>OXH$W_|Mf#Kk@{hczf|X`=CtH5KV)c>{m70&VF*p1vKh$p=vd`c1 zc6ZC<9R?$@jrv@0j5b5kn@VzuAbHW_p1v+CaEND19qAk;u6f9rcPA{on@yV-jT5>! zI@XgnF8YUxX=;^dUNS@&Hm;VwjatOn{?Gh<$wWCeZrbVspGX-W0zB{|?@ab@@m@z0F?IXM;Yc=y|dfA<`PX&h=Jv1Sh#d zAy2tBE3NsBF0c&*KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00jPM0Y>X_g}s5_ z>1`LHZ$7k2PEYw~$s_xI-^Z5rmp<0l*Tr&sB9miNMhbQteyD5LJLs7_djI@4A3m$! z@pscE_SxOxakX|`pQrI`?d%qH|LC(pKM()`5C8!X0D=Egfop@5fddCE0@hbPTJ?G- zz=k6~ViSEoIjJlB)z$AagPNO)+T!KmE4&+5g12#Oi#S4lxs2FMcDHh1^$X@7JROsr z*)`SLL8`*@rl-R^xsL~DEPnVh<*?HPc56k%K=457!HfmG34=>L;7!3(_D`#}{7T6l zWO?jRhbU>yF33`0 ++#include ++#include ++ ++#include "vl.h" ++ ++static void port_e9_write(void *opaque, uint32_t address, uint32_t data) ++{ ++ CharDriverState *chr; ++ chr = opaque; ++ ++ qemu_chr_write(chr, & data, 1); ++} ++ ++static uint32_t port_e9_read(void *opaque, uint32_t address) ++{ ++ return 0xE9; ++} ++ ++CharDriverState *port_e9_init (CharDriverState *chr) ++{ ++ register_ioport_write(0xe9, 1, 1, port_e9_write, chr); ++ register_ioport_read (0xe9, 1, 1, port_e9_read, chr); ++ ++ return chr; ++} ++ +diff -urpNX /usr/kernel/dontdiff qemu-0.7.2/vl.c qemu-0.7.2-clucas/vl.c +--- qemu-0.7.2/vl.c 2005-09-04 19:11:31.000000000 +0200 ++++ qemu-0.7.2-clucas/vl.c 2005-09-14 11:24:51.000000000 +0200 +@@ -146,6 +146,7 @@ int graphic_depth = 15; + int full_screen = 0; + TextConsole *vga_console; + CharDriverState *serial_hds[MAX_SERIAL_PORTS]; ++CharDriverState *port_e9_hds[MAX_PORT_E9_PORTS]; + CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; + #ifdef TARGET_I386 + int win2k_install_hack = 0; +@@ -2969,6 +2970,7 @@ enum { + QEMU_OPTION_monitor, + QEMU_OPTION_serial, + QEMU_OPTION_parallel, ++ QEMU_OPTION_port_e9, + QEMU_OPTION_loadvm, + QEMU_OPTION_full_screen, + QEMU_OPTION_pidfile, +@@ -3040,6 +3042,7 @@ const QEMUOption qemu_options[] = { + { "monitor", 1, QEMU_OPTION_monitor }, + { "serial", 1, QEMU_OPTION_serial }, + { "parallel", 1, QEMU_OPTION_parallel }, ++ { "port-e9", 1, QEMU_OPTION_port_e9 }, + { "loadvm", HAS_ARG, QEMU_OPTION_loadvm }, + { "full-screen", 0, QEMU_OPTION_full_screen }, + { "pidfile", HAS_ARG, QEMU_OPTION_pidfile }, +@@ -3143,6 +3146,8 @@ int main(int argc, char **argv) + char monitor_device[128]; + char serial_devices[MAX_SERIAL_PORTS][128]; + int serial_device_index; ++ char port_e9_devices[MAX_PORT_E9_PORTS][128]; ++ int port_e9_device_index; + char parallel_devices[MAX_PARALLEL_PORTS][128]; + int parallel_device_index; + const char *loadvm = NULL; +@@ -3184,12 +3189,17 @@ int main(int argc, char **argv) + for(i = 1; i < MAX_SERIAL_PORTS; i++) + serial_devices[i][0] = '\0'; + serial_device_index = 0; +- ++ + pstrcpy(parallel_devices[0], sizeof(parallel_devices[0]), "vc"); + for(i = 1; i < MAX_PARALLEL_PORTS; i++) + parallel_devices[i][0] = '\0'; + parallel_device_index = 0; +- ++ ++ pstrcpy(port_e9_devices[0], sizeof(port_e9_devices[0]), "vc"); ++ for(i = 1; i < MAX_PORT_E9_PORTS; i++) ++ port_e9_devices[i][0] = '\0'; ++ port_e9_device_index = 0; ++ + nb_tun_fds = 0; + net_if_type = -1; + nb_nics = 1; +@@ -3526,6 +3536,15 @@ int main(int argc, char **argv) + sizeof(parallel_devices[0]), optarg); + parallel_device_index++; + break; ++ case QEMU_OPTION_port_e9: ++ if (port_e9_device_index >= MAX_PORT_E9_PORTS) { ++ fprintf(stderr, "qemu: too many port e9 ports\n"); ++ exit(1); ++ } ++ pstrcpy(port_e9_devices[port_e9_device_index], ++ sizeof(port_e9_devices[0]), optarg); ++ port_e9_device_index++; ++ break; + case QEMU_OPTION_loadvm: + loadvm = optarg; + break; +@@ -3771,6 +3790,19 @@ int main(int argc, char **argv) + } + } + ++ for (i=0 ; i> 9); + /* ---> This is equivalent to ceil( (__e_load - __b_load) / 512 ) */ + + /* At offsets 511 and 512, we set the boot sector signature (AA55h) */ + . = 0x1fe; + SHORT(0xAA55); + } +} + + +/* This is to avoid a cut/paste here. Please notice that a multiboot + * section WILL be inserted, which is NOT mandatory (we could have + * removed it without getting into trouble). Please note however that + * the *.bin files will NOT be multiboot compatible (they are not in ELF + * format): they are expected to be directly booted by the BIOS (or + * by the "chainloader" command of Grub). */ +INCLUDE ../support/sos.lds + +/* We overload the entry set in sos.lds, just to avoid an ld warning */ +ENTRY(sos_main); diff --git a/extra/termslave.c b/extra/termslave.c new file mode 100644 index 0000000..b61c9f6 --- /dev/null +++ b/extra/termslave.c @@ -0,0 +1,130 @@ +/* Unix pty slave program -- David Decotigny 2005 + License: GNU GPL version 2 + Most of it taken from the GNU C library doc examples */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file termslave.c + * + * Linux pseudo-TTY slave program. To be used with "qemu -monitor pty" + * to access qemu's monitor from any linux terminal. To use it, launch + * a "qemu -monitor pty" in one terminal, take a look at the first + * line stating "char device redirected to /dev/pts/4" for example, + * and lauch the termslave program as "./termslave /dev/pts/4" in + * another terminal. + * + * To make this correctly work, one has to apply the + * patch-qemu-pty.diff patch to qemu < 0.8.0 (qemu 0.8.0 and beyond + * already includes this patch). + * + * This program also works with the "-serial pty" flag of qemu. It can + * be used to send commands to the SOS serial-line shell (article 9). + */ + + +/* Use this variable to remember original terminal attributes. */ +struct termios saved_attributes; + +static void +reset_input_mode (void) +{ + tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); +} + +static void +set_input_mode (void) +{ + struct termios tattr; + + /* Make sure stdin is a terminal. */ + if (!isatty (STDIN_FILENO)) + { + fprintf (stderr, "Not a terminal.\n"); + exit (EXIT_FAILURE); + } + + /* Save the terminal attributes so we can restore them later. */ + tcgetattr (STDIN_FILENO, &saved_attributes); + atexit (reset_input_mode); + + /* Set the funny terminal modes. */ + tcgetattr (STDIN_FILENO, &tattr); + tattr.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tattr.c_lflag &= ~(ICANON|ECHO); + tattr.c_cflag |= CLOCAL; + tattr.c_cc[VMIN] = 1; + tattr.c_cc[VTIME] = 0; + tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr); +} + +int main (int argc, char *argv[]) +{ + int term; + + if (argc < 2) + { + fprintf(stderr, "Usage: %s /dev/pts/number\n", argv[0]); + return -1; + } + + term = open(argv[1], O_RDWR); + if (term < 0) + { + perror("open"); + return -1; + } + if (! isatty(term)) + { + fprintf(stderr, "%s is not a valid terminal\n", argv[1]); + return -1; + } + + set_input_mode(); + + while (1) + { + fd_set cur_set; + FD_ZERO(& cur_set); + FD_SET(STDIN_FILENO, & cur_set); + FD_SET(term, & cur_set); + if (select(FD_SETSIZE, & cur_set, NULL, NULL, NULL) < 1) + continue; + + if (FD_ISSET(term, & cur_set)) + { + char buf[1024]; + int len = read(term, buf, sizeof(buf)); + + if (len >= 1) + write(STDOUT_FILENO, buf, len); + else + { + fprintf(stderr, "Master exitted\n"); + break; + } + } + + if (FD_ISSET(STDIN_FILENO, & cur_set)) + { + char c; + if (read(STDIN_FILENO, &c, 1) == 1) + { + if (c == 0x4) /* ctrl-D */ + break; + write(term, &c, 1); + } + else + break; + } + } + + return 0; +} diff --git a/hwcore/bitmap.c b/hwcore/bitmap.c new file mode 100644 index 0000000..f4fba28 --- /dev/null +++ b/hwcore/bitmap.c @@ -0,0 +1,177 @@ +/* Copyright (C) 2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +#include "bitmap.h" + + +/** + * ffs: find first bit set (1 = LSb, 32 = MSb). + * + * @return 0 when none found + * + * @note: License is GPLv2, origin is Linux Kernel 2.6.14.3 + * (@see include/asm-i386/bitops.h) + */ +static inline int word_ffs(sos_ui32_t x) +{ + if (!x) + return 0; + + __asm__("bsfl %1,%0" + :"=r" (x) + :"rm" (x)); + return x+1; +} + + +/** Set a 32bits mask of nbits '1' starting at LSb */ +static inline sos_ui32_t generate_lsb_mask(int nbits) +{ + return (1 << nbits) - 1; +} + + +sos_size_t sos_bitmap_ffs(const void * area, + sos_size_t bit_length, + sos_size_t _start_bit_index) +{ + sos_size_t start_bit_index = _start_bit_index - 1; + sos_size_t word_index = start_bit_index >> 5; + const sos_ui32_t * word = (const sos_ui32_t*)area; + sos_ui32_t mask = ~ generate_lsb_mask(start_bit_index & 31); + + while (start_bit_index < bit_length) + { + sos_size_t fsb = word_ffs(word[word_index] & mask); + + if (fsb > 0) + { + sos_size_t bit_index = SOS_ALIGN_INF(start_bit_index, 32) + fsb; + if (bit_index > bit_length) + return 0; + + return bit_index; + } + + start_bit_index = SOS_ALIGN_INF(start_bit_index, 32) + 32; + word_index ++; + mask = ~0; + } + + return 0; +} + + +sos_size_t sos_bitmap_ffc(const void * area, + sos_size_t bit_length, + sos_size_t _start_bit_index) +{ + sos_size_t start_bit_index = _start_bit_index - 1; + sos_size_t word_index = start_bit_index >> 5; + const sos_ui32_t * word = (const sos_ui32_t*)area; + sos_ui32_t mask = ~ generate_lsb_mask(start_bit_index & 31); + + while (start_bit_index < bit_length) + { + sos_size_t fcb = word_ffs((~ word[word_index]) & mask); + + if (fcb > 0) + { + sos_size_t bit_index = SOS_ALIGN_INF(start_bit_index, 32) + fcb; + if (bit_index > bit_length) + return 0; + + return bit_index; + } + + start_bit_index = SOS_ALIGN_INF(start_bit_index, 32) + 32; + word_index ++; + mask = ~0; + } + + return 0; +} + + +#undef ADDR +#define ADDR (*(volatile long *) addr) + +/** + * test_and_set_bit - Set a bit and return its old value + * @param nr Bit to set (starting at 0 !) + * @param addr Address to count from + * + * @note: License is GPLv2, origin is Linux Kernel 2.6.14.3 + * (@see include/asm-i386/bitops.h) + */ +static inline int test_and_set_bit(int nr, volatile unsigned long * addr) +{ + int oldbit; + + __asm__ __volatile__( + "btsl %2,%1\n\tsbbl %0,%0" + :"=r" (oldbit),"=m" (ADDR) + :"Ir" (nr) : "memory"); + return oldbit; +} + + +/** + * test_and_clear_bit - Clear a bit and return its old value + * @param nr Bit to clear (starting at 0 !) + * @param addr Address to count from + * + * @note: License is GPLv2, origin is Linux Kernel 2.6.14.3 + * (@see include/asm-i386/bitops.h) + */ +static inline int test_and_clear_bit(int nr, volatile unsigned long * addr) +{ + int oldbit; + + __asm__ __volatile__( + "btrl %2,%1\n\tsbbl %0,%0" + :"=r" (oldbit),"=m" (ADDR) + :"Ir" (nr) : "memory"); + return oldbit; +} + + +sos_bool_t sos_bitmap_test_and_set(void * area, + sos_size_t _bit_index) +{ + sos_ui32_t * word = (sos_ui32_t*)area; + sos_size_t bit_index = _bit_index - 1; + sos_size_t word_index = bit_index >> 5; + + bit_index &= 31; + return test_and_set_bit(bit_index, word + word_index); +} + + +sos_bool_t sos_bitmap_test_and_clear(void * area, + sos_size_t _bit_index) +{ + sos_ui32_t * word = (sos_ui32_t*)area; + sos_size_t bit_index = _bit_index - 1; + sos_size_t word_index = bit_index >> 5; + + bit_index &= 31; + return test_and_clear_bit(bit_index, word + word_index); +} diff --git a/hwcore/bitmap.h b/hwcore/bitmap.h new file mode 100644 index 0000000..27d204a --- /dev/null +++ b/hwcore/bitmap.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_BITMAP_H_ +#define _SOS_BITMAP_H_ + + +/** + * @file bitmap.h + * + * Bitmap manipulation. The implementation of this API is + * architecture-dependent, hence its location in hwcore/ + */ + +#include +#include + + +/** + * Return index (start at start_bit_index) of first bit set + * @return 0 < x <= bit_length when found, 0 when none found + * @note in-memory size of area MUST be a multiple of 4B (32bits) ! + * + * @note not atomic + */ +sos_size_t sos_bitmap_ffs(const void * area, + sos_size_t bit_length, + sos_size_t start_bit_index /* start at 1 */); + + +/** + * Return index (start at start_bit_index) of first bit clear + * @return 0 < x <= bit_length when found, 0 when none found + * @note in-memory size of area MUST be a multiple of 4B (32bits) ! + * + * @note not atomic + */ +sos_size_t sos_bitmap_ffc(const void * area, + sos_size_t bit_length, + sos_size_t start_bit_index /* start at 1 */); + + +/** + * Set bit at index (start at 1) + * @return old value of the bit + * + * @note atomic + */ +sos_bool_t +sos_bitmap_test_and_set(void * area, + sos_size_t bit_index /* start at 1 */); + + +/** + * Clear bit at index (start at 1) + * @return old value of the bit + * + * @note atomic + */ +sos_bool_t +sos_bitmap_test_and_clear(void * area, + sos_size_t bit_index /* start at 1 */); + + +#endif /* _SOS_BITMAP_H_ */ diff --git a/hwcore/cpu_context.c b/hwcore/cpu_context.c new file mode 100644 index 0000000..6813d55 --- /dev/null +++ b/hwcore/cpu_context.c @@ -0,0 +1,1008 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2000-2004, The KOS team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include "cpu_context.h" + + +/** + * Here is the definition of a CPU context for IA32 processors. This + * is a SOS convention, not a specification given by the IA32 + * spec. However there is a strong constraint related to the x86 + * interrupt handling specification: the top of the stack MUST be + * compatible with the 'iret' instruction, ie there must be the + * err_code (might be 0), eip, cs and eflags of the destination + * context in that order (see Intel x86 specs vol 3, figure 5-4). + * + * @note IMPORTANT: This definition MUST be consistent with the way + * the registers are stored on the stack in + * irq_wrappers.S/exception_wrappers.S !!! Hence the constraint above. + */ +struct sos_cpu_state { + /* (Lower addresses) */ + + /* These are SOS convention */ + sos_ui16_t gs; + sos_ui16_t fs; + sos_ui16_t es; + sos_ui16_t ds; + sos_ui16_t cpl0_ss; /* This is ALWAYS the Stack Segment of the + Kernel context (CPL0) of the interrupted + thread, even for a user thread */ + sos_ui16_t alignment_padding; /* unused */ + sos_ui32_t eax; + sos_ui32_t ebx; + sos_ui32_t ecx; + sos_ui32_t edx; + sos_ui32_t esi; + sos_ui32_t edi; + sos_ui32_t ebp; + + /* MUST NEVER CHANGE (dependent on the IA32 iret instruction) */ + sos_ui32_t error_code; + sos_vaddr_t eip; + sos_ui32_t cs; /* 32bits according to the specs ! However, the CS + register is really 16bits long */ + sos_ui32_t eflags; + + /* (Higher addresses) */ +} __attribute__((packed)); + + +/** + * The CS value pushed on the stack by the CPU upon interrupt, and + * needed by the iret instruction, is 32bits long while the real CPU + * CS register is 16bits only: this macro simply retrieves the CPU + * "CS" register value from the CS value pushed on the stack by the + * CPU upon interrupt. + * + * The remaining 16bits pushed by the CPU should be considered + * "reserved" and architecture dependent. IMHO, the specs don't say + * anything about them. Considering that some architectures generate + * non-zero values for these 16bits (at least Cyrix), we'd better + * ignore them. + */ +#define GET_CPU_CS_REGISTER_VALUE(pushed_ui32_cs_value) \ + ( (pushed_ui32_cs_value) & 0xffff ) + + +/** + * Structure of an interrupted Kernel thread's context + */ +struct sos_cpu_kstate +{ + struct sos_cpu_state regs; +} __attribute__((packed)); + + +/** + * Structure of an interrupted User thread's context. This is almost + * the same as a kernel context, except that 2 additional values are + * pushed on the stack before the eflags/cs/eip of the interrupted + * context: the stack configuration of the interrupted user context. + * + * @see Section 6.4.1 of Intel x86 vol 1 + */ +struct sos_cpu_ustate +{ + struct sos_cpu_state regs; + struct + { + sos_ui32_t cpl3_esp; + sos_ui16_t cpl3_ss; + }; +} __attribute__((packed)); + + +/* + * Structure of a Task State Segment on the x86 Architecture. + * + * @see Intel x86 spec vol 3, figure 6-2 + * + * @note Such a data structure should not cross any page boundary (see + * end of section 6.2.1 of Intel spec vol 3). This is the reason why + * we tell gcc to align it on a 128B boundary (its size is 104B, which + * is <= 128). + */ +struct x86_tss { + + /** + * Intel provides a way for a task to switch to another in an + * automatic way (call gates). In this case, the back_link field + * stores the source TSS of the context switch. This allows to + * easily implement coroutines, task backtracking, ... In SOS we + * don't use TSS for the context switch purpouse, so we always + * ignore this field. + * (+0) + */ + sos_ui16_t back_link; + + sos_ui16_t reserved1; + + /* CPL0 saved context. (+4) */ + sos_vaddr_t esp0; + sos_ui16_t ss0; + + sos_ui16_t reserved2; + + /* CPL1 saved context. (+12) */ + sos_vaddr_t esp1; + sos_ui16_t ss1; + + sos_ui16_t reserved3; + + /* CPL2 saved context. (+20) */ + sos_vaddr_t esp2; + sos_ui16_t ss2; + + sos_ui16_t reserved4; + + /* Interrupted context's saved registers. (+28) */ + sos_vaddr_t cr3; + sos_vaddr_t eip; + sos_ui32_t eflags; + sos_ui32_t eax; + sos_ui32_t ecx; + sos_ui32_t edx; + sos_ui32_t ebx; + sos_ui32_t esp; + sos_ui32_t ebp; + sos_ui32_t esi; + sos_ui32_t edi; + + /* +72 */ + sos_ui16_t es; + sos_ui16_t reserved5; + + /* +76 */ + sos_ui16_t cs; + sos_ui16_t reserved6; + + /* +80 */ + sos_ui16_t ss; + sos_ui16_t reserved7; + + /* +84 */ + sos_ui16_t ds; + sos_ui16_t reserved8; + + /* +88 */ + sos_ui16_t fs; + sos_ui16_t reserved9; + + /* +92 */ + sos_ui16_t gs; + sos_ui16_t reserved10; + + /* +96 */ + sos_ui16_t ldtr; + sos_ui16_t reserved11; + + /* +100 */ + sos_ui16_t debug_trap_flag :1; + sos_ui16_t reserved12 :15; + sos_ui16_t iomap_base_addr; + + /* 104 */ +} __attribute__((packed, aligned(128))); + + +static struct x86_tss kernel_tss; + + +sos_ret_t sos_cpu_context_subsystem_setup() +{ + /* Reset the kernel TSS */ + memset(&kernel_tss, 0x0, sizeof(kernel_tss)); + + /** + * Now setup the kernel TSS. + * + * Considering the privilege change method we choose (cpl3 -> cpl0 + * through a software interrupt), we don't need to initialize a + * full-fledged TSS. See section 6.4.1 of Intel x86 vol 1. Actually, + * only a correct value for the kernel esp and ss are required (aka + * "ss0" and "esp0" fields). Since the esp0 will have to be updated + * at privilege change time, we don't have to set it up now. + */ + kernel_tss.ss0 = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); + + /* Register this TSS into the gdt */ + sos_gdt_register_kernel_tss((sos_vaddr_t) &kernel_tss); + + return SOS_OK; +} + + +/** + * THE main operation of a kernel thread. This routine calls the + * kernel thread function start_func and calls exit_func when + * start_func returns. + */ +static void core_routine (sos_cpu_kstate_function_arg1_t *start_func, + sos_ui32_t start_arg, + sos_cpu_kstate_function_arg1_t *exit_func, + sos_ui32_t exit_arg) + __attribute__((noreturn)); + +static void core_routine (sos_cpu_kstate_function_arg1_t *start_func, + sos_ui32_t start_arg, + sos_cpu_kstate_function_arg1_t *exit_func, + sos_ui32_t exit_arg) +{ + start_func(start_arg); + exit_func(exit_arg); + + SOS_ASSERT_FATAL(! "The exit function of the thread should NOT return !"); + for(;;); +} + + +sos_ret_t sos_cpu_kstate_init(struct sos_cpu_state **ctxt, + sos_cpu_kstate_function_arg1_t *start_func, + sos_ui32_t start_arg, + sos_vaddr_t stack_bottom, + sos_size_t stack_size, + sos_cpu_kstate_function_arg1_t *exit_func, + sos_ui32_t exit_arg) +{ + /* We are initializing a Kernel thread's context */ + struct sos_cpu_kstate *kctxt; + + /* This is a critical internal function, so that it is assumed that + the caller knows what he does: we legitimally assume that values + for ctxt, start_func, stack_* and exit_func are allways VALID ! */ + + /* Setup the stack. + * + * On x86, the stack goes downward. Each frame is configured this + * way (higher addresses first): + * + * - (optional unused space. As of gcc 3.3, this space is 24 bytes) + * - arg n + * - arg n-1 + * - ... + * - arg 1 + * - return instruction address: The address the function returns to + * once finished + * - local variables + * + * The remaining of the code should be read from the end upward to + * understand how the processor will handle it. + */ + + sos_vaddr_t tmp_vaddr = stack_bottom + stack_size; + sos_ui32_t *stack = (sos_ui32_t*)tmp_vaddr; + + /* If needed, poison the stack */ +#ifdef SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS + memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, stack_size); +#elif defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) + sos_cpu_state_prepare_detect_kernel_stack_overflow(stack_bottom, stack_size); +#endif + + /* Simulate a call to the core_routine() function: prepare its + arguments */ + *(--stack) = exit_arg; + *(--stack) = (sos_ui32_t)exit_func; + *(--stack) = start_arg; + *(--stack) = (sos_ui32_t)start_func; + *(--stack) = 0; /* Return address of core_routine => force page fault */ + + /* + * Setup the initial context structure, so that the CPU will execute + * the function core_routine() once this new context has been + * restored on CPU + */ + + /* Compute the base address of the structure, which must be located + below the previous elements */ + tmp_vaddr = ((sos_vaddr_t)stack) - sizeof(struct sos_cpu_kstate); + kctxt = (struct sos_cpu_kstate*)tmp_vaddr; + + /* Initialize the CPU context structure */ + memset(kctxt, 0x0, sizeof(struct sos_cpu_kstate)); + + /* Tell the CPU context structure that the first instruction to + execute will be that of the core_routine() function */ + kctxt->regs.eip = (sos_ui32_t)core_routine; + + /* Setup the segment registers */ + kctxt->regs.cs + = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE); /* Code */ + kctxt->regs.ds + = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */ + kctxt->regs.es + = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Data */ + kctxt->regs.cpl0_ss + = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Stack */ + /* fs and gs unused for the moment. */ + + /* The newly created context is initially interruptible */ + kctxt->regs.eflags = (1 << 9); /* set IF bit */ + + /* Finally, update the generic kernel/user thread context */ + *ctxt = (struct sos_cpu_state*) kctxt; + + return SOS_OK; +} + + +/** + * Helper function to create a new user thread context. When + * model_uctxt is NON NULL, the new user context is the copy of + * model_uctxt, otherwise the SP/PC registers are initialized to the + * user_initial_SP/PC arguments + */ +static sos_ret_t cpu_ustate_init(struct sos_cpu_state **ctxt, + const struct sos_cpu_state *model_uctxt, + sos_uaddr_t user_start_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size) +{ + /* We are initializing a User thread's context */ + struct sos_cpu_ustate *uctxt; + + /* This is a critical internal function, so that it is assumed that + the caller knows what he does: we legitimally assume that values + for ctxt, etc. are allways VALID ! */ + + /* Compute the address of the CPU state to restore on CPU when + switching to this new user thread */ + sos_vaddr_t uctxt_vaddr = kernel_stack_bottom + + kernel_stack_size + - sizeof(struct sos_cpu_ustate); + uctxt = (struct sos_cpu_ustate*)uctxt_vaddr; + + if (model_uctxt && !sos_cpu_context_is_in_user_mode(model_uctxt)) + return -SOS_EINVAL; + + /* If needed, poison the kernel stack */ +#ifdef SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS + memset((void*)kernel_stack_bottom, + SOS_CPU_STATE_STACK_POISON, + kernel_stack_size); +#elif defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) + sos_cpu_state_prepare_detect_kernel_stack_overflow(kernel_stack_bottom, + kernel_stack_size); +#endif + + /* + * Setup the initial context structure, so that the CPU will restore + * the initial registers' value for the user thread. The + * user thread argument is passed in the EAX register. + */ + + /* Initialize the CPU context structure */ + if (! model_uctxt) + { + memset(uctxt, 0x0, sizeof(struct sos_cpu_ustate)); + + /* Tell the CPU context structure that the first instruction to + execute will be located at user_start_PC (in user space) */ + uctxt->regs.eip = (sos_ui32_t)user_start_PC; + + /* Tell the CPU where will be the user stack */ + uctxt->cpl3_esp = user_initial_SP; + } + else + memcpy(uctxt, model_uctxt, sizeof(struct sos_cpu_ustate)); + + /* The parameter to the start function is not passed by the stack to + avoid a possible page fault */ + uctxt->regs.eax = user_start_arg1; + + /* Optional additional argument for non-duplicated threads */ + if (! model_uctxt) + uctxt->regs.ebx = user_start_arg2; + + /* Setup the segment registers */ + uctxt->regs.cs + = SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UCODE); /* Code */ + uctxt->regs.ds + = SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UDATA); /* Data */ + uctxt->regs.es + = SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UDATA); /* Data */ + uctxt->cpl3_ss + = SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UDATA); /* User Stack */ + + /* We need also to update the segment for the kernel stack + segment. It will be used when this context will be restored on + CPU: initially it will be executing in kernel mode and will + switch immediatly to user mode */ + uctxt->regs.cpl0_ss + = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA); /* Kernel Stack */ + + /* fs and gs unused for the moment. */ + + /* The newly created context is initially interruptible */ + uctxt->regs.eflags = (1 << 9); /* set IF bit */ + + /* Finally, update the generic kernel/user thread context */ + *ctxt = (struct sos_cpu_state*) uctxt; + + return SOS_OK; +} + + +sos_ret_t sos_cpu_ustate_init(struct sos_cpu_state **ctxt, + sos_uaddr_t user_start_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size) +{ + return cpu_ustate_init(ctxt, NULL, + user_start_PC, + user_start_arg1, user_start_arg2, + user_initial_SP, + kernel_stack_bottom, kernel_stack_size); +} + + +sos_ret_t sos_cpu_ustate_duplicate(struct sos_cpu_state **ctxt, + const struct sos_cpu_state *model_uctxt, + sos_ui32_t user_retval, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size) +{ + return cpu_ustate_init(ctxt, model_uctxt, + /* ignored */0, + user_retval, /* ignored */0, + /* ignored */0, + kernel_stack_bottom, kernel_stack_size); +} + + +sos_ret_t +sos_cpu_context_is_in_user_mode(const struct sos_cpu_state *ctxt) +{ + /* An interrupted user thread has its CS register set to that of the + User code segment */ + switch (GET_CPU_CS_REGISTER_VALUE(ctxt->cs)) + { + case SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UCODE): + return TRUE; + break; + + case SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE): + return FALSE; + break; + + default: + SOS_FATAL_ERROR("Invalid saved context Code segment register: 0x%x (k=%x, u=%x) !", + (unsigned) GET_CPU_CS_REGISTER_VALUE(ctxt->cs), + SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE), + SOS_BUILD_SEGMENT_REG_VALUE(3, FALSE, SOS_SEG_UCODE)); + break; + } + + /* Should never get here */ + return -SOS_EFATAL; +} + + +#if defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) +void +sos_cpu_state_prepare_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt, + sos_vaddr_t stack_bottom, + sos_size_t stack_size) +{ + sos_size_t poison_size = SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW; + if (poison_size > stack_size) + poison_size = stack_size; + + memset((void*)stack_bottom, SOS_CPU_STATE_STACK_POISON, poison_size); +} + + +void +sos_cpu_state_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt, + sos_vaddr_t stack_bottom, + sos_size_t stack_size) +{ + unsigned char *c; + unsigned int i; + + /* On SOS, "ctxt" corresponds to the address of the esp register of + the saved context in Kernel mode (always, even for the interrupted + context of a user thread). Here we make sure that this stack + pointer is within the allowed stack area */ + SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) >= stack_bottom); + SOS_ASSERT_FATAL(((sos_vaddr_t)ctxt) + sizeof(struct sos_cpu_kstate) + <= stack_bottom + stack_size); + + /* Check that the bottom of the stack has not been altered */ + for (c = (unsigned char*) stack_bottom, i = 0 ; + (i < SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) && (i < stack_size) ; + c++, i++) + { + SOS_ASSERT_FATAL(SOS_CPU_STATE_STACK_POISON == *c); + } +} +#endif + + +/* ======================================================================= + * Public Accessor functions + */ + + +sos_vaddr_t sos_cpu_context_get_PC(const struct sos_cpu_state *ctxt) +{ + SOS_ASSERT_FATAL(NULL != ctxt); + + /* This is the PC of the interrupted context (ie kernel or user + context). */ + return ctxt->eip; +} + + +sos_vaddr_t sos_cpu_context_get_SP(const struct sos_cpu_state *ctxt) +{ + SOS_ASSERT_FATAL(NULL != ctxt); + + /* 'ctxt' corresponds to the SP of the interrupted context, in Kernel + mode. We have to test whether the original interrupted context + was that of a kernel or user thread */ + if (TRUE == sos_cpu_context_is_in_user_mode(ctxt)) + { + struct sos_cpu_ustate const* uctxt = (struct sos_cpu_ustate const*)ctxt; + return uctxt->cpl3_esp; + } + + /* On SOS, "ctxt" corresponds to the address of the esp register of + the saved context in Kernel mode (always, even for the interrupted + context of a user thread). */ + return (sos_vaddr_t)ctxt; +} + + +sos_ret_t +sos_cpu_context_set_EX_return_address(struct sos_cpu_state *ctxt, + sos_vaddr_t ret_vaddr) +{ + ctxt->eip = ret_vaddr; + return SOS_OK; +} + + +void sos_cpu_context_dump(const struct sos_cpu_state *ctxt) +{ + char buf[128]; + + snprintf(buf, sizeof(buf), + "CPU: eip=%x esp0=%x eflags=%x cs=%x ds=%x ss0=%x err=%x", + (unsigned)ctxt->eip, (unsigned)ctxt, (unsigned)ctxt->eflags, + (unsigned)GET_CPU_CS_REGISTER_VALUE(ctxt->cs), (unsigned)ctxt->ds, + (unsigned)ctxt->cpl0_ss, + (unsigned)ctxt->error_code); + if (TRUE == sos_cpu_context_is_in_user_mode(ctxt)) + { + struct sos_cpu_ustate const* uctxt = (struct sos_cpu_ustate const*)ctxt; + snprintf(buf, sizeof(buf), + "%s esp3=%x ss3=%x", + buf, (unsigned)uctxt->cpl3_esp, (unsigned)uctxt->cpl3_ss); + } + else + snprintf(buf, sizeof(buf), "%s [KERNEL MODE]", buf); + + sos_bochs_putstring(buf); sos_bochs_putstring("\n"); + sos_x86_videomem_putstring(23, 0, + SOS_X86_VIDEO_FG_BLACK | SOS_X86_VIDEO_BG_LTGRAY, + buf); +} + + +/* ======================================================================= + * Public Accessor functions TO BE USED ONLY BY Exception handlers + */ + + +sos_ui32_t sos_cpu_context_get_EX_info(const struct sos_cpu_state *ctxt) +{ + SOS_ASSERT_FATAL(NULL != ctxt); + return ctxt->error_code; +} + + +sos_vaddr_t +sos_cpu_context_get_EX_faulting_vaddr(const struct sos_cpu_state *ctxt) +{ + sos_ui32_t cr2; + + /* + * See Intel Vol 3 (section 5.14): the address of the faulting + * virtual address of a page fault is stored in the cr2 + * register. + * + * Actually, we do not store the cr2 register in a saved + * kernel thread's context. So we retrieve the cr2's value directly + * from the processor. The value we retrieve in an exception handler + * is actually the correct one because an exception is synchronous + * with the code causing the fault, and cannot be interrupted since + * the IDT entries in SOS are "interrupt gates" (ie IRQ are + * disabled). + */ + asm volatile ("movl %%cr2, %0" + :"=r"(cr2) + : ); + + return cr2; +} + + +/* ======================================================================= + * Public Accessor functions TO BE USED ONLY BY the SYSCALL handler + */ + + +/* + * By convention, the USER SOS programs always pass 4 arguments to the + * kernel syscall handler: in eax/../edx. For less arguments, the + * unused registers are filled with 0s. For more arguments, the 4th + * syscall parameter gives the address of the array containing the + * remaining arguments. In any case, eax corresponds to the syscall + * IDentifier. + */ + + +inline +sos_ret_t sos_syscall_get3args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3) +{ + *arg1 = user_ctxt->ebx; + *arg2 = user_ctxt->ecx; + *arg3 = user_ctxt->edx; + return SOS_OK; +} + + +sos_ret_t sos_syscall_get1arg(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1) +{ + unsigned int unused; + return sos_syscall_get3args(user_ctxt, arg1, & unused, & unused); +} + + +sos_ret_t sos_syscall_get2args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2) +{ + unsigned int unused; + return sos_syscall_get3args(user_ctxt, arg1, arg2, & unused); +} + + +/* + * sos_syscall_get3args() is defined in cpu_context.c because it needs + * to know the structure of a struct spu_state + */ + +sos_ret_t sos_syscall_get4args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4) +{ + sos_uaddr_t uaddr_other_args; + unsigned int other_args[2]; + sos_ret_t retval; + + /* Retrieve the 3 arguments. The last one is an array containing the + remaining arguments */ + retval = sos_syscall_get3args(user_ctxt, arg1, arg2, + (unsigned int *)& uaddr_other_args); + if (SOS_OK != retval) + return retval; + + /* Copy the array containing the remaining arguments from user + space */ + retval = sos_memcpy_from_user((sos_vaddr_t)other_args, + (sos_uaddr_t)uaddr_other_args, + sizeof(other_args)); + if (sizeof(other_args) != retval) + return -SOS_EFAULT; + + *arg3 = other_args[0]; + *arg4 = other_args[1]; + return SOS_OK; +} + + +sos_ret_t sos_syscall_get5args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5) +{ + sos_uaddr_t uaddr_other_args; + unsigned int other_args[3]; + sos_ret_t retval; + + /* Retrieve the 3 arguments. The last one is an array containing the + remaining arguments */ + retval = sos_syscall_get3args(user_ctxt, arg1, arg2, + (unsigned int *)& uaddr_other_args); + if (SOS_OK != retval) + return retval; + + /* Copy the array containing the remaining arguments from user + space */ + retval = sos_memcpy_from_user((sos_vaddr_t)other_args, + (sos_uaddr_t)uaddr_other_args, + sizeof(other_args)); + if (sizeof(other_args) != retval) + return -SOS_EFAULT; + + *arg3 = other_args[0]; + *arg4 = other_args[1]; + *arg5 = other_args[2]; + return SOS_OK; +} + + +sos_ret_t sos_syscall_get6args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6) +{ + sos_uaddr_t uaddr_other_args; + unsigned int other_args[4]; + sos_ret_t retval; + + /* Retrieve the 3 arguments. The last one is an array containing the + remaining arguments */ + retval = sos_syscall_get3args(user_ctxt, arg1, arg2, + (unsigned int *)& uaddr_other_args); + if (SOS_OK != retval) + return retval; + + /* Copy the array containing the remaining arguments from user + space */ + retval = sos_memcpy_from_user((sos_vaddr_t)other_args, + (sos_uaddr_t)uaddr_other_args, + sizeof(other_args)); + if (sizeof(other_args) != retval) + return -SOS_EFAULT; + + *arg3 = other_args[0]; + *arg4 = other_args[1]; + *arg5 = other_args[2]; + *arg6 = other_args[3]; + return SOS_OK; +} + + +sos_ret_t sos_syscall_get7args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6, + /* out */unsigned int *arg7) +{ + sos_uaddr_t uaddr_other_args; + unsigned int other_args[5]; + sos_ret_t retval; + + /* Retrieve the 3 arguments. The last one is an array containing the + remaining arguments */ + retval = sos_syscall_get3args(user_ctxt, arg1, arg2, + (unsigned int *)& uaddr_other_args); + if (SOS_OK != retval) + return retval; + + /* Copy the array containing the remaining arguments from user + space */ + retval = sos_memcpy_from_user((sos_vaddr_t)other_args, + (sos_uaddr_t)uaddr_other_args, + sizeof(other_args)); + if (sizeof(other_args) != retval) + return -SOS_EFAULT; + + *arg3 = other_args[0]; + *arg4 = other_args[1]; + *arg5 = other_args[2]; + *arg6 = other_args[3]; + *arg7 = other_args[4]; + return SOS_OK; +} + + +sos_ret_t sos_syscall_get8args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6, + /* out */unsigned int *arg7, + /* out */unsigned int *arg8) +{ + sos_uaddr_t uaddr_other_args; + unsigned int other_args[6]; + sos_ret_t retval; + + /* Retrieve the 3 arguments. The last one is an array containing the + remaining arguments */ + retval = sos_syscall_get3args(user_ctxt, arg1, arg2, + (unsigned int *)& uaddr_other_args); + if (SOS_OK != retval) + return retval; + + /* Copy the array containing the remaining arguments from user + space */ + retval = sos_memcpy_from_user((sos_vaddr_t)other_args, + (sos_uaddr_t)uaddr_other_args, + sizeof(other_args)); + if (sizeof(other_args) != retval) + return -SOS_EFAULT; + + *arg3 = other_args[0]; + *arg4 = other_args[1]; + *arg5 = other_args[2]; + *arg6 = other_args[3]; + *arg7 = other_args[4]; + *arg8 = other_args[5]; + return SOS_OK; +} + + +/* ======================================================================= + * Backtrace facility. To be used for DEBUGging purpose ONLY. + */ + + +sos_ui32_t sos_backtrace(const struct sos_cpu_state *cpu_state, + sos_ui32_t max_depth, + sos_vaddr_t stack_bottom, + sos_size_t stack_size, + sos_backtrace_callback_t * backtracer, + void *custom_arg) +{ + unsigned int depth; + sos_vaddr_t callee_PC, caller_frame; + + /* Cannot backtrace an interrupted user thread ! */ + if ((NULL != cpu_state) + && + (TRUE == sos_cpu_context_is_in_user_mode(cpu_state))) + { + return 0; + } + + /* + * Layout of a frame on the x86 (compiler=gcc): + * + * funcA calls funcB calls funcC + * + * .... + * funcB Argument 2 + * funcB Argument 1 + * funcA Return eip + * frameB: funcA ebp (ie previous stack frame) + * .... + * (funcB local variables) + * .... + * funcC Argument 2 + * funcC Argument 1 + * funcB Return eip + * frameC: funcB ebp (ie previous stack frame == A0) <---- a frame address + * .... + * (funcC local variables) + * .... + * + * The presence of "ebp" on the stack depends on 2 things: + * + the compiler is gcc + * + the source is compiled WITHOUT the -fomit-frame-pointer option + * In the absence of "ebp", chances are high that the value pushed + * at that address is outside the stack boundaries, meaning that the + * function will return -SOS_ENOSUP. + */ + + if (cpu_state) + { + callee_PC = cpu_state->eip; + caller_frame = cpu_state->ebp; + } + else + { + /* Skip the sos_backtrace() frame */ + callee_PC = (sos_vaddr_t)__builtin_return_address(0); + caller_frame = (sos_vaddr_t)__builtin_frame_address(1); + } + + for(depth=0 ; depth < max_depth ; depth ++) + { + /* Call the callback */ + backtracer(callee_PC, caller_frame + 8, depth, custom_arg); + + /* If the frame address is funky, don't go further */ + if ( (caller_frame < stack_bottom) + || (caller_frame + 4 >= stack_bottom + stack_size) ) + return depth; + + /* Go to caller frame */ + callee_PC = *((sos_vaddr_t*) (caller_frame + 4)); + caller_frame = *((sos_vaddr_t*) caller_frame); + } + + return depth; +} + + +/* ************************************************************* + * Function to manage the TSS. This function is not really "public": + * it is reserved to the assembler routines defined in + * cpu_context_switch.S + * + * Update the kernel stack address so that the IRQ, syscalls and + * exception return in a correct stack location when coming back into + * kernel mode. + */ +void +sos_cpu_context_update_kernel_tss(struct sos_cpu_state *next_ctxt) +{ + /* next_ctxt corresponds to an interrupted user thread ? */ + if (sos_cpu_context_is_in_user_mode(next_ctxt)) + { + /* + * Yes: "next_ctxt" is an interrupted user thread => we are + * going to switch to user mode ! Setup the stack address so + * that the user thread "next_ctxt" can come back to the correct + * stack location when returning in kernel mode. + * + * This stack location corresponds to the SP of the next user + * thread once its context has been transferred on the CPU, ie + * once the CPU has executed all the pop/iret instruction of the + * context switch with privilege change. + */ + kernel_tss.esp0 = ((sos_vaddr_t)next_ctxt) + + sizeof(struct sos_cpu_ustate); + /* Note: no need to protect this agains IRQ because IRQs are not + allowed to update it by themselves, and they are not allowed + to block */ + } + else + { + /* No: No need to update kernel TSS when we stay in kernel + mode */ + } +} diff --git a/hwcore/cpu_context.h b/hwcore/cpu_context.h new file mode 100644 index 0000000..a2a5daf --- /dev/null +++ b/hwcore/cpu_context.h @@ -0,0 +1,433 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2000-2004, The KOS team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_CPUCTXT_H_ +#define _SOS_CPUCTXT_H_ + + +/** + * @file cpu_context.h + * + * Low level API to manage kernel and user thread CPU contexts. Should + * be some kind of architecture-independent. + */ + +#include +#include + + +/** + * Prepare the system to deal with multiple CPU execution contexts + */ +sos_ret_t sos_cpu_context_subsystem_setup(void); + + +/** + * Opaque structure storing the CPU context of an inactive kernel or + * user thread, as saved by the low level primitives below or by the + * interrupt/exception handlers. + * + * @note This is an (architecture-independent) forward declaration: + * see cpu_context.c and the *.S files for its + * (architecture-dependent) definition. + */ +struct sos_cpu_state; + + +/** + * The type of the functions passed as arguments to the Kernel thread + * related functions. + */ +typedef void (sos_cpu_kstate_function_arg1_t(sos_ui32_t arg1)); + + +/** + * Function to create an initial context for a kernel thread starting + * its execution at function start_func with the argument initial_arg, + * and having the stack defined by stack_bottom/stack_size. When the + * start_func function returns, the function exit_func is called with + * argument exit_arg. + * + * @param kctxt The kernel thread CPU context to initialize. The + * address of the newly-initialized struct sos_cpu_state will be + * stored in this variable. The contents of this struct sos_cpu_state + * are actually located /inside/ the stack. + * + * @param start_func The address of the first instruction that will be + * executed when this context will be first transferred on + * CPU. Practically speaking, this is the address of a function that + * is assumed to take 1 argument. + * + * @param start_arg The value that will be passed as the argument to + * start_func when the thread starts. The stack will be setup + * accordingly to simulate a real call to the function and really + * passing this arguement. + * + * @param stack_bottom The lowest address of the stack. + * + * @param stack_size The size of the stack. + * + * @param exit_func The address of the instruction executed after the + * function start_func has returned. This function takes 1 parameter + * as argument: exit_arg. + * + * @param exit_arg The argument passed to the function exit_func. + * + * @note the newly created context is INTERRUPTIBLE by default ! + */ +sos_ret_t sos_cpu_kstate_init(struct sos_cpu_state **kctxt, + sos_cpu_kstate_function_arg1_t *start_func, + sos_ui32_t start_arg, + sos_vaddr_t stack_bottom, + sos_size_t stack_size, + sos_cpu_kstate_function_arg1_t *exit_func, + sos_ui32_t exit_arg); + + +/** + * Function to create an initial context for a user thread starting + * its execution at function user_start_PC with the user_start_arg + * argument. The address of the user stack before any modification by + * the ustate_init() function is given by user_start_SP. The user + * thread starts in user space first and needs a kernel stack for + * the syscalls and for handling interrupts: the address of this + * kernel stack is given by the kernel_stack_* parameters. + * + * @param uctxt The user thread CPU context to initialize. The + * address of the newly-initialized struct sos_cpu_state will be + * stored in this variable. The contents of this struct sos_cpu_state + * are actually located /inside/ the kernel stack of the thread. + * + * @param user_start_PC The address of the first instruction that will + * be executed in user mode when this context will be first + * transferred on CPU. Practically speaking, this is the address of a + * function that is assumed to take 1 argument. + * + * @param user_start_SP The initial user stack address. + * + * @param user_start_argX The 2 parameters passed to the initial user + * thread function (in registers). + * + * @param kernel_stack_bottom The lowest address of the kernel stack + * used to switch to user mode and to handle interrupts/exceptions. + * + * @param kernel_stack_size The size of the kernel stack (@see + * kernel_stack_bottom). + * + * @note the newly thread context is INTERRUPTIBLE ! + */ +sos_ret_t sos_cpu_ustate_init(struct sos_cpu_state **uctxt, + sos_uaddr_t user_start_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size); + + +/** + * Function to create an initial context for a user thread, copy of an + * existing user thread context. The user thread needs a kernel stack + * for the syscalls and for handling interrupts: the address of this + * kernel stack is given by the kernel_stack_* parameters. + * + * @param uctxt The user thread CPU context to initialize. The + * address of the newly-initialized struct sos_cpu_state will be + * stored in this variable. The contents of this struct sos_cpu_state + * are actually located /inside/ the kernel stack of the thread. + * + * @param model_uctxt The user thread context that will be copied to + * the new user thread context + * + * @param user_retval The parameter passed to the initial user + * thread function. + * + * @param kernel_stack_bottom The lowest address of the kernel stack + * used to switch to user mode and to handle interrupts/exceptions. + * + * @param kernel_stack_size The size of the kernel stack (@see + * kernel_stack_bottom). + * + * @note the newly thread context is INTERRUPTIBLE ! + */ +sos_ret_t sos_cpu_ustate_duplicate(struct sos_cpu_state **uctxt, + const struct sos_cpu_state *model_uctxt, + sos_ui32_t user_retval, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size); + + +/** + * Function that performs an immediate context-switch from one + * kernel/user thread to another one. It stores the current executing + * context in from_ctxt, and restores to_context on CPU. + * + * @param from_ctxt The address of the struct sos_cpu_state will be + * stored in this variable. Must NOT be NULL. + * + * @param to_ctxt The CPU will resume its execution with the struct + * sos_cpu_state located at this address. Must NOT be NULL. + */ +void sos_cpu_context_switch(struct sos_cpu_state **from_ctxt, + struct sos_cpu_state *to_ctxt); + + +/* + * Switch to the new given context (of a kernel/user thread) without + * saving the old context (of another kernel/user thread), and call + * the function reclaiming_func passing it the recalining_arg + * argument. The reclaining function is called from within the stack + * of the new context, so that it can (among other things) safely + * destroy the stack of the former context. + * + * @param switch_to_ctxt The context that will be restored on the CPU + * + * @param reclaiming_func The address of the function that will be + * called after having changed the stack, but before restoring the CPU + * context to switch_to_ctxt. + */ +void +sos_cpu_context_exit_to(struct sos_cpu_state *switch_to_ctxt, + sos_cpu_kstate_function_arg1_t *reclaiming_func, + sos_ui32_t reclaiming_arg) __attribute__((noreturn)); + +/* ======================================================================= + * Public Accessor functions + */ + + +/** + * Return whether the saved context was in kernel or user context + * + * @return TRUE when context was interrupted when in user mode, FALSE + * when in kernel mode, < 0 on error. + */ +sos_ret_t +sos_cpu_context_is_in_user_mode(const struct sos_cpu_state *ctxt); + + +/** + * Return Program Counter stored in the saved kernel/user context + */ +sos_vaddr_t sos_cpu_context_get_PC(const struct sos_cpu_state *ctxt); + + +/** + * Return Stack Pointer stored in the saved kernel/user context + */ +sos_vaddr_t sos_cpu_context_get_SP(const struct sos_cpu_state *ctxt); + + +/** + * Dump the contents of the CPU context (bochs + x86_videomem) + */ +void sos_cpu_context_dump(const struct sos_cpu_state *ctxt); + + +/* ======================================================================= + * Public Accessor functions TO BE USED ONLY BY Exception handlers + */ + + +/** + * Return the argument passed by the CPU upon exception, as stored in the + * saved context + */ +sos_ui32_t sos_cpu_context_get_EX_info(const struct sos_cpu_state *ctxt); + + +/** + * Return the faulting address of the exception + */ +sos_vaddr_t +sos_cpu_context_get_EX_faulting_vaddr(const struct sos_cpu_state *ctxt); + + +/** + * Change the return address of the given context + */ +sos_ret_t +sos_cpu_context_set_EX_return_address(struct sos_cpu_state *ctxt, + sos_vaddr_t ret_vaddr); + + +/* ======================================================================= + * Public Accessor functions TO BE USED ONLY BY the SYSCALL handler + */ + +/** + * Low-level functions used by the syscall handler. They are + * responsible for retrieving the arguments passed to the syscall when + * a user thread makes a syscall. Some of these arguments are + * available as registers' values in the user context, some of them + * are user-space addresses given by these registers. + * + * @return SOS_OK on success, <0 otherwise + */ +sos_ret_t sos_syscall_get1arg(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1); + +sos_ret_t sos_syscall_get2args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2); + +sos_ret_t sos_syscall_get3args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3); + +sos_ret_t sos_syscall_get4args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4); + +sos_ret_t sos_syscall_get5args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5); + +sos_ret_t sos_syscall_get6args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6); + +sos_ret_t sos_syscall_get7args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6, + /* out */unsigned int *arg7); + +sos_ret_t sos_syscall_get8args(const struct sos_cpu_state *user_ctxt, + /* out */unsigned int *arg1, + /* out */unsigned int *arg2, + /* out */unsigned int *arg3, + /* out */unsigned int *arg4, + /* out */unsigned int *arg5, + /* out */unsigned int *arg6, + /* out */unsigned int *arg7, + /* out */unsigned int *arg8); + + +/* ======================================================================= + * Macros controlling stack poisoning. + * Stack poisoning can be used to detect: + * - unitialized local variables + * - when the thread might have gone too deep in the stack + */ +/** The signature of the poison */ +#define SOS_CPU_STATE_STACK_POISON 0xa5 + +/** + * When set, mean that the whole stack is poisoned to detect use of + * unititialized variables + */ +#define SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS +/* #undef SOS_CPU_STATE_DETECT_UNINIT_KERNEL_VARS */ + +/** + * When set, mean that the bottom of the stack is poisoned to detect + * probable stack overflow. Its value indicates the number of bytes + * used for this detection. + */ +#define SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW 64 +/* #undef SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW */ + +#if defined(SOS_CPU_STATE_DETECT_KERNEL_STACK_OVERFLOW) +void +sos_cpu_state_prepare_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size); +void sos_cpu_state_detect_kernel_stack_overflow(const struct sos_cpu_state *ctxt, + sos_vaddr_t kernel_stack_bottom, + sos_size_t kernel_stack_size); +#else +# define sos_cpu_state_prepare_detect_kernel_stack_overflow(ctxt,stkbottom,stksize) \ + ({ /* nop */ }) +# define sos_cpu_state_detect_kernel_stack_overflow(ctxt,stkbottom,stksize) \ + ({ /* nop */ }) +#endif + + +/* ======================================================================= + * Backtrace facility. To be used for DEBUGging purpose ONLY. + */ + + +/** + * The function called at each step of the backtrace iterations + * + * @param PC The address of the next instruction of the function that + * will be executed + * + * @param params The address of the array of the parameteres that have + * been passed to the function considered + * + * @param depth The index of the iteration (ie the depth of the + * current frame into the stack) + * + * @param custom_arg Whatever you want: this is the argument passed as + * custom_arg to sos_backtrace() + */ +typedef void (sos_backtrace_callback_t)(sos_vaddr_t PC, + sos_vaddr_t params, + sos_ui32_t depth, + void *custom_arg); + + +/** + * Call the backtracer callback on each frame stored in the cpu_state + * + * @param cpu_state The CPU context we want to explore. MUST be the + * context of a thread in Kernel mode, or NULL. When NULL: backtrace + * the current CPU context. + * + * @param max_depth The maximum number of frames to explore + * + * @param stack_bottom The lower boundary of the stack. This is used + * to make sure that the frame addresses fit inside the stack + * boudaries (ie are potentially correct). + * + * @param stack_size The size of the stack. Same comment. + * + * @param backtracer The function to call to handle the frame for each + * iteration + * + * @param custom_arg The arg passed as custom_arg to the backtracer + * + * @return The number of frames explored. + * + * @note Might be inaccurate when gcc's -fomit-frame-pointer has been + * used. + */ +sos_ui32_t sos_backtrace(const struct sos_cpu_state *cpu_state, + sos_ui32_t max_depth, + sos_vaddr_t stack_bottom, + sos_size_t stack_size, + sos_backtrace_callback_t * backtracer, + void *custom_arg); + +#endif /* _SOS_CPUCTXT_H_ */ diff --git a/hwcore/cpu_context_switch.S b/hwcore/cpu_context_switch.S new file mode 100644 index 0000000..f089426 --- /dev/null +++ b/hwcore/cpu_context_switch.S @@ -0,0 +1,159 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2000-2004, The KOS team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#define ASM_SOURCE 1 + + +#include "segment.h" + + +.file "cpu_context_switch.S" + +.text + + +/** + * C Function called by the routines below in order to tell the CPU + * where will be the kernel stack (needed by the interrupt handlers) + * when next_ctxt will come back into kernel mode. + * + * void sos_cpu_context_update_kernel_tss(struct sos_cpu_state *next_ctxt) + * + * @see end of cpu_context.c + */ +.extern sos_cpu_context_update_kernel_tss + + +.globl sos_cpu_context_switch +.type sos_cpu_context_switch, @function +sos_cpu_context_switch: + // arg2= to_context -- esp+64 + // arg1= from_context -- esp+60 + // caller ip -- esp+56 + pushf // (eflags) esp+52 + pushl %cs // (cs) esp+48 + pushl $resume_pc // (ip) esp+44 + pushl $0 // (error code) esp+40 + pushl %ebp // esp+36 + pushl %edi // esp+32 + pushl %esi // esp+28 + pushl %edx // esp+24 + pushl %ecx // esp+20 + pushl %ebx // esp+16 + pushl %eax // esp+12 + subl $2, %esp // (alignment) esp+10 + pushw %ss // esp+8 + pushw %ds // esp+6 + pushw %es // esp+4 + pushw %fs // esp+2 + pushw %gs // esp + + /* + * Now that the original eax/ebx are stored, we can use them safely + */ + + /* Store the address of the saved context */ + movl 60(%esp), %ebx + movl %esp, (%ebx) + + /* This is the proper context switch ! We change the stack here */ + movl 64(%esp), %esp + + /* Prepare kernel TSS in case we are switching to a user thread: we + make sure that we will come back into the kernel at a correct + stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + + /* Restore the CPU context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + addl $4, %esp /* Ignore "error code" */ + + /* This restores the eflags, the cs and the eip registers */ + iret /* equivalent to: popfl ; ret */ + +resume_pc: + // Same context as that when sos_cpu_context_switch got called + // arg2= to_context -- esp+8 + // arg1= from_context -- esp+4 + // caller ip -- esp + ret + + + +/* ------------------------- */ +.globl sos_cpu_context_exit_to +.type sos_cpu_context_exit_to, @function +sos_cpu_context_exit_to: + // arg3= reclaiming_arg -- esp+12 + // arg2= reclaiming_func -- esp+8 + // arg1= to_context -- esp+4 + // caller ip -- esp + + /* Store the current SP in a temporary register */ + movl %esp, %eax + + /* This is the proper context switch ! We change the stack here */ + movl 4(%eax), %esp + + /* Call the reclaiming function (remember: the old frame address + is stored in eax) */ + pushl 12(%eax) + call *8(%eax) + addl $4, %esp + + /* Prepare kernel TSS in case we are switching to a user thread: we + make sure that we will come back into the kernel at a correct + stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + + /* Restore the CPU context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + addl $4, %esp /* Ignore "error code" */ + + /* This restores the eflags, the cs and the eip registers */ + iret /* equivalent to: popfl ; ret */ diff --git a/hwcore/exception.c b/hwcore/exception.c new file mode 100644 index 0000000..7faf158 --- /dev/null +++ b/hwcore/exception.c @@ -0,0 +1,183 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "idt.h" +#include "irq.h" + +#include +#include +#include + +#include "exception.h" + +/* array of exception wrappers, defined in exception_wrappers.S */ +extern sos_vaddr_t sos_exception_wrapper_array[SOS_EXCEPT_NUM]; + +/* arrays of exception handlers, shared with exception_wrappers.S */ +sos_exception_handler_t sos_exception_handler_array[SOS_EXCEPT_NUM] = + { NULL, }; + +/* List of exception names for the x86 architecture */ +static const char * sos_x86_exnames[] = { + [SOS_EXCEPT_DIVIDE_ERROR] = "Division by zero", + [SOS_EXCEPT_DEBUG] = "Debug", + [SOS_EXCEPT_NMI_INTERRUPT] = "Non Maskable Interrupt", + [SOS_EXCEPT_BREAKPOINT] = "Breakpoint", + [SOS_EXCEPT_OVERFLOW] = "Overflow", + [SOS_EXCEPT_BOUND_RANGE_EXCEDEED] = "Bound Range Exceeded", + [SOS_EXCEPT_INVALID_OPCODE] = "Invalid Opcode", + [SOS_EXCEPT_DEVICE_NOT_AVAILABLE] = "Device Unavailable", + [SOS_EXCEPT_DOUBLE_FAULT] = "Double Fault", + [SOS_EXCEPT_COPROCESSOR_SEGMENT_OVERRUN] = "Coprocessor Segment Overrun", + [SOS_EXCEPT_INVALID_TSS] = "Invalid TSS", + [SOS_EXCEPT_SEGMENT_NOT_PRESENT] = "Segment Not Present", + [SOS_EXCEPT_STACK_SEGMENT_FAULT] = "Stack Segfault", + [SOS_EXCEPT_GENERAL_PROTECTION] = "General Protection", + [SOS_EXCEPT_PAGE_FAULT] = "Page Fault", + [SOS_EXCEPT_INTEL_RESERVED_1] = "INTEL1", + [SOS_EXCEPT_FLOATING_POINT_ERROR] = "FP Error", + [SOS_EXCEPT_ALIGNEMENT_CHECK] = "Alignment Check", + [SOS_EXCEPT_MACHINE_CHECK] = "Machine Check", + [SOS_EXCEPT_INTEL_RESERVED_2] = "INTEL2", + [SOS_EXCEPT_INTEL_RESERVED_3] = "INTEL3", + [SOS_EXCEPT_INTEL_RESERVED_4] = "INTEL4", + [SOS_EXCEPT_INTEL_RESERVED_5] = "INTEL5", + [SOS_EXCEPT_INTEL_RESERVED_6] = "INTEL6", + [SOS_EXCEPT_INTEL_RESERVED_7] = "INTEL7", + [SOS_EXCEPT_INTEL_RESERVED_8] = "INTEL8", + [SOS_EXCEPT_INTEL_RESERVED_9] = "INTEL9", + [SOS_EXCEPT_INTEL_RESERVED_10] = "INTEL10", + [SOS_EXCEPT_INTEL_RESERVED_11] = "INTEL11", + [SOS_EXCEPT_INTEL_RESERVED_12] = "INTEL12", + [SOS_EXCEPT_INTEL_RESERVED_13] = "INTEL13", + [SOS_EXCEPT_INTEL_RESERVED_14] = "INTEL14" +}; + + +/* Catch-all exception handler */ +static void sos_generic_ex(int exid, struct sos_cpu_state *ctxt) + __attribute__((noreturn)); +static void sos_generic_ex(int exid, struct sos_cpu_state *ctxt) +{ + const char *exname = sos_exception_get_name(exid); + + if (sos_cpu_context_is_in_user_mode(ctxt)) + { + /* Exception while in user mode */ + sos_bochs_printf("Exception %s in User mode at instruction 0x%x (info=%x)!\n", + exname, + sos_cpu_context_get_PC(ctxt), + (unsigned)sos_cpu_context_get_EX_info(ctxt)); + sos_bochs_printf("Terminating User thread\n"); + sos_thread_exit(); + } + else + sos_display_fatal_error("Exception %s in Kernel at instruction 0x%x (info=%x)!\n", + exname, + sos_cpu_context_get_PC(ctxt), + (unsigned)sos_cpu_context_get_EX_info(ctxt)); +} + + +sos_ret_t sos_exception_subsystem_setup(void) +{ + sos_ret_t retval; + int exid; + + /* Setup the generic exception handler by default for everybody + except for the double fault exception */ + for (exid = 0 ; exid < SOS_EXCEPT_NUM ; exid ++) + { + /* Skip double fault (see below) */ + if (exid == SOS_EXCEPT_DOUBLE_FAULT) + continue; + + retval = sos_exception_set_routine(exid, sos_generic_ex); + if (SOS_OK != retval) + return retval; + } + + + /* We inidicate that the double fault exception handler is defined, + and give its address. this handler is a do-nothing handler (see + exception_wrappers.S), and it can NOT be overriden by the + functions below */ + return sos_idt_set_handler(SOS_EXCEPT_BASE + SOS_EXCEPT_DOUBLE_FAULT, + (sos_vaddr_t) sos_exception_wrapper_array[SOS_EXCEPT_DOUBLE_FAULT], + 0 /* CPL0 routine */); +} + + +sos_ret_t sos_exception_set_routine(int exception_number, + sos_exception_handler_t routine) +{ + sos_ret_t retval; + sos_ui32_t flags; + + if ((exception_number < 0) || (exception_number >= SOS_EXCEPT_NUM)) + return -SOS_EINVAL; + + /* Double fault not supported */ + if (exception_number == SOS_EXCEPT_DOUBLE_FAULT) + return -SOS_ENOSUP; + + sos_disable_IRQs(flags); + + retval = SOS_OK; + + /* Set the exception routine to be called by the exception wrapper */ + sos_exception_handler_array[exception_number] = routine; + + /* If the exception is to be enabled, update the IDT with the exception + wrapper */ + if (routine != NULL) + retval + = sos_idt_set_handler(SOS_EXCEPT_BASE + exception_number, + (sos_vaddr_t) sos_exception_wrapper_array[exception_number], + 0 /* CPL0 routine */); + else /* Disable the IDT entry */ + retval + = sos_idt_set_handler(SOS_EXCEPT_BASE + exception_number, + (sos_vaddr_t)NULL /* No routine => disable IDTE */, + 0 /* don't care */); + + sos_restore_IRQs(flags); + return retval; +} + + +sos_exception_handler_t sos_exception_get_routine(int exception_number) +{ + if ((exception_number < 0) || (exception_number >= SOS_EXCEPT_NUM)) + return NULL; + + /* Double fault not supported */ + if (exception_number == SOS_EXCEPT_DOUBLE_FAULT) + return NULL; + + /* Expected to be atomic */ + return sos_exception_handler_array[exception_number]; +} + + +const char * sos_exception_get_name(int exception_number) +{ + if ((exception_number < 0) || (exception_number >= SOS_EXCEPT_NUM)) + return NULL; + + return sos_x86_exnames[exception_number]; +} diff --git a/hwcore/exception.h b/hwcore/exception.h new file mode 100644 index 0000000..177d760 --- /dev/null +++ b/hwcore/exception.h @@ -0,0 +1,83 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_HWEXCEPT_H_ +#define _SOS_HWEXCEPT_H_ + +/** + * @file exception.c + * + * Hardware exception routines management. + */ + +#ifndef ASM_SOURCE +# include +# include "cpu_context.h" +#endif + +/** + * Standard Intel x86 exceptions. + * + * @see Intel x86 doc vol 3, section 5.12. + */ +#define SOS_EXCEPT_DIVIDE_ERROR 0 // No error code +#define SOS_EXCEPT_DEBUG 1 // No error code +#define SOS_EXCEPT_NMI_INTERRUPT 2 // No error code +#define SOS_EXCEPT_BREAKPOINT 3 // No error code +#define SOS_EXCEPT_OVERFLOW 4 // No error code +#define SOS_EXCEPT_BOUND_RANGE_EXCEDEED 5 // No error code +#define SOS_EXCEPT_INVALID_OPCODE 6 // No error code +#define SOS_EXCEPT_DEVICE_NOT_AVAILABLE 7 // No error code +#define SOS_EXCEPT_DOUBLE_FAULT 8 // Yes (Zero) +#define SOS_EXCEPT_COPROCESSOR_SEGMENT_OVERRUN 9 // No error code +#define SOS_EXCEPT_INVALID_TSS 10 // Yes +#define SOS_EXCEPT_SEGMENT_NOT_PRESENT 11 // Yes +#define SOS_EXCEPT_STACK_SEGMENT_FAULT 12 // Yes +#define SOS_EXCEPT_GENERAL_PROTECTION 13 // Yes +#define SOS_EXCEPT_PAGE_FAULT 14 // Yes +#define SOS_EXCEPT_INTEL_RESERVED_1 15 // No +#define SOS_EXCEPT_FLOATING_POINT_ERROR 16 // No +#define SOS_EXCEPT_ALIGNEMENT_CHECK 17 // Yes (Zero) +#define SOS_EXCEPT_MACHINE_CHECK 18 // No +#define SOS_EXCEPT_INTEL_RESERVED_2 19 // No +#define SOS_EXCEPT_INTEL_RESERVED_3 20 // No +#define SOS_EXCEPT_INTEL_RESERVED_4 21 // No +#define SOS_EXCEPT_INTEL_RESERVED_5 22 // No +#define SOS_EXCEPT_INTEL_RESERVED_6 23 // No +#define SOS_EXCEPT_INTEL_RESERVED_7 24 // No +#define SOS_EXCEPT_INTEL_RESERVED_8 25 // No +#define SOS_EXCEPT_INTEL_RESERVED_9 26 // No +#define SOS_EXCEPT_INTEL_RESERVED_10 27 // No +#define SOS_EXCEPT_INTEL_RESERVED_11 28 // No +#define SOS_EXCEPT_INTEL_RESERVED_12 29 // No +#define SOS_EXCEPT_INTEL_RESERVED_13 30 // No +#define SOS_EXCEPT_INTEL_RESERVED_14 31 // No + +#ifndef ASM_SOURCE + +typedef void (*sos_exception_handler_t)(int exception_number, + struct sos_cpu_state *cpu_kstate); + +sos_ret_t sos_exception_subsystem_setup(void); +sos_ret_t sos_exception_set_routine(int exception_number, + sos_exception_handler_t routine); +sos_exception_handler_t sos_exception_get_routine(int exception_number); + +const char * sos_exception_get_name(int exception_number); +#endif /* ! ASM_SOURCE */ + +#endif /* _SOS_HWEXCEPT_H_ */ diff --git a/hwcore/exception_wrappers.S b/hwcore/exception_wrappers.S new file mode 100644 index 0000000..fdc06ca --- /dev/null +++ b/hwcore/exception_wrappers.S @@ -0,0 +1,271 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "exception.h" + +#include "segment.h" + +.file "exception_wrappers.S" + +.text + +/* The address of the table of handlers (defined in exception.c) */ +.extern sos_exception_handler_array + +/* The address of the table of wrappers (defined below, and shared + with exception.c */ +.globl sos_exception_wrapper_array + +/** Update the kernel TSS in case we are switching to a thread in user + mode in order to come back into the correct kernel stack */ +.extern sos_cpu_context_update_kernel_tss + +/* The address of the function to call to set back the user thread's + MMU configuration upon return to user context */ +.extern sos_thread_prepare_exception_switch_back + +/** + * For exceptions with/without error code, refer to Intel x86 doc vol 3, + * section 5.12 + */ + +/* These wrappers are for exceptions without error code */ +.irp id, \ + SOS_EXCEPT_DIVIDE_ERROR, \ + SOS_EXCEPT_DEBUG, \ + SOS_EXCEPT_NMI_INTERRUPT, \ + SOS_EXCEPT_BREAKPOINT, \ + SOS_EXCEPT_OVERFLOW, \ + SOS_EXCEPT_BOUND_RANGE_EXCEDEED, \ + SOS_EXCEPT_INVALID_OPCODE, \ + SOS_EXCEPT_DEVICE_NOT_AVAILABLE, \ + SOS_EXCEPT_COPROCESSOR_SEGMENT_OVERRUN, \ + SOS_EXCEPT_INTEL_RESERVED_1, \ + SOS_EXCEPT_FLOATING_POINT_ERROR, \ + SOS_EXCEPT_MACHINE_CHECK, \ + SOS_EXCEPT_INTEL_RESERVED_2, \ + SOS_EXCEPT_INTEL_RESERVED_3, \ + SOS_EXCEPT_INTEL_RESERVED_4, \ + SOS_EXCEPT_INTEL_RESERVED_5, \ + SOS_EXCEPT_INTEL_RESERVED_6, \ + SOS_EXCEPT_INTEL_RESERVED_7, \ + SOS_EXCEPT_INTEL_RESERVED_8, \ + SOS_EXCEPT_INTEL_RESERVED_9, \ + SOS_EXCEPT_INTEL_RESERVED_10, \ + SOS_EXCEPT_INTEL_RESERVED_11, \ + SOS_EXCEPT_INTEL_RESERVED_12, \ + SOS_EXCEPT_INTEL_RESERVED_13, \ + SOS_EXCEPT_INTEL_RESERVED_14 + + .p2align 2, 0x90 + sos_exception_wrapper_\id: + .type sos_exception_wrapper_\id,@function + + /* Fake error code */ + pushl $0 + /* Backup the context */ + pushl %ebp + movl %esp, %ebp + + pushl %edi + pushl %esi + pushl %edx + pushl %ecx + pushl %ebx + pushl %eax + subl $2,%esp + pushw %ss + pushw %ds + pushw %es + pushw %fs + pushw %gs + + /* Set correct kernel segment descriptors' value */ + movw $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_KDATA), %di + pushw %di ; popw %ds + pushw %di ; popw %es + pushw %di ; popw %fs + pushw %di ; popw %gs + + /* + * Call the handler with the exception number and the + * address of the stored CPU context as arguments + */ + pushl %esp + pushl $\id + leal sos_exception_handler_array,%edi + call *\id*4(%edi) + /* Unallocate the arguments passed to the handler */ + addl $8, %esp + + /* Reconfigure the MMU if needed */ + pushl %esp /* cpu_ctxt */ + call sos_thread_prepare_exception_switch_back + addl $4, %esp /* Unallocate the stack */ + + /* Prepare kernel TSS in case we are switching to a + user thread: we make sure that we will come back + into the kernel at a correct stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + + /* Restore the context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + + popl %ebp + /* Remove fake error code */ + addl $4, %esp + iret +.endr + + /* These wrappers are for exceptions with error code */ +.irp id, \ + SOS_EXCEPT_INVALID_TSS, \ + SOS_EXCEPT_SEGMENT_NOT_PRESENT, \ + SOS_EXCEPT_STACK_SEGMENT_FAULT, \ + SOS_EXCEPT_GENERAL_PROTECTION, \ + SOS_EXCEPT_PAGE_FAULT, \ + SOS_EXCEPT_ALIGNEMENT_CHECK + + .p2align 2, 0x90 + sos_exception_wrapper_\id: + .type sos_exception_wrapper_\id,@function + + /* ret eflags */ + /* ret cs */ + /* ret eip */ + /* Error code */ + + /* Backup the context */ + pushl %ebp + movl %esp, %ebp + + pushl %edi + pushl %esi + pushl %edx + pushl %ecx + pushl %ebx + pushl %eax + subl $2,%esp + pushw %ss + pushw %ds + pushw %es + pushw %fs + pushw %gs + + /* Set correct kernel segment descriptors' value */ + movw $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_KDATA), %di + pushw %di ; popw %ds + pushw %di ; popw %es + pushw %di ; popw %fs + pushw %di ; popw %gs + + /* + * Call the handler with the exception number and the + * address of the stored CPU context as arguments + */ + pushl %esp + pushl $\id + leal sos_exception_handler_array,%edi + call *\id*4(%edi) + /* Unallocate the arguments passed to the handler */ + addl $8, %esp + + /* Reconfigure the MMU if needed */ + pushl %esp /* cpu_ctxt */ + call sos_thread_prepare_exception_switch_back + addl $4, %esp /* Unallocate the stack */ + + /* Prepare kernel TSS in case we are switching to a + user thread: we make sure that we will come back + into the kernel at a correct stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + + /* Restore the context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + + /* Error code isn't compatible with iretd */ + addl $4, %esp + + iret +.endr + + +/* Double fault handler not supported. We must define it since we + define an entry for it in the sos_exception_wrapper_array. It + simply uses an alternate stack to display a message and stop the + system. qemu won't handle it correctly (see comment in qemu's + sources). */ +#define ALTERNATE_DOUBLE_FAULT_STACK_SIZE 512 +.irp id, SOS_EXCEPT_DOUBLE_FAULT +.p2align 2, 0x90 +sos_exception_wrapper_\id: +.type sos_exception_wrapper_\id,@function +1: cli /* Not necessary */ + movl $double_fault_alternate_stack, %eax + addl $ALTERNATE_DOUBLE_FAULT_STACK_SIZE, %eax + movl %eax, %esp + pushl $msg_double_fault_not_supported + call sos_display_fatal_error ; jmp 1b /* Not necessary */ +.endr + +.section ".rodata" +msg_double_fault_not_supported: + .string "exception_wrappers.S: Double fault detected ! NOT SUPPORTED yet. System Halted." + +/* Build the sos_irq_wrapper_array, shared with interrupt.c */ +.p2align 5, 0x0 +sos_exception_wrapper_array: + .irp id, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, \ + 16,17,18,19,20,21,22,23,24,25,26,27,29,30,31 + .long (sos_exception_wrapper_\id) + .endr + +/* Alternate stack for double fault handler */ +.bss +.p2align 2, 0x0 +.size double_fault_alternate_stack, ALTERNATE_DOUBLE_FAULT_STACK_SIZE +double_fault_alternate_stack: + .fill ALTERNATE_DOUBLE_FAULT_STACK_SIZE, 1, 0x0 diff --git a/hwcore/gdt.c b/hwcore/gdt.c new file mode 100644 index 0000000..e684d70 --- /dev/null +++ b/hwcore/gdt.c @@ -0,0 +1,203 @@ +/* Copyright (C) 2004 David Decotigny + Copyright (C) 2003 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "segment.h" + +#include "gdt.h" + + +/** + * The sructure of a segment descriptor. + * + * @see Intel x86 doc, Vol 3, section 3.4.3, figure 3-8. For segment + * types, see section 3.5 + */ +struct x86_segment_descriptor +{ + /* Lowest dword */ + sos_ui16_t limit_15_0; /* Segment limit, bits 15..0 */ + sos_ui16_t base_paged_addr_15_0; /* Base address, bits 15..0 */ + + /* Highest dword */ + sos_ui8_t base_paged_addr_23_16; /* Base address bits 23..16 */ + sos_ui8_t segment_type:4; /* Section 3.4.3.1 (code/data) + and 3.5 (system) of Intel x86 vol 3 */ + sos_ui8_t descriptor_type:1; /* 0=system, 1=Code/Data */ + sos_ui8_t dpl:2; /* Descriptor privilege level */ + sos_ui8_t present:1; + + sos_ui8_t limit_19_16:4; /* Segment limit, bits 19..16 */ + sos_ui8_t custom:1; + sos_ui8_t zero:1; + sos_ui8_t op_size:1; /* 0=16bits instructions, 1=32bits */ + sos_ui8_t granularity:1; /* 0=limit in bytes, 1=limit in pages */ + + sos_ui8_t base_paged_addr_31_24; /* Base address bits 31..24 */ +} __attribute__ ((packed, aligned(8))); + + +/** + * The GDT register, which stores the address and size of the + * GDT. + * + * @see Intel x86 doc vol 3, section 2.4, figure 2-4; and section + * 3.5.1 + */ +struct x86_gdt_register { + /** Intel doc says that the real GDT register (ie the "limit" field) + should be odd-word aligned. That's why we add a padding here. + Credits to Romain for having signalled this to us. */ + sos_ui16_t padding; + + /** The maximum GDT offset allowed to access an entry in the GDT */ + sos_ui16_t limit; + + /** + * This is not exactly a "virtual" address, ie an adddress such as + * those of instructions and data; this is a "linear" address, ie an + * address in the paged memory. However, in SOS we configure the + * segmented memory as a "flat" space: the 0-4GB segment-based (ie + * "virtual") addresses directly map to the 0-4GB paged memory (ie + * "linear"), so that the "linear" addresses are numerically equal + * to the "virtual" addresses: this base_addr will thus be the same + * as the address of the gdt array + */ + sos_ui32_t base_addr; +} __attribute__((packed, aligned(4))); + + +/** + * Helper macro that builds a Segment descriptor for the virtual + * 0..4GB addresses to be mapped to the linear 0..4GB linear + * addresses. + */ +#define BUILD_GDTE(descr_privilege_level,is_code) \ + ((struct x86_segment_descriptor) { \ + .limit_15_0= 0xffff, \ + .base_paged_addr_15_0= 0, \ + .base_paged_addr_23_16= 0, \ + .segment_type= ((is_code)?0xb:0x3), \ + /* With descriptor_type (below) = 1 (code/data), \ + * see Figure 3-1 of section 3.4.3.1 in Intel \ + * x86 vol 3: \ + * - Code (bit 3 = 1): \ + * bit 0: 1=Accessed \ + * bit 1: 1=Readable \ + * bit 2: 0=Non-Conforming \ + * - Data (bit 3 = 0): \ + * bit 0: 1=Accessed \ + * bit 1: 1=Writable \ + * bit 2: 0=Expand up (stack-related) \ + * For Conforming/non conforming segments, see \ + * Intel x86 Vol 3 section 4.8.1.1 \ + */ \ + .descriptor_type= 1, /* 1=Code/Data */ \ + .dpl= ((descr_privilege_level) & 0x3), \ + .present= 1, \ + .limit_19_16= 0xf, \ + .custom= 0, \ + .zero= 0, \ + .op_size= 1, /* 32 bits instr/data */ \ + .granularity= 1, /* limit is in 4kB Pages */ \ + .base_paged_addr_31_24= 0 \ + }) + + +/** The actual GDT */ +static struct x86_segment_descriptor gdt[] = { + [SOS_SEG_NULL] = (struct x86_segment_descriptor){ 0, }, + [SOS_SEG_KCODE] = BUILD_GDTE(0, 1), + [SOS_SEG_KDATA] = BUILD_GDTE(0, 0), + [SOS_SEG_UCODE] = BUILD_GDTE(3, 1), + [SOS_SEG_UDATA] = BUILD_GDTE(3, 0), + [SOS_SEG_KERNEL_TSS] = { 0, } /* Initialized by + register_kernel_tss */ +}; + + +sos_ret_t sos_gdt_subsystem_setup(void) +{ + struct x86_gdt_register gdtr; + + /* Put some garbage in the padding field of the GDTR */ + gdtr.padding = ~0; + + /* Address of the GDT */ + gdtr.base_addr = (sos_ui32_t) gdt; + + /* The limit is the maximum offset in bytes from the base address of + the GDT */ + gdtr.limit = sizeof(gdt) - 1; + + /* Commit the GDT into the CPU, and update the segment + registers. The CS register may only be updated with a long jump + to an absolute address in the given segment (see Intel x86 doc + vol 3, section 4.8.1). */ + asm volatile ("lgdt %0 \n\ + ljmp %1,$1f \n\ + 1: \n\ + movw %2, %%ax \n\ + movw %%ax, %%ss \n\ + movw %%ax, %%ds \n\ + movw %%ax, %%es \n\ + movw %%ax, %%fs \n\ + movw %%ax, %%gs" + : + :"m"(gdtr.limit) /* The real beginning of the GDT + register is /after/ the "padding" + field, ie at the "limit" + field. */, + "i"(SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE)), + "i"(SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KDATA)) + :"memory","eax"); + + return SOS_OK; +} + + +sos_ret_t sos_gdt_register_kernel_tss(sos_vaddr_t tss_vaddr) +{ + sos_ui16_t regval_tss; + + /* Initialize the GDT entry */ + gdt[SOS_SEG_KERNEL_TSS] + = (struct x86_segment_descriptor) { + .limit_15_0= 0x67, /* See Intel x86 vol 3 section 6.2.2 */ + .base_paged_addr_15_0= (tss_vaddr) & 0xffff, + .base_paged_addr_23_16= (tss_vaddr >> 16) & 0xff, + .segment_type= 0x9, /* See Intel x86 vol 3 figure 6-3 */ + .descriptor_type= 0, /* (idem) */ + .dpl= 3, /* Allowed for CPL3 tasks */ + .present= 1, + .limit_19_16= 0, /* Size of a TSS is < 2^16 ! */ + .custom= 0, /* Unused */ + .zero= 0, + .op_size= 0, /* See Intel x86 vol 3 figure 6-3 */ + .granularity= 1, /* limit is in Bytes */ + .base_paged_addr_31_24= (tss_vaddr >> 24) & 0xff + }; + + /* Load the TSS register into the processor */ + regval_tss = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, + SOS_SEG_KERNEL_TSS); + asm ("ltr %0" : :"r"(regval_tss)); + + return SOS_OK; +} + + diff --git a/hwcore/gdt.h b/hwcore/gdt.h new file mode 100644 index 0000000..11e829f --- /dev/null +++ b/hwcore/gdt.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_GDT_H_ +#define _SOS_GDT_H_ + +/** + * @file gdt.h + * + * The routines that manage the GDT, the table that maps the virtual + * addresses (data/instructions, segment-relative), to "linear" + * addresses (ie paged-memory). In SOS/x86, we use a "flat" virtual + * space, ie the virtual and linear spaces are equivalent. + * + * @see Intel x86 doc vol 3, chapter 3 + */ + +#include +#include + +/** + * Configure the virtual space as a direct mapping to the linear + * address space (ie "flat" virtual space). + */ +sos_ret_t sos_gdt_subsystem_setup(void); + + +/** + * Register the given address as the kernel TSS (for cpl3 -> cpl0 + * privilege changes). Assumes that this address corresponds to a + * valid TSS. + * + * @note Internal function called only by the cpu_context subsystem. + */ +sos_ret_t sos_gdt_register_kernel_tss(sos_vaddr_t tss_vaddr); + + +#endif /* _SOS_GDT_H_ */ diff --git a/hwcore/i8254.c b/hwcore/i8254.c new file mode 100644 index 0000000..5198129 --- /dev/null +++ b/hwcore/i8254.c @@ -0,0 +1,79 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include + +#include "i8254.h" + +/** 82c54 clock frequency */ +#define I8254_MAX_FREQ 1193180 + +/* Ports to communicate with the 82c54 */ +#define I8254_TIMER0 0x40 +#define I8254_TIMER1 0x41 +#define I8254_TIMER2 0x42 +#define I8254_CONTROL 0x43 + +/** + * Configure the first timer of the 82c54 chip as a rate generator, + * which will raise an IRQ0 on a regular periodic basis, as given by + * the freq parameter. Second (RAM refresh) and third (speaker) timers + * are left unchanged. Maximum frequency is that of the 8254 clock, ie + * 1193180 Hz. + * + * Ahhh PC systems are nice toys: this maximum "strange" frequency + * equals that of the NTSC clock (14.31818 MHz) divided by 12. In + * turn, the famous 4.77 MHz cpu clock frequency of the first IBM PC + * is this same NTSC frequency divided by 3. Why the NTSC frequency as + * a base "standard" ? Because the 14.31818 MHz quartz were cheap at + * that time, and because it allows to simply drive altogether the + * cpu, the "time of day" timer, and the video signal generators. + */ +sos_ret_t sos_i8254_set_frequency(unsigned int freq) +{ + unsigned int nb_tick; + + if (freq <= 0) + return -SOS_EINVAL; + + /* Compute counter value */ + nb_tick = I8254_MAX_FREQ / freq; + + /* Counter must be between 1 and 65536 */ + if (nb_tick > 65536) + return -SOS_EINVAL; + if (nb_tick <= 0) + return -SOS_EINVAL; + + /* The i8254 interprets 0 to mean counter == 65536, because 65536 + cannot be coded on 16bits */ + if (nb_tick == 65536) + nb_tick = 0; + + /* We want to configure timer0, we want to send both LSB+MSB to set + timer0 freq (-> 0x30), and we configure timer0 in mode 2, ie as a + rate generator (-> 0x4) ==> 0x34 */ + outb(0x34, I8254_CONTROL); + + /* Send LSB of counter first */ + outb((nb_tick & 0xFF), I8254_TIMER0); + + /* Send MSB of counter */ + outb((nb_tick >> 8) & 0xFF, I8254_TIMER0); + + return SOS_OK; +} diff --git a/hwcore/i8254.h b/hwcore/i8254.h new file mode 100644 index 0000000..e89a847 --- /dev/null +++ b/hwcore/i8254.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_i8254_H_ +#define _SOS_i8254_H_ + +#include + +/** + * @file i8254.h PC programmable timer + * + * Programmable timer routines. See the Intel 82C54 datasheet (on kos + * website). + * + * @see i82C54 datasheet on Kos website. + */ + +/** Change timer interrupt (IRQ 0) frequency */ +sos_ret_t sos_i8254_set_frequency(unsigned int freq); + +#endif /* _SOS_i8254_H_ */ diff --git a/hwcore/i8259.c b/hwcore/i8259.c new file mode 100644 index 0000000..d6c6c60 --- /dev/null +++ b/hwcore/i8259.c @@ -0,0 +1,79 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "ioports.h" + +#include "i8259.h" + +#define PIC_MASTER 0x20 +#define PIC_SLAVE 0xa0 + +/** Setup the 8259 PIC */ +sos_ret_t sos_i8259_subsystem_setup(void) +{ + /* Send ICW1: 8086 mode + NOT Single ctrl + call address + interval=8 */ + outb(0x11, PIC_MASTER); + outb(0x11, PIC_SLAVE); + + /* Send ICW2: ctrl base address */ + outb(0x20, PIC_MASTER+1); + outb(0x28, PIC_SLAVE+1); + + /* Send ICW3 master: mask where slaves are connected */ + outb(0x4, PIC_MASTER+1); + /* Send ICW3 slave: index where the slave is connected on master */ + outb(0x2, PIC_SLAVE+1); + + /* Send ICW4: 8086 mode, fully nested, not buffered, no implicit EOI */ + outb(0x1, PIC_MASTER+1); + outb(0x1, PIC_SLAVE+1); + + /* Send OCW1: + * Closing all IRQs : waiting for a correct handler The only IRQ + * enabled is the cascade (that's why we use 0xFB for the master) */ + outb(0xFB, PIC_MASTER+1); + outb(0xFF, PIC_SLAVE+1); + + return SOS_OK; +} + + +sos_ret_t sos_i8259_enable_irq_line(int numirq) +{ + if(numirq < 8) + /* irq on master PIC */ + outb((inb(PIC_MASTER+1) & ~(1 << numirq)), PIC_MASTER+1); + else + /* irq on slave PIC */ + outb((inb(PIC_SLAVE+1) & ~(1 << (numirq-8))), PIC_SLAVE+1); + + return SOS_OK; +} + + +sos_ret_t sos_i8259_disable_irq_line(int numirq) +{ + if(numirq < 8) + /* irq on master PIC */ + outb((inb(PIC_MASTER+1) | (1 << numirq)), PIC_MASTER+1); + else + /* irq on slave PIC */ + outb((inb(PIC_SLAVE+1) | (1 << (numirq-8))), PIC_SLAVE+1); + + return SOS_OK; +} diff --git a/hwcore/i8259.h b/hwcore/i8259.h new file mode 100644 index 0000000..0820524 --- /dev/null +++ b/hwcore/i8259.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_i8259_H_ +#define _SOS_i8259_H_ + +#include + +/** + * @file i8259.h PIC + * + * PIC Management routines. See the Intel 8259A datasheet (on kos + * website), page 9+. Should be not be used directly: only interrupt.c + * should use this. + * + * @see i8259A datasheet on Kos website. + */ + +/** Setup PIC and Disable all IRQ lines */ +sos_ret_t sos_i8259_subsystem_setup(void); + +sos_ret_t sos_i8259_enable_irq_line(int numirq); + +sos_ret_t sos_i8259_disable_irq_line(int numirq); + +#endif /* _SOS_i8259_H_ */ diff --git a/hwcore/idt.c b/hwcore/idt.c new file mode 100644 index 0000000..07db4ab --- /dev/null +++ b/hwcore/idt.c @@ -0,0 +1,159 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "segment.h" + +#include "idt.h" + +/** + * An entry in the IDT, or "IDTE" in the following, ie a reference to + * a interrupt/trap routine or a task gate to handle the sw/hw + * interrupts and exceptions. + * + * @see figure 5-2, intel x86 doc, vol 3 + */ +struct x86_idt_entry +{ + /* Low dword */ + sos_ui16_t offset_low; /* 15..0, offset of the routine in the segment */ + sos_ui16_t seg_sel; /* 31..16, the ID of the segment */ + + /* High dword */ + sos_ui8_t reserved:5; /* 4..0 */ + sos_ui8_t flags:3; /* 7..5 */ + sos_ui8_t type:3; /* 10..8 (interrupt gate, trap gate...) */ + sos_ui8_t op_size:1; /* 11 (0=16bits instructions, 1=32bits instr.) */ + sos_ui8_t zero:1; /* 12 */ + sos_ui8_t dpl:2; /* 14..13 */ + sos_ui8_t present:1; /* 15 */ + sos_ui16_t offset_high; /* 31..16 */ +} __attribute__((packed)); + + +/** + * The IDT register, which stores the address and size of the + * IDT. + * + * @see Intel x86 doc vol 3, section 2.4, figure 2-4 + */ +struct x86_idt_register +{ + /* The maximum GDT offset allowed to access an entry in the GDT */ + sos_ui16_t limit; + + /* This is not exactly a "virtual" address, ie an adddress such as + those of instructions and data; this is a "linear" address, ie an + address in the paged memory. However, in SOS we configure the + segmented memory as a "flat" space: the 0-4GB segment-based (ie + "virtual") addresses directly map to the 0-4GB paged memory (ie + "linear"), so that the "linear" addresses are numerically equal + to the "virtual" addresses: this base_addr will thus be the same + as the address of the gdt array */ + sos_ui32_t base_addr; +} __attribute__((packed, aligned (8))); + + +static struct x86_idt_entry idt[SOS_IDTE_NUM]; + +sos_ret_t sos_idt_subsystem_setup() +{ + struct x86_idt_register idtr; + int i; + + for (i = 0 ; + i < SOS_IDTE_NUM ; + i++) + { + struct x86_idt_entry *idte = idt + i; + + /* Setup an empty IDTE interrupt gate, see figure 5-2 in Intel + x86 doc, vol 3 */ + idte->seg_sel = SOS_BUILD_SEGMENT_REG_VALUE(0, FALSE, SOS_SEG_KCODE); + idte->reserved = 0; + idte->flags = 0; + idte->type = 0x6; /* Interrupt gate (110b) */ + idte->op_size = 1; /* 32bits instructions */ + idte->zero = 0; + + /* Disable this IDT entry for the moment */ + sos_idt_set_handler(i, (sos_vaddr_t)NULL, 0/* Don't care */); + } + + /* + * Setup the IDT register, see Intel x86 doc vol 3, section 5.8. + */ + + /* Address of the IDT */ + idtr.base_addr = (sos_ui32_t) idt; + + /* The limit is the maximum offset in bytes from the base address of + the IDT */ + idtr.limit = sizeof(idt) - 1; + + /* Commit the IDT into the CPU */ + asm volatile ("lidt %0\n"::"m"(idtr):"memory"); + + return SOS_OK; +} + + +sos_ret_t sos_idt_set_handler(int index, + sos_vaddr_t handler_address, + int lowest_priviledge /* 0..3 */) +{ + struct x86_idt_entry *idte; + + if ((index < 0) || (index >= SOS_IDTE_NUM)) + return -SOS_EINVAL; + if ((lowest_priviledge < 0) || (lowest_priviledge > 3)) + return -SOS_EINVAL; + + idte = idt + index; + if (handler_address != (sos_vaddr_t)NULL) + { + idte->offset_low = handler_address & 0xffff; + idte->offset_high = (handler_address >> 16) & 0xffff; + idte->dpl = lowest_priviledge; + idte->present = 1; /* Yes, there is a handler */ + } + else /* Disable this IDT entry */ + { + idte->offset_low = 0; + idte->offset_high = 0; + idte->dpl = 0; + idte->present = 0; /* No, there is no handler */ + } + + return SOS_OK; +} + + +sos_ret_t sos_idt_get_handler(int index, + sos_vaddr_t *handler_address, + int *lowest_priviledge) +{ + if ((index < 0) || (index >= SOS_IDTE_NUM)) + return -SOS_EINVAL; + + if (handler_address != NULL) + *handler_address = idt[index].offset_low + | (idt[index].offset_high << 16); + if (lowest_priviledge != NULL) + *lowest_priviledge = idt[index].dpl; + + return SOS_OK; +} diff --git a/hwcore/idt.h b/hwcore/idt.h new file mode 100644 index 0000000..2d165b0 --- /dev/null +++ b/hwcore/idt.h @@ -0,0 +1,84 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_IDT_H_ +#define _SOS_IDT_H_ + +/** + * @file idt.h + * + * Manage the x86 Interrupt Descriptor Table, the table which maps the + * hardware interrupt lines, hardware exceptions, and software + * interrupts, to software routines. We only define "interrupt gate" + * IDT entries. Don't use it directly; refer instead to interrupt.c, + * exceptions.c and syscall.c. + * + * @see Intel x86 doc, Vol 3, chapter 5 + */ + +#include +#include + +/* Mapping of the CPU exceptions in the IDT (imposed by Intel + standards) */ +#define SOS_EXCEPT_BASE 0 +#define SOS_EXCEPT_NUM 32 +#define SOS_EXCEPT_MAX (SOS_HWEXCEPT_BASE + SOS_HWEXCEPT_NUM - 1) + +/* Mapping of the IRQ lines in the IDT */ +#define SOS_IRQ_BASE 32 +#define SOS_IRQ_NUM 16 +#define SOS_IRQ_MAX (SOS_IRQ_BASE + SOS_IRQ_NUM - 1) + +/** + * Number of IDT entries. + * + * @note Must be large enough to map the hw interrupts, the exceptions + * (=> total is 48 entries), and the syscall(s). Since our syscall + * will be 0x42, it must be >= 0x43. Intel doc limits this to 256 + * entries, we use this limit. + */ +#define SOS_IDTE_NUM 256 /* 0x100 */ + +/** Initialization routine: all the IDT entries (or "IDTE") are marked + "not present". */ +sos_ret_t sos_idt_subsystem_setup(void); + +/** + * Enable the IDT entry if handler_address != NULL, with the given + * lowest_priviledge.\ Disable the IDT entry when handler_address == + * NULL (the lowest_priviledge parameter is then ignored). Intel doc + * says that there must not be more than 256 entries. + * + * @note IRQ Unsafe + */ +sos_ret_t sos_idt_set_handler(int index, + sos_vaddr_t handler_address, + int lowest_priviledge /* 0..3 */); + + +/** + * @note IRQ Unsafe + * + * @return the handler address and DPL in the 2nd and 3rd + * parameters + */ +sos_ret_t sos_idt_get_handler(int index, + sos_vaddr_t *handler_address, + int *lowest_priviledge); + +#endif /* _SOS_IDT_H_ */ diff --git a/hwcore/ioports.h b/hwcore/ioports.h new file mode 100644 index 0000000..e6fbc30 --- /dev/null +++ b/hwcore/ioports.h @@ -0,0 +1,84 @@ +/* Copyright (C) 2004 All GPL'ed OS + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_IOPORTS_H_ +#define _SOS_IOPORTS_H_ + +/** + * @ioports.h + * + * Intel-specific I/O space access routines. + */ + +/* This macro allows to write to an I/O port */ +#define outb(value, port) \ + __asm__ volatile ( \ + "outb %b0,%w1" \ + ::"a" (value),"Nd" (port) \ + ) \ + +// read one byte from port +#define inb(port) \ +({ \ + unsigned char _v; \ + __asm__ volatile ( \ + "inb %w1,%0" \ + :"=a" (_v) \ + :"Nd" (port) \ + ); \ + _v; \ +}) + +// write value (word) on port +#define outw(value, port) \ + __asm__ volatile ( \ + "outw %w0,%w1" \ + ::"a" (value),"Nd" (port) \ + ) \ + +// read one word from port +#define inw(port) \ +({ \ + unsigned int _v; \ + __asm__ volatile ( \ + "inw %w1,%w0" \ + :"=a" (_v) \ + :"Nd" (port) \ + ); \ + _v; \ +}) + +// write value (word) on port +#define outl(value, port) \ + __asm__ volatile ( \ + "outl %0,%w1" \ + ::"a" (value),"Nd" (port) \ + ) \ + +// read one word from port +#define inl(port) \ +({ \ + unsigned int _v; \ + __asm__ volatile ( \ + "inl %w1,%0" \ + :"=a" (_v) \ + :"Nd" (port) \ + ); \ + _v; \ +}) + +#endif /* _SOS_IOPORTS_H_ */ diff --git a/hwcore/irq.c b/hwcore/irq.c new file mode 100644 index 0000000..6722027 --- /dev/null +++ b/hwcore/irq.c @@ -0,0 +1,100 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "idt.h" +#include "i8259.h" + +#include "irq.h" + +/** array of IRQ wrappers, defined in irq_wrappers.S */ +extern sos_vaddr_t sos_irq_wrapper_array[SOS_IRQ_NUM]; + +/** arrays of IRQ handlers, shared with irq_wrappers.S */ +sos_irq_handler_t sos_irq_handler_array[SOS_IRQ_NUM] = { NULL, }; + +/** Number of interrupt handlers that are currently executing */ +sos_ui32_t sos_irq_nested_level_counter; + +sos_ret_t sos_irq_subsystem_setup(void) +{ + sos_irq_nested_level_counter = 0; + return sos_i8259_subsystem_setup(); +} + + +sos_ret_t sos_irq_set_routine(int irq_level, + sos_irq_handler_t routine) +{ + sos_ret_t retval; + sos_ui32_t flags; + + if ((irq_level < 0) || (irq_level >= SOS_IRQ_NUM)) + return -SOS_EINVAL; + + sos_disable_IRQs(flags); + + retval = SOS_OK; + + /* Set the irq routine to be called by the IRQ wrapper */ + sos_irq_handler_array[irq_level] = routine; + + /* If the irq is to be enabled, update the IDT with the IRQ + wrapper */ + if (routine != NULL) + { + retval + = sos_idt_set_handler(SOS_IRQ_BASE + irq_level, + (sos_vaddr_t) sos_irq_wrapper_array[irq_level], + 0 /* CPL0 routine */); + /* A problem occured */ + if (retval != SOS_OK) + sos_irq_handler_array[irq_level] = NULL; + } + else /* Disable this idt entry */ + { + retval + = sos_idt_set_handler(SOS_IRQ_BASE + irq_level, + (sos_vaddr_t)NULL /* Disable IDTE */, + 0 /* Don't care */); + } + + /* Update the PIC only if an IRQ handler has been set */ + if (sos_irq_handler_array[irq_level] != NULL) + sos_i8259_enable_irq_line(irq_level); + else + sos_i8259_disable_irq_line(irq_level); + + sos_restore_IRQs(flags); + return retval; +} + + +sos_irq_handler_t sos_irq_get_routine(int irq_level) +{ + if ((irq_level < 0) || (irq_level >= SOS_IRQ_NUM)) + return NULL; + + /* Expected to be atomic */ + return sos_irq_handler_array[irq_level]; +} + + +sos_ui32_t sos_irq_get_nested_level() +{ + /* No need to disable interrupts here */ + return sos_irq_nested_level_counter; +} diff --git a/hwcore/irq.h b/hwcore/irq.h new file mode 100644 index 0000000..9eed17d --- /dev/null +++ b/hwcore/irq.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_HWINTR_H_ +#define _SOS_HWINTR_H_ + + +/** + * @file irq.c + * + * Hardware interrupts routines management. + */ + + +#include +#include "cpu_context.h" + + +#define sos_save_flags(flags) \ + asm volatile("pushfl ; popl %0":"=g"(flags)::"memory") +#define sos_restore_flags(flags) \ + asm volatile("push %0; popfl"::"g"(flags):"memory") + + +#define sos_disable_IRQs(flags) \ + ({ sos_save_flags(flags); asm("cli\n"); }) +#define sos_restore_IRQs(flags) \ + sos_restore_flags(flags) + + +/* Usual IRQ levels */ +#define SOS_IRQ_TIMER 0 +#define SOS_IRQ_KEYBOARD 1 +#define SOS_IRQ_SLAVE_PIC 2 +#define SOS_IRQ_COM2 3 +#define SOS_IRQ_COM1 4 +#define SOS_IRQ_LPT2 5 +#define SOS_IRQ_FLOPPY 6 +#define SOS_IRQ_LPT1 7 +#define SOS_IRQ_8_NOT_DEFINED 8 +#define SOS_IRQ_RESERVED_1 9 +#define SOS_IRQ_RESERVED_2 10 +#define SOS_IRQ_RESERVED_3 11 +#define SOS_IRQ_RESERVED_4 12 +#define SOS_IRQ_COPROCESSOR 13 +#define SOS_IRQ_HARDDISK 14 +#define SOS_IRQ_RESERVED_5 15 + + +/** Definition of an hardware IRQ handler */ +typedef void (*sos_irq_handler_t)(int irq_level); + + +/** Setup the PIC */ +sos_ret_t sos_irq_subsystem_setup(void); + + +/** + * If the routine is not NULL, the IDT is setup to call an IRQ + * wrapper upon interrupt, which in turn will call the routine, and + * the PIC is programmed to raise an irq.\ If the routine is + * NULL, we disable the irq line. + */ +sos_ret_t sos_irq_set_routine(int irq_level, + sos_irq_handler_t routine); + +sos_irq_handler_t sos_irq_get_routine(int irq_level); + + +/** + * Tell how many nested IRQ handler have been fired + */ +sos_ui32_t sos_irq_get_nested_level(void); + + +/** + * Return TRUE when we are currently executing in interrupt context + */ +#define sos_servicing_irq() \ + (sos_irq_get_nested_level() > 0) + + +#endif /* _SOS_HWINTR_H_ */ diff --git a/hwcore/irq_wrappers.S b/hwcore/irq_wrappers.S new file mode 100644 index 0000000..167891b --- /dev/null +++ b/hwcore/irq_wrappers.S @@ -0,0 +1,305 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include "segment.h" + +.file "irq_wrappers.S" + +.text + +/** The address of the table of handlers (defined in irq.c) */ +.extern sos_irq_handler_array + +/** The address of the table of wrappers (defined below, and shared + with irq.c */ +.globl sos_irq_wrapper_array + +/** The variable holding the nested level of the IRQ handlers */ +.extern sos_irq_nested_level_counter + +/** Update the interrupted current thread's CPU context + Its prototype is: + sos_thread_prepare_irq_servicing(struct sos_cpu_state *); +*/ +.extern sos_thread_prepare_irq_servicing + +/** Update the kernel TSS in case we are switching to a thread in user + mode in order to come back into the correct kernel stack */ +.extern sos_cpu_context_update_kernel_tss + +/** Select a thread to set on CPU (this enables user-threads + preemption) and configure the MMU to match that of the destination + thread. + Its prototype is: + struct sos_cpu_state * // Selected CPU context + sos_thread_prepare_irq_switch_back(); +*/ +.extern sos_thread_prepare_irq_switch_back + +/* These pre-handlers are for IRQ (Master PIC) */ +.irp id, 0,1,2,3,4,5,6,7 + + .p2align 2, 0x90 + + sos_irq_wrapper_\id: + .type sos_irq_wrapper_\id,@function + + /* + * Backup the CPU context + */ + + /* Fake error code */ + pushl $0 + + /* Backup the actual context */ + pushl %ebp + movl %esp, %ebp + + pushl %edi + pushl %esi + pushl %edx + pushl %ecx + pushl %ebx + pushl %eax + subl $2,%esp + pushw %ss + pushw %ds + pushw %es + pushw %fs + pushw %gs + + /* Set correct kernel segment descriptors' value */ + movw $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_KDATA), %di + pushw %di ; popw %ds + pushw %di ; popw %es + pushw %di ; popw %fs + pushw %di ; popw %gs + + /* + * Increment IRQ nested level + */ + incl sos_irq_nested_level_counter + /* Outermost IRQ only: store the interrupted context + of the current thread */ + cmpl $1, sos_irq_nested_level_counter + jne 1f + pushl %esp + call sos_thread_prepare_irq_servicing + addl $4, %esp + + 1: + + /* Send EOI to PIC. See Intel 8259 datasheet + available on Kos website */ + movb $0x20, %al + outb %al, $0x20 + + /* + * Call the handler with IRQ number as argument + */ + pushl $\id + leal sos_irq_handler_array,%edi + call *\id*4(%edi) + addl $4, %esp + + /* + * Decrement IRQ nested level + */ + cli /* Just in case we messed up everything in the handler */ + subl $1, sos_irq_nested_level_counter + + /* sos_irq_nested_level_counter went below 0 ?! */ + jnc 2f + + 1: /* Yes: Print fatal error message */ + pushl $msg_nested_level_overflow + call sos_display_fatal_error + addl $4, %esp ; jmp 1b + /* Never returns */ + + 2: /* No: all right ! */ + + /* Was this the outermost IRQ handler ? */ + jnz 3f + + /* Yes: reschedule */ + call sos_thread_prepare_irq_switch_back + /* Establish new context: context switch ! */ + movl %eax, %esp + + /* Prepare kernel TSS in case we are switching to a + user thread: we make sure that we will come back + into the kernel at a correct stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + 3: + /* Restore the context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + + /* Remove fake error code */ + addl $4, %esp + + iret + .endr + + +/* These pre-handlers are for IRQ (Slave PIC) */ +.irp id, 8,9,10,11,12,13,14,15 + + .p2align 2, 0x90 + + sos_irq_wrapper_\id: + .type sos_irq_wrapper_\id,@function + + /* + * Backup the CPU context + */ + + /* Fake error code */ + pushl $0 + + /* Backup the actual context */ + pushl %ebp + movl %esp, %ebp + + pushl %edi + pushl %esi + pushl %edx + pushl %ecx + pushl %ebx + pushl %eax + subl $2,%esp + pushw %ss + pushw %ds + pushw %es + pushw %fs + pushw %gs + + /* Set correct kernel segment descriptors' value */ + movw $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_KDATA), %di + pushw %di ; popw %ds + pushw %di ; popw %es + pushw %di ; popw %fs + pushw %di ; popw %gs + + /* + * Increment IRQ nested level + */ + incl sos_irq_nested_level_counter + /* Outermost IRQ only: store the interrupted context + of the current thread */ + cmpl $1, sos_irq_nested_level_counter + jne 1f + pushl %esp + call sos_thread_prepare_irq_servicing + addl $4, %esp + + 1: + + /* Send EOI to PIC. See Intel 8259 datasheet + available on Kos website */ + movb $0x20, %al + outb %al, $0xa0 + outb %al, $0x20 + + /* + * Call the handler with IRQ number as argument + */ + pushl $\id + leal sos_irq_handler_array,%edi + call *\id*4(%edi) + addl $4, %esp + + /* + * Decrement IRQ nested level + */ + cli /* Just in case we messed up everything in the handler */ + subl $1, sos_irq_nested_level_counter + + /* sos_irq_nested_level_counter went below 0 ?! */ + jnc 2f + + 1: /* Yes: Print fatal error message */ + pushl $msg_nested_level_overflow + call sos_display_fatal_error + addl $4, %esp ; jmp 1b + /* Never returns */ + + 2: /* No: all right ! */ + + /* Was this the outermost IRQ handler ? */ + jnz 3f + + /* Yes: reschedule */ + call sos_thread_prepare_irq_switch_back + /* Establish new context: context switch ! */ + movl %eax, %esp + + /* Prepare kernel TSS in case we are switching to a + user thread: we make sure that we will come back + into the kernel at a correct stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + 3: + /* Restore the context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + popl %ebp + + /* Remove fake error code */ + addl $4, %esp + + iret + .endr + +.section ".rodata" +msg_nested_level_overflow: + .string "irq_wrappers.S: IRQ Nested level overflow ! System halted." + +/* Build the sos_irq_wrapper_array, shared with irq.c */ +.p2align 5, 0x0 +sos_irq_wrapper_array: + .irp id, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 + .long (sos_irq_wrapper_\id) + .endr diff --git a/hwcore/mm_context.c b/hwcore/mm_context.c new file mode 100644 index 0000000..6fb8d12 --- /dev/null +++ b/hwcore/mm_context.c @@ -0,0 +1,321 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mm_context.h" + + +/** + * Definition of an MMU context. + */ +struct sos_mm_context +{ + /** Physical address of the PD for this MMU context */ + sos_paddr_t paddr_PD; + + /** Virtual address where it is mapped into the Kernel space */ + sos_vaddr_t vaddr_PD; + + /** Reference counter for this mm_context */ + sos_ui32_t ref_cnt; + + /** List of MMU contexts in the system */ + struct sos_mm_context *prev, *next; +}; + + +/** + * The cache of mm_context structures + */ +struct sos_kslab_cache * cache_struct_mm_context; + + +/** + * The current MMU context corresponding to the current configuration + * of the MMU. + */ +static struct sos_mm_context *current_mm_context = NULL; + + +/** + * System-wide list of all the mm_contexts in the system + */ +static struct sos_mm_context *list_mm_context = NULL; +/* The "= NULL" here is FUNDAMENTAL, because paging.c must work + correctly, ie synch_PDE below must behave reasonably (eg do + nothing), until the mm_context subsystem has been initialized. */ + + +sos_ret_t sos_mm_context_subsystem_setup() +{ + struct sos_mm_context * initial_mm_context; + sos_ret_t retval; + + /* Create the new mm_context cache */ + cache_struct_mm_context = sos_kmem_cache_create("struct mm_context", + sizeof(struct sos_mm_context), + 1, 0, + SOS_KSLAB_CREATE_MAP); + if (NULL == cache_struct_mm_context) + return -SOS_ENOMEM; + + /* + * Allocate the initial mm_context structure + */ + initial_mm_context + = (struct sos_mm_context*) sos_kmem_cache_alloc(cache_struct_mm_context, + SOS_KSLAB_ALLOC_ATOMIC); + if (NULL == initial_mm_context) + return -SOS_ENOMEM; + + /* Retrieve the address of the current page where the PD lies */ + initial_mm_context->paddr_PD = sos_paging_get_current_PD_paddr(); + + /* + * Map it somewhere in kernel virtual memory + */ + + /* Allocate 1 page of kernel Virtual memory */ + initial_mm_context->vaddr_PD = sos_kmem_vmm_alloc(1, 0); + if (initial_mm_context->vaddr_PD == 0) + return -SOS_ENOMEM; + + /* Map the PD at this virtual address: it will thus be mapped 2 + times (1 time for the mirroring, 1 time for mm_context) ! */ + retval = sos_paging_map(initial_mm_context->paddr_PD, + initial_mm_context->vaddr_PD, + FALSE, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE); + if (SOS_OK != retval) + return retval; + + /* Initialize the initial list of mm_contexts */ + list_singleton(list_mm_context, initial_mm_context); + + /* We just created this mm_context: mark it as "referenced" */ + initial_mm_context->ref_cnt ++; + + /* We are actually already using it ! */ + initial_mm_context->ref_cnt ++; /* ie reference it a 2nd time ! */ + current_mm_context = initial_mm_context; + + return SOS_OK; +} + + +struct sos_mm_context * sos_mm_context_create(void) +{ + sos_ui32_t flags; + struct sos_mm_context *mmctxt; + + /* + * Allocate the initial mm_context structure + */ + mmctxt = (struct sos_mm_context*) sos_kmem_cache_alloc(cache_struct_mm_context, 0); + if (NULL == mmctxt) + return NULL; + + /* Allocate a new page for the new PD and map it into the kernel */ + mmctxt->vaddr_PD = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if (mmctxt->vaddr_PD == 0) + { + sos_kmem_cache_free((sos_vaddr_t) mmctxt); + return NULL; + } + + /* Retrieve its physical address */ + mmctxt->paddr_PD = sos_paging_get_paddr(mmctxt->vaddr_PD); + if (mmctxt->paddr_PD == 0) + { + sos_kmem_cache_free((sos_vaddr_t) mmctxt->vaddr_PD); + sos_kmem_cache_free((sos_vaddr_t) mmctxt); + return NULL; + } + + /* Copy the current hardware MMU address translation tables */ + if (SOS_OK != sos_paging_copy_kernel_space(mmctxt->vaddr_PD, + current_mm_context->vaddr_PD)) + { + sos_kmem_cache_free((sos_vaddr_t) mmctxt->vaddr_PD); + sos_kmem_cache_free((sos_vaddr_t) mmctxt); + return NULL; + } + + /* Mark the mm_context as "referenced" */ + mmctxt->ref_cnt = 1; + + /* Add it to the list of MMU contexts */ + sos_disable_IRQs(flags); + list_add_tail(list_mm_context, mmctxt); + sos_restore_IRQs(flags); + + return mmctxt; +} + + +struct sos_mm_context * +sos_mm_context_duplicate(const struct sos_mm_context *model) +{ + struct sos_mm_context *mmctxt; + + /* Create an mm_context, the kernel space will be copied in it */ + mmctxt = sos_mm_context_create(); + if (NULL == mmctxt) + return NULL; + + /* Copy the user-space configuration of the MMU */ + if (SOS_OK != sos_paging_copy_user_space(mmctxt->vaddr_PD, + model->vaddr_PD)) + { + sos_mm_context_unref(mmctxt); + return NULL; + } + + return mmctxt; +} + + +sos_ret_t sos_mm_context_unref(struct sos_mm_context *mmctxt) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + + /* A valid mmctxt is one which is not yet unreferenced */ + SOS_ASSERT_FATAL(mmctxt->ref_cnt > 0); + + /* Unreference it */ + mmctxt->ref_cnt --; + + /* If somebody is still using it, don't release it now */ + if (mmctxt->ref_cnt > 0) + { + sos_restore_IRQs(flags); + return SOS_OK; + } + + /* If nobody uses it, then it cannot be the current mm_context ! */ + SOS_ASSERT_FATAL(mmctxt != current_mm_context); + + /* Remove it from the list of mm_contexts */ + list_delete(list_mm_context, mmctxt); + + sos_restore_IRQs(flags); + + /* Remove all user mappings (if any) */ + sos_paging_dispose(mmctxt->vaddr_PD); + + /* Unmap the PD from the kernel */ + sos_kmem_vmm_free(mmctxt->vaddr_PD); + + memset(mmctxt, 0x0, sizeof(*mmctxt)); + + return SOS_OK; +} + + +sos_ret_t sos_mm_context_ref(struct sos_mm_context *mmctxt) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + + /* A valid mmctxt is one which is not yet unreferenced */ + SOS_ASSERT_FATAL(mmctxt->ref_cnt > 0); + + /* Reference it once again */ + mmctxt->ref_cnt ++; + + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +sos_ret_t sos_mm_context_switch_to(struct sos_mm_context *mmctxt) +{ + SOS_ASSERT_FATAL(NULL != mmctxt); + SOS_ASSERT_FATAL(mmctxt->ref_cnt > 0); + SOS_ASSERT_FATAL(current_mm_context->ref_cnt > 0); + if (mmctxt != current_mm_context) + { + sos_ui32_t flags; + struct sos_mm_context * prev_mm_context = current_mm_context; + + /* This is the most dangerous part of the whole thing. If we set + the wrong MMU configuration in mmctxt, this will hang or + reboot the machine... */ + sos_paging_set_current_PD_paddr(mmctxt->paddr_PD); + + /* Exchange the mm_contexts */ + current_mm_context = mmctxt; + + /* Update the reference counts */ + sos_disable_IRQs(flags); + mmctxt->ref_cnt ++; + sos_mm_context_unref(prev_mm_context); + sos_restore_IRQs(flags); + } + + return SOS_OK; +} + + +struct sos_mm_context *get_current_mm_context(void) +{ + SOS_ASSERT_FATAL(current_mm_context->ref_cnt > 0); + return current_mm_context; +} + + +/* ****************************************************** + * Reserved functions + */ + + +sos_ret_t sos_mm_context_synch_kernel_PDE(unsigned int index_in_pd, + sos_ui32_t pde) +{ + sos_ui32_t flags; + struct sos_mm_context * dest_mm_context; + int nb_mm_contexts; + + sos_disable_IRQs(flags); + list_foreach_forward(list_mm_context, dest_mm_context, nb_mm_contexts) + { + sos_ui32_t * dest_pd; + + SOS_ASSERT_FATAL(dest_mm_context->ref_cnt > 0); + + dest_pd = (sos_ui32_t*) dest_mm_context->vaddr_PD; + dest_pd[index_in_pd] = pde; + } + sos_restore_IRQs(flags); + + return SOS_OK; +} diff --git a/hwcore/mm_context.h b/hwcore/mm_context.h new file mode 100644 index 0000000..03a9bcc --- /dev/null +++ b/hwcore/mm_context.h @@ -0,0 +1,113 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_MMCTXT_H_ +#define _SOS_MMCTXT_H_ + + +/** + * @file mm_context.h + * + * Low level API to manage multiple MMU translation tables. The API + * (not its implementation) should be some kind of + * architecture-independent. + * + * The goal of this API is: + * - To provide a simple arch-independent API to change the current + * address space configured in the MMU + * - To make sure that ALL the kernel space mappings are always kept + * IDENTICAL among all the address spaces in the whole system. That + * way, a virtual address in the kernel space refers to EXACTLY the + * same physical memory location for all the address spaces. + */ + +#include +#include + + +/** + * Declaration of an MMU context. Opaque structure defined in + * mm_context.c + */ +struct sos_mm_context; + + +/** + * Setup the MMU context management subsystem + */ +sos_ret_t sos_mm_context_subsystem_setup(void); + + +/** + * Public function to allocate a new MMU context. + * + * Allocate a new PD, map it into kernel virtual address space and + * initialize its PDE for the Kernel space so that the Kernel space is + * kept identical between ALL the MMU context in the system. + */ +struct sos_mm_context * sos_mm_context_create(void); + + +/** + * Public function to create a new MMU context, copy of another one. + * + * Allocate a new PD, map it into kernel virtual address space, + * initialize its PDE for the Kernel space so that the Kernel space is + * kept identical between ALL the MMU context in the system and copy + * the PDE/PT of the user space to match that of the model mm_context. + */ +struct sos_mm_context * +sos_mm_context_duplicate(const struct sos_mm_context *model); + + +/** + * Public function to release the given MMU context (based on + * reference counting). Only the virtual page that maps the PD of the + * MMU context is released. All the other user-space pages are + * expected to be already released by the parent process. + */ +sos_ret_t sos_mm_context_unref(struct sos_mm_context *mmctxt); + + +/** + * Reconfigure the MMU to use a different MMU context. mmctxt MUST + * NOT be NULL. + */ +sos_ret_t sos_mm_context_switch_to(struct sos_mm_context *mmctxt); + + +/* ****************************************************** + * Reserved functions + */ + + +/** + * Reserved function to increase the reference count of the given MMU + * context (based on reference counting). + */ +sos_ret_t sos_mm_context_ref(struct sos_mm_context *mmctxt); + + +/** + * Restricted function reserved to paging.c to synchronize all the PDEs + * accross all the MMU contexts in the system. This is limited to PDEs + * pertaining to the kernel space. + */ +sos_ret_t sos_mm_context_synch_kernel_PDE(unsigned int index_in_pd, + sos_ui32_t pde); + +#endif /* _SOS_MMCTXT_H_ */ diff --git a/hwcore/paging.c b/hwcore/paging.c new file mode 100644 index 0000000..1395495 --- /dev/null +++ b/hwcore/paging.c @@ -0,0 +1,1056 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include +#include + +#include "mm_context.h" + +#include "paging.h" + + +/* + * Important NOTICE concerning the use of the reference & occupation + * counters of the physical pages by the "paging" subsystem: + * - All the kernel PT are SHARED. This means that as soon as one + * kernel PT belongs to one mm_context, it belongs to ALL the + * mm_contexts. We don't update the real reference count of the PT + * in this respect, because it would require to update the + * reference counts of ALL the kernel PTs as soon as a new + * mm_context is created, or as soon as a mm_context is + * suppressed. This way, the reference count is constant + * independently of the actual number of PD really sharing them. + * - We do NOT maintain the occupation count of the PDs. This would add + * some little overhead that is useless + * - We do maintain the occupation count of ALL the PTs: it represents the + * number of PTE allocated in the PT + */ + + +/** The structure of a page directory entry. See Intel vol 3 section + 3.6.4 */ +struct x86_pde +{ + sos_ui32_t present :1; /* 1=PT mapped */ + sos_ui32_t write :1; /* 0=read-only, 1=read/write */ + sos_ui32_t user :1; /* 0=supervisor, 1=user */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t accessed :1; /* 1=read/write access since last clear */ + sos_ui32_t zero :1; /* Intel reserved */ + sos_ui32_t page_size :1; /* 0=4kB, 1=4MB or 2MB (depending on PAE) */ + sos_ui32_t global_page :1; /* Ignored (Intel reserved) */ + sos_ui32_t custom :3; /* Do what you want with them */ + sos_ui32_t pt_paddr :20; +} __attribute__ ((packed)); + + +/** Intermediate type to speed up PDE copy */ +typedef union { + struct x86_pde pde; + sos_ui32_t ui32; +} x86_pde_val_t; + + +/** The structure of a page table entry. See Intel vol 3 section + 3.6.4 */ +struct x86_pte +{ + sos_ui32_t present :1; /* 1=PT mapped */ + sos_ui32_t write :1; /* 0=read-only, 1=read/write */ + sos_ui32_t user :1; /* 0=supervisor, 1=user */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t accessed :1; /* 1=read/write access since last clear */ + sos_ui32_t dirty :1; /* 1=write access since last clear */ + sos_ui32_t zero :1; /* Intel reserved */ + sos_ui32_t global_page :1; /* 1=No TLB invalidation upon cr3 switch + (when PG set in cr4) */ + sos_ui32_t custom :3; /* Do what you want with them */ + sos_ui32_t paddr :20; +} __attribute__ ((packed)); + + +/** Intermediate type to speed up PTE copy */ +typedef union { + struct x86_pte pte; + sos_ui32_t ui32; +} x86_pte_val_t; + + +/** Structure of the x86 CR3 register: the Page Directory Base + Register. See Intel x86 doc Vol 3 section 2.5 */ +struct x86_pdbr +{ + sos_ui32_t zero1 :3; /* Intel reserved */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t zero2 :7; /* Intel reserved */ + sos_ui32_t pd_paddr :20; +} __attribute__ ((packed)); + + +/** + * Helper macro to control the MMU: invalidate the TLB entry for the + * page located at the given virtual address. See Intel x86 vol 3 + * section 3.7. + */ +#define invlpg(vaddr) \ + do { \ + __asm__ __volatile__("invlpg %0"::"m"(*((unsigned *)(vaddr)))); \ + } while(0) + + +/** + * Helper macro to control the MMU: invalidate the whole TLB. See + * Intel x86 vol 3 section 3.7. + */ +#define flush_tlb() \ + do { \ + unsigned long tmpreg; \ + asm volatile("movl %%cr3,%0\n\tmovl %0,%%cr3" :"=r" \ + (tmpreg) : :"memory"); \ + } while (0) + + +/** + * Helper macro to compute the index in the PD for the given virtual + * address + */ +#define virt_to_pd_index(vaddr) \ + (((unsigned)(vaddr)) >> 22) + + +/** + * Helper macro to compute the index in the PT for the given virtual + * address + */ +#define virt_to_pt_index(vaddr) \ + ( (((unsigned)(vaddr)) >> 12) & 0x3ff ) + + +/** + * Helper macro to compute the offset in the page for the given virtual + * address + */ +#define virt_to_page_offset(vaddr) \ + (((unsigned)(vaddr)) & SOS_PAGE_MASK) + + +/** + * Helper function to map a page in the pd.\ Suppose that the RAM + * is identity mapped to resolve PT actual (CPU) address from the PD + * entry + */ +static sos_ret_t paging_setup_map_helper(struct x86_pde * pd, + sos_paddr_t ppage, + sos_vaddr_t vaddr) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Make sure the page table was mapped */ + struct x86_pte * pt; + if (pd[index_in_pd].present) + { + pt = (struct x86_pte*) (pd[index_in_pd].pt_paddr << 12); + + /* This test will always be TRUE here, since the setup routine + scans the kernel pages in a strictly increasing order: at + each step, the map will result in the allocation of a new PT + entry. For the sake of clarity, we keep the test here. */ + if (pt[index_in_pt].present) + SOS_ASSERT_FATAL(FALSE); /* indicate a fatal error */ + } + else + { + /* No : allocate a new one */ + pt = (struct x86_pte*) sos_physmem_ref_physpage_new(FALSE); + if (! pt) + return -SOS_ENOMEM; + + memset((void*)pt, 0x0, SOS_PAGE_SIZE); + + pd[index_in_pd].present = TRUE; + pd[index_in_pd].write = 1; /* It would be too complicated to + determine whether it + corresponds to a real R/W area + of the kernel code/data or + read-only */ + pd[index_in_pd].pt_paddr = ((sos_paddr_t)pt) >> 12; + } + + + /* Map the page in the page table */ + pt[index_in_pt].present = 1; + pt[index_in_pt].write = 1; /* It would be too complicated to + determine whether it corresponds to + a real R/W area of the kernel + code/data or R/O only */ + pt[index_in_pt].user = 0; + pt[index_in_pt].paddr = ppage >> 12; + + /* Increase the PT's occupation count because we allocated a new PTE + inside it */ + sos_physmem_inc_physpage_occupation((sos_paddr_t)pt); + + return SOS_OK; +} + + +sos_ret_t sos_paging_subsystem_setup(sos_paddr_t identity_mapping_base, + sos_paddr_t identity_mapping_top) +{ + /* The PDBR we will setup below */ + struct x86_pdbr cr3; + + /* Get the PD for the kernel */ + struct x86_pde * pd + = (struct x86_pde*) sos_physmem_ref_physpage_new(FALSE); + + /* The iterator for scanning the kernel area */ + sos_paddr_t paddr; + + /* Reset the PD. For the moment, there is still an IM for the whole + RAM, so that the paddr are also vaddr */ + memset((void*)pd, + 0x0, + SOS_PAGE_SIZE); + + /* Identity-map the identity_mapping_* area */ + for (paddr = identity_mapping_base ; + paddr < identity_mapping_top ; + paddr += SOS_PAGE_SIZE) + { + if (paging_setup_map_helper(pd, paddr, paddr)) + return -SOS_ENOMEM; + } + + /* Identity-map the PC-specific BIOS/Video area */ + for (paddr = BIOS_N_VIDEO_START ; + paddr < BIOS_N_VIDEO_END ; + paddr += SOS_PAGE_SIZE) + { + if (paging_setup_map_helper(pd, paddr, paddr)) + return -SOS_ENOMEM; + } + + /* Ok, kernel is now identity mapped in the PD. We still have to set + up the mirroring */ + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].present = TRUE; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].write = 1; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].user = 0; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].pt_paddr + = ((sos_paddr_t)pd)>>12; + + /* We now just have to configure the MMU to use our PD. See Intel + x86 doc vol 3, section 3.6.3 */ + memset(& cr3, 0x0, sizeof(struct x86_pdbr)); /* Reset the PDBR */ + cr3.pd_paddr = ((sos_paddr_t)pd) >> 12; + + /* Actual loading of the PDBR in the MMU: setup cr3 + bits 31[Paging + Enabled] and 16[Write Protect] of cr0, see Intel x86 doc vol 3, + sections 2.5, 3.6.1 and 4.11.3 + note table 4-2 */ + asm volatile ("movl %0,%%cr3\n\t" + "movl %%cr0,%%eax\n\t" + "orl $0x80010000, %%eax\n\t" /* bit 31 | bit 16 */ + "movl %%eax,%%cr0\n\t" + "jmp 1f\n\t" + "1:\n\t" + "movl $2f, %%eax\n\t" + "jmp *%%eax\n\t" + "2:\n\t" ::"r"(cr3):"memory","eax"); + + /* + * Here, the only memory available is: + * - The BIOS+video area + * - the identity_mapping_base .. identity_mapping_top area + * - the PD mirroring area (4M) + * All accesses to other virtual addresses will generate a #PF + */ + + return SOS_OK; +} + + +/* Suppose that the current address is configured with the mirroring + * enabled to access the PD and PT. */ +sos_ret_t sos_paging_map(sos_paddr_t ppage_paddr, + sos_vaddr_t vpage_vaddr, + sos_bool_t is_user_page, + sos_ui32_t flags) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vpage_vaddr); + unsigned index_in_pt = virt_to_pt_index(vpage_vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(ppage_paddr)); + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(vpage_vaddr)); + + /* EXEC permission ignored on x86 */ + flags &= ~SOS_VM_MAP_PROT_EXEC; + + /* The mapping of anywhere in the PD mirroring is FORBIDDEN ;) */ + if ((vpage_vaddr >= SOS_PAGING_MIRROR_VADDR) + && (vpage_vaddr < SOS_PAGING_MIRROR_VADDR + SOS_PAGING_MIRROR_SIZE)) + return -SOS_EINVAL; + + /* Map a page for the PT if necessary */ + if (! pd[index_in_pd].present) + { + x86_pde_val_t u; + + /* No : allocate a new one */ + sos_paddr_t pt_ppage + = sos_physmem_ref_physpage_new(! (flags & SOS_VM_MAP_ATOMIC)); + if (! pt_ppage) + { + return -SOS_ENOMEM; + } + + /* Prepare the value of the PDE */ + u.pde = (struct x86_pde){ + .present = TRUE, + .write = 1, + .pt_paddr = ((sos_paddr_t)pt_ppage) >> 12 + }; + + /* Is it a PDE concerning the kernel space */ + if (vpage_vaddr < SOS_PAGING_MIRROR_VADDR) + { + /* Yes: So we need to update the PDE of ALL the mm_contexts + in the system */ + + /* First of all: this is a kernel PT */ + u.pde.user = 0; + + /* Now synchronize all the PD */ + SOS_ASSERT_FATAL(SOS_OK == + sos_mm_context_synch_kernel_PDE(index_in_pd, + u.ui32)); + } + else /* We should have written "else if (vpage_vaddr >= + SOS_PAGING_BASE_USER_ADDRESS)" but this is not needed + because the beginning of the function detects and + rejects mapping requests inside the mirroring */ + { + /* No: The request concerns the user space. So only the + current MMU context is concerned */ + + /* First of all: this is a user PT */ + u.pde.user = 1; + + /* Now update the current PD */ + pd[index_in_pd] = u.pde; + } + + /* + * The PT is now mapped in the PD mirroring + */ + + /* Invalidate TLB for the page we just added */ + invlpg(pt); + + /* Reset this new PT */ + memset((void*)pt, 0x0, SOS_PAGE_SIZE); + } + + /* If we allocate a new entry in the PT, increase its occupation + count. */ + if (! pt[index_in_pt].present) + sos_physmem_inc_physpage_occupation(pd[index_in_pd].pt_paddr << 12); + + /* Otherwise, that means that a physical page is implicitely + unmapped */ + else + sos_physmem_unref_physpage(pt[index_in_pt].paddr << 12); + + /* Map the page in the page table */ + pt[index_in_pt].present = TRUE; + pt[index_in_pt].write = (flags & SOS_VM_MAP_PROT_WRITE)?1:0; + pt[index_in_pt].user = (is_user_page)?1:0; + pt[index_in_pt].paddr = ppage_paddr >> 12; + sos_physmem_ref_physpage_at(ppage_paddr); + + + /* + * The page is now mapped in the current address space + */ + + /* Invalidate TLB for the page we just added */ + invlpg(vpage_vaddr); + + return SOS_OK; +} + + +sos_ret_t sos_paging_unmap(sos_vaddr_t vpage_vaddr) +{ + sos_ret_t pt_dec_occupation_retval; + + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vpage_vaddr); + unsigned index_in_pt = virt_to_pt_index(vpage_vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(vpage_vaddr)); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return -SOS_EINVAL; + if (! pt[index_in_pt].present) + return -SOS_EINVAL; + + /* The unmapping of anywhere in the PD mirroring is FORBIDDEN ;) */ + if ((vpage_vaddr >= SOS_PAGING_MIRROR_VADDR) + && (vpage_vaddr < SOS_PAGING_MIRROR_VADDR + SOS_PAGING_MIRROR_SIZE)) + return -SOS_EINVAL; + + /* Reclaim the physical page */ + sos_physmem_unref_physpage(pt[index_in_pt].paddr << 12); + + /* Unmap the page in the page table */ + memset(pt + index_in_pt, 0x0, sizeof(struct x86_pte)); + + /* Invalidate TLB for the page we just unmapped */ + invlpg(vpage_vaddr); + + /* Reclaim this entry in the PT, which may free the PT */ + pt_dec_occupation_retval + = sos_physmem_dec_physpage_occupation(pd[index_in_pd].pt_paddr << 12); + SOS_ASSERT_FATAL(pt_dec_occupation_retval >= 0); + if (pt_dec_occupation_retval > 0) + /* If the PT is now completely unused... */ + { + x86_pde_val_t u; + + + /* + * The PT is not referenced by this PD anymore + */ + sos_physmem_unref_physpage(pd[index_in_pd].pt_paddr << 12); + + + /* + * Reset the PDE + */ + + /* Mark the PDE as unavailable */ + u.ui32 = 0; + + /* Is it a PDE concerning the kernel space */ + if (vpage_vaddr < SOS_PAGING_MIRROR_VADDR) + { + /* Now synchronize all the PD */ + SOS_ASSERT_FATAL(SOS_OK == + sos_mm_context_synch_kernel_PDE(index_in_pd, + u.ui32)); + } + else /* We should have written "else if (vpage_vaddr >= + SOS_PAGING_BASE_USER_ADDRESS)" but this is not needed + because the beginning of the function detects and + rejects mapping requests inside the mirroring */ + { + /* No: The request concerns the user space. So only the + current MMU context is concerned */ + pd[index_in_pd] = u.pde; + } + + /* Update the TLB */ + invlpg(pt); + } + + return SOS_OK; +} + + +sos_ret_t sos_paging_unmap_interval(sos_vaddr_t vaddr, + sos_size_t size) +{ + sos_ret_t retval = 0; + + if (! SOS_IS_PAGE_ALIGNED(vaddr)) + return -SOS_EINVAL; + if (! SOS_IS_PAGE_ALIGNED(size)) + return -SOS_EINVAL; + + for ( ; + size >= SOS_PAGE_SIZE ; + vaddr += SOS_PAGE_SIZE, size -= SOS_PAGE_SIZE) + if (SOS_OK == sos_paging_unmap(vaddr)) + retval += SOS_PAGE_SIZE; + + return retval; +} + + +sos_ui32_t sos_paging_get_prot(sos_vaddr_t vaddr) +{ + sos_ui32_t retval; + + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return SOS_VM_MAP_PROT_NONE; + if (! pt[index_in_pt].present) + return SOS_VM_MAP_PROT_NONE; + + /* Default access right of an available page is "read" on x86 */ + retval = SOS_VM_MAP_PROT_READ; + if (pd[index_in_pd].write && pt[index_in_pt].write) + retval |= SOS_VM_MAP_PROT_WRITE; + + return retval; +} + + +sos_ret_t sos_paging_set_prot(sos_vaddr_t vaddr, + sos_ui32_t new_prot) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* EXEC permission ignored on x86 */ + new_prot &= ~SOS_VM_MAP_PROT_EXEC; + + /* Check flags */ + if (new_prot & ~(SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE)) + return -SOS_EINVAL; + if (! (new_prot & SOS_VM_MAP_PROT_READ)) + /* x86 READ flag always set by default */ + return -SOS_ENOSUP; + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return -SOS_EINVAL; + if (! pt[index_in_pt].present) + return -SOS_EINVAL; + + /* Update access rights */ + pt[index_in_pt].write = ((new_prot & SOS_VM_MAP_PROT_WRITE) != 0); + invlpg(vaddr); + + return SOS_OK; +} + + +sos_ret_t sos_paging_set_prot_of_interval(sos_vaddr_t vaddr, + sos_size_t size, + sos_ui32_t new_prot) +{ + if (! SOS_IS_PAGE_ALIGNED(vaddr)) + return -SOS_EINVAL; + if (! SOS_IS_PAGE_ALIGNED(size)) + return -SOS_EINVAL; + + for ( ; size >= SOS_PAGE_SIZE ; vaddr += SOS_PAGE_SIZE, size -= SOS_PAGE_SIZE) + sos_paging_set_prot(vaddr, new_prot); + + return SOS_OK; +} + + +sos_bool_t sos_paging_is_dirty(sos_vaddr_t vaddr) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return FALSE; + if (! pt[index_in_pt].present) + return FALSE; + + return (pt[index_in_pt].dirty != 0); +} + + +sos_ret_t sos_paging_set_dirty(sos_vaddr_t vaddr, + sos_bool_t is_dirty) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return -SOS_EFAULT; + if (! pt[index_in_pt].present) + return -SOS_EFAULT; + + pt[index_in_pt].dirty = is_dirty; + return SOS_OK; +} + + +sos_paddr_t sos_paging_get_paddr(sos_vaddr_t vaddr) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + unsigned offset_in_page = virt_to_page_offset(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return (sos_paddr_t)NULL; + if (! pt[index_in_pt].present) + return (sos_paddr_t)NULL; + + return (pt[index_in_pt].paddr << 12) + offset_in_page; +} + + +/* ************************************************* + * Functions restricted to mm_context module + */ + + +sos_paddr_t sos_paging_get_current_PD_paddr(void) +{ + struct x86_pdbr pdbr; + asm volatile("movl %%cr3, %0\n": "=r"(pdbr)); + return (pdbr.pd_paddr << 12); +} + + +sos_ret_t sos_paging_set_current_PD_paddr(sos_paddr_t paddr_PD) +{ + struct x86_pdbr pdbr; + + SOS_ASSERT_FATAL(paddr_PD != 0); + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(paddr_PD)); + + /* Setup the value of the PDBR */ + memset(& pdbr, 0x0, sizeof(struct x86_pdbr)); /* Reset the PDBR */ + pdbr.pd_paddr = (paddr_PD >> 12); + + /* Configure the MMU according to the PDBR */ + asm volatile ("movl %0,%%cr3\n" ::"r"(pdbr)); + + return SOS_OK; +} + + +sos_ret_t sos_paging_dispose(sos_vaddr_t vaddr_PD) +{ + x86_pde_val_t *pd = (x86_pde_val_t*) vaddr_PD; + x86_pte_val_t *pt; + int index_in_pd; + + /* Allocate 1 page in kernel space to map the PTs in order to + unreference the physical pages they reference */ + pt = (x86_pte_val_t *)sos_kmem_vmm_alloc(1, 0); + if (! pt) + return -SOS_ENOMEM; + + /* (Nothing to do in kernel space) */ + + /* Reset all the PTs in user space */ + for (index_in_pd = (SOS_PAGING_BASE_USER_ADDRESS >> 22) ; + index_in_pd < 1024 ; /* 1 PDE = 1 PT + = 1024 Pages + = 4MB */ + index_in_pd ++) + { + sos_paddr_t paddr_pt = (pd[index_in_pd].pde.pt_paddr << 12); + int index_in_pt; + + /* Nothing to do if there is no PT */ + if (! pd[index_in_pd].pde.present) + { + pd[index_in_pd].ui32 = 0; + continue; + } + + /* Map this PT inside kernel */ + SOS_ASSERT_FATAL(SOS_OK + == sos_paging_map(paddr_pt, + (sos_vaddr_t)pt, FALSE, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE)); + + /* Reset all the mappings in this PT */ + for (index_in_pt = 0 ; index_in_pt < 1024 ; index_in_pt ++) + { + /* Ignore unmapped PTE */ + if (! pt[index_in_pt].pte.present) + { + pt[index_in_pt].ui32 = 0; + continue; + } + + /* Unreference the associated page */ + sos_physmem_unref_physpage(pt[index_in_pt].pte.paddr << 12); + + /* Decrease occupation count of the PT */ + sos_physmem_dec_physpage_occupation(paddr_pt); + + /* Reset PTE */ + pt[index_in_pt].ui32 = 0; + } + + /* Unmap PT */ + SOS_ASSERT_FATAL(SOS_OK == sos_paging_unmap((sos_vaddr_t)pt)); + + /* Reset PDE */ + pd[index_in_pd].ui32 = 0; + + /* Unreference PT */ + sos_physmem_unref_physpage(paddr_pt); + } + + /* Unallocate kernel space used for the temporary PT */ + SOS_ASSERT_FATAL(SOS_OK == sos_kmem_vmm_free((sos_vaddr_t)pt)); + + return SOS_OK; +} + + +sos_ret_t sos_paging_copy_kernel_space(sos_vaddr_t dest_vaddr_PD, + sos_vaddr_t src_vaddr_PD) +{ + x86_pde_val_t *src_pd = (x86_pde_val_t*) src_vaddr_PD; + x86_pde_val_t *dest_pd = (x86_pde_val_t*) dest_vaddr_PD; + sos_paddr_t dest_paddr_PD = sos_paging_get_paddr(dest_vaddr_PD); + x86_pde_val_t mirror_pde; + int index_in_pd; + + /* Fill destination PD with zeros */ + memset((void*)dest_vaddr_PD, 0x0, SOS_PAGE_SIZE); + + /* Synchronize it with the master Kernel MMU context. Stop just + before the mirroring ! */ + for (index_in_pd = 0 ; + index_in_pd < (SOS_PAGING_MIRROR_VADDR >> 22) ; /* 1 PDE = 1 PT + = 1024 Pages + = 4MB */ + index_in_pd ++) + { + /* Copy the master's configuration */ + dest_pd[index_in_pd].ui32 = src_pd[index_in_pd].ui32; + + /* We DON'T mark the underlying PT and pages as referenced + because all the PD are equivalent in the kernel space: as + soon as a page is mapped in the kernel, it is mapped by X + address spaces, and as soon as it is unmapped by 1 address + space, it is unmapped in all the others. So that for X + address spaces, the reference counter will be either 0 or X, + and not something else: using the reference counter correctly + won't be of any use and would consume some time in updating it. */ + } + + /* Setup the mirroring for the new address space */ + mirror_pde.ui32 = 0; + mirror_pde.pde.present = TRUE; + mirror_pde.pde.write = 1; + mirror_pde.pde.user = 0; /* This is a KERNEL PDE */ + mirror_pde.pde.pt_paddr = (dest_paddr_PD >> 12); + dest_pd[SOS_PAGING_MIRROR_VADDR >> 22].ui32 = mirror_pde.ui32; + + return SOS_OK; +} + + +sos_ret_t sos_paging_copy_user_space(sos_vaddr_t dest_vaddr_PD, + sos_vaddr_t src_vaddr_PD) +{ + x86_pde_val_t *src_pd = (x86_pde_val_t*) src_vaddr_PD; + x86_pde_val_t *dest_pd = (x86_pde_val_t*) dest_vaddr_PD; + x86_pte_val_t *tmp_src_pt, *tmp_dest_pt; + int index_in_pd; + + /* Allocate 2 pages in kernel space to map the PT in order to + perform the copy of the PTs from source to destination */ + tmp_src_pt = (x86_pte_val_t *)sos_kmem_vmm_alloc(1, 0); + if (! tmp_src_pt) + return -SOS_ENOMEM; + + tmp_dest_pt = (x86_pte_val_t *)sos_kmem_vmm_alloc(1, 0); + if (! tmp_dest_pt) + { + sos_kmem_vmm_free((sos_vaddr_t)tmp_dest_pt); + return -SOS_ENOMEM; + } + + /* Copy each used PT from source to destination */ + for (index_in_pd = (SOS_PAGING_BASE_USER_ADDRESS >> 22) ; + index_in_pd < 1024 ; /* 1 PDE = 1 PT + = 1024 Pages + = 4MB */ + index_in_pd ++) + { + sos_paddr_t paddr_dest_pt; + int index_in_pt; + + /* We first litterally copy the source PDE in the destination + PDE. However, please bare in mind that, in the end, both + won't reference the same physical PT: the destination PDE + will be updated (below) to match the address of its own new + PT */ + dest_pd[index_in_pd].ui32 = src_pd[index_in_pd].ui32; + + /* Ignore unused PTs */ + if (! src_pd[index_in_pd].pde.present) + continue; + + /* Allocate the destination PT */ + paddr_dest_pt = sos_physmem_ref_physpage_new(TRUE); + if (NULL == (void*)paddr_dest_pt) + { + sos_paging_dispose((sos_vaddr_t)dest_vaddr_PD); + + /* Unallocate temporary kernel space used for the copy */ + sos_kmem_vmm_free((sos_vaddr_t)tmp_src_pt); + sos_kmem_vmm_free((sos_vaddr_t)tmp_dest_pt); + return -SOS_ENOMEM; + } + + /* Map source and destination PT */ + SOS_ASSERT_FATAL(SOS_OK + == sos_paging_map(src_pd[index_in_pd].pde.pt_paddr << 12, + (sos_vaddr_t)tmp_src_pt, FALSE, + SOS_VM_MAP_PROT_READ)); + SOS_ASSERT_FATAL(SOS_OK + == sos_paging_map(paddr_dest_pt, + (sos_vaddr_t)tmp_dest_pt, FALSE, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE)); + + /* Copy the contents of the source to the destination PT, + updating the reference counts of the pages */ + for (index_in_pt = 0 ; index_in_pt < 1024 ; index_in_pt ++) + { + /* Copy the source PTE */ + tmp_dest_pt[index_in_pt].ui32 = tmp_src_pt[index_in_pt].ui32; + + /* Ignore non-present pages */ + if (! tmp_dest_pt[index_in_pt].pte.present) + continue; + + /* Reset the dirty/accessed flags */ + tmp_dest_pt[index_in_pt].pte.accessed = 0; + tmp_dest_pt[index_in_pt].pte.dirty = 0; + + /* Increase the reference count of the destination page */ + sos_physmem_ref_physpage_at(tmp_src_pt[index_in_pt].pte.paddr << 12); + + /* Increase occupation count of the PT */ + sos_physmem_inc_physpage_occupation(paddr_dest_pt); + } + + /* Unmap the temporary PTs */ + SOS_ASSERT_FATAL(SOS_OK == sos_paging_unmap((sos_vaddr_t)tmp_src_pt)); + SOS_ASSERT_FATAL(SOS_OK == sos_paging_unmap((sos_vaddr_t)tmp_dest_pt)); + + /* Update the destination PDE */ + dest_pd[index_in_pd].pde.pt_paddr = (paddr_dest_pt >> 12); + + /* Reset the dirty/accessed flags */ + dest_pd[index_in_pd].pde.accessed = 0; + } + + + /* Unallocate temporary kernel space used for the copy */ + SOS_ASSERT_FATAL(SOS_OK == sos_kmem_vmm_free((sos_vaddr_t)tmp_src_pt)); + SOS_ASSERT_FATAL(SOS_OK == sos_kmem_vmm_free((sos_vaddr_t)tmp_dest_pt)); + + return SOS_OK; +} + + +sos_ret_t sos_paging_prepare_COW(sos_uaddr_t base_address, + sos_size_t length) +{ + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(base_address)); + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(length)); + SOS_ASSERT_FATAL(SOS_PAGING_IS_USER_AREA(base_address, length)); + + /* Mark all the pages read-only, when already mapped in physical + memory */ + for ( ; + length > 0 ; + length -= SOS_PAGE_SIZE, base_address += SOS_PAGE_SIZE) + { + sos_paging_set_prot(base_address, + SOS_VM_MAP_PROT_READ); + } + + return SOS_OK; +} + + +sos_ret_t sos_paging_try_resolve_COW(sos_uaddr_t uaddr) +{ + sos_ret_t refcnt; + + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(uaddr); + unsigned index_in_pt = virt_to_pt_index(uaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return -SOS_EFAULT; + if (! pt[index_in_pt].present) + return -SOS_EFAULT; + + /* Read-only PT not supported by kernel ! */ + if (! pd[index_in_pd].write) + return -SOS_EFAULT; + + /* Cannot understand a COW request if the page is already + read/write */ + SOS_ASSERT_FATAL(! pt[index_in_pt].write); + + /* We do a private copy of the page only if the current mapped page + is shared by more than 1 process */ + refcnt = sos_physmem_get_physpage_refcount(pt[index_in_pt].paddr << 12); + SOS_ASSERT_FATAL(refcnt > 0); + + if (refcnt == 1) + { + /* We are the only address space to reference this page, we can + safely turn it read/write now */ + pt[index_in_pt].write = 1; + invlpg(pt[index_in_pt].paddr << 12); + } + + /* Otherwise we need to make a private copy of the page */ + else + { + sos_paddr_t new_ppage; + sos_vaddr_t vpage_src, tmp_dest; + + /* For that, we allocate the destination page inside the kernel + space to perform the copy. We will transfer it into its + final user-space address later */ + tmp_dest = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if (! tmp_dest) + return -SOS_ENOMEM; + + /* copy the contents of the page */ + vpage_src = SOS_PAGE_ALIGN_INF(uaddr); + memcpy((void*)tmp_dest, (void*)vpage_src, SOS_PAGE_SIZE); + + /* replace the original (read-only) mapping with a (read/write) + mapping to the new page. This will automatically unreference + the original page */ + new_ppage = sos_paging_get_paddr(tmp_dest); + SOS_ASSERT_FATAL(new_ppage != (sos_vaddr_t)NULL); + if (SOS_OK != sos_paging_map(new_ppage, vpage_src, + TRUE, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE)) + { + sos_kmem_vmm_free(tmp_dest); + return -SOS_ENOMEM; + } + + /* We can now unmap the destination page from inside the + kernel and free the kernel VM range for it */ + SOS_ASSERT_FATAL(SOS_OK == sos_kmem_vmm_free(tmp_dest)); + } + + /* That's all, folks ! */ + return SOS_OK; +} diff --git a/hwcore/paging.h b/hwcore/paging.h new file mode 100644 index 0000000..3c0b00c --- /dev/null +++ b/hwcore/paging.h @@ -0,0 +1,252 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_PAGING_H_ +#define _SOS_PAGING_H_ + +/** + * @file paging.h + * + * MMU management routines (arch-dependent). Setup the MMU without + * identity-mapping physical<->virtual addresses over the whole + * physical address space: a single, restricted and known, area is + * identity-mapped, the remaining kernel/user space is not. To access + * and manage the MMU translation tables (PD/PT on x86), we rely on a + * particular configuration, called "mirroring", where the top-level + * translation table (PD on x86) maps itself at a known and fixed (virtual) + * address. The only assumption for this to be possible is that the + * structure of the translation table entries are compatible at the + * different levels of vadddr->paddr translation process (PDE and PTE + * on x86 are Ok). Credits go to Christophe Avoinne for that. + */ + +#include +#include + + +/** + * Basic SOS virtual memory organization + * + * - Kernel space starts at the lower 4kB address (first page is not + * mapped in order to catch invalid pointers) + * - User space starts at the 1GB address + */ +#define SOS_PAGING_BASE_USER_ADDRESS (0x40000000) /* 1GB (must be 4MB-aligned) */ +#define SOS_PAGING_UPPER_USER_ADDRESS (0xFFFFFFFF) /* 4GB - 1B */ +#define SOS_PAGING_USER_SPACE_SIZE (SOS_PAGING_UPPER_USER_ADDRESS - \ + SOS_PAGING_BASE_USER_ADDRESS + 1) /* 3GB */ +#define SOS_PAGING_IS_USER_AREA(base_addr, length) \ + ( (SOS_PAGING_BASE_USER_ADDRESS <= (base_addr)) \ + && ((length) <= SOS_PAGING_USER_SPACE_SIZE) ) + +/** Length of the space reserved for the mirroring in the kernel + virtual space */ +#define SOS_PAGING_MIRROR_SIZE (1 << 22) /* 1 PD = 1024 Page Tables = 4MB */ + +/** Virtual address where the mirroring takes place */ +#define SOS_PAGING_MIRROR_VADDR \ + (SOS_PAGING_BASE_USER_ADDRESS - SOS_PAGING_MIRROR_SIZE) + +#define SOS_PAGING_BASE_KERNEL_ADDRESS (0x00004000) /* 16kB */ +#define SOS_PAGING_UPPER_KERNEL_ADDRESS (SOS_PAGING_MIRROR_VADDR - 1) /* 1GB - 4MB - 1B */ +#define SOS_PAGING_KERNEL_SPACE_SIZE (SOS_PAGING_UPPER_KERNEL_ADDRESS - \ + SOS_PAGING_BASE_KERNEL_ADDRESS + 1) /* 1GB - 4MB - 16kB */ +#define SOS_PAGING_IS_KERNEL_AREA(base_addr, length) \ + ( (SOS_PAGING_BASE_KERNEL_ADDRESS <= (base_addr)) \ + && ((length) <= SOS_PAGING_KERNEL_SPACE_SIZE) ) + + +/** + * sos_paging_map flags + */ +/** Usual virtual memory access rights */ +#define SOS_VM_MAP_PROT_NONE 0 +#define SOS_VM_MAP_PROT_READ (1<<0) +#define SOS_VM_MAP_PROT_WRITE (1<<1) +#define SOS_VM_MAP_PROT_EXEC (1<<2) /* Not supported on IA32 */ + +/** Mapping a page may involve an physical page allocation (for a new + PT), hence may potentially block */ +#define SOS_VM_MAP_ATOMIC (1<<31) + + +/** + * Setup initial page directory structure where the kernel is + * identically-mapped, and the mirroring. This routine also + * identity-maps the BIOS and video areas, to allow some debugging + * text to be printed to the console. Finally, this routine installs + * the whole configuration into the MMU. + */ +sos_ret_t sos_paging_subsystem_setup(sos_paddr_t identity_mapping_base, + sos_paddr_t identity_mapping_top); + + +/** + * Map the given physical page at the given virtual address in the + * current address space. + * + * @note *IMPORTANT*: The physical page ppage_paddr *MUST* have been + * referenced by the caller through either a call to + * sos_physmem_ref_physpage_new() or sos_physmem_ref_physpage_at(). It + * would work if this were untrue, but this would be INCORRECT (it is + * expected that one is owning the page before mapping it, or + * otherwise the page could have been stolen by an interrupt or + * another thread). + * + * @param ppage_paddr The address of a physical page (page-aligned) + * @param vpage_vaddr The address of the virtual page (page-aligned) + * @param is_user_page TRUE when the page is available from user space + * @param flags A mask made of SOS_VM_* bits + * + * @note Unless the SOS_VM_MAP_ATOMIC bit is set in the flags, the + * function may potentially block, because a physical page may be + * allocated for a new PT. + */ +sos_ret_t sos_paging_map(sos_paddr_t ppage_paddr, + sos_vaddr_t vpage_vaddr, + sos_bool_t is_user_page, + sos_ui32_t flags); + +/** + * Undo the mapping from vaddr to the underlying physical page (if any) + * @param vpage_vaddr The address of the virtual page (page-aligned) + * + * @return >= 0 when OK (the number of bytes of RAM unmapped), < 0 on error + */ +sos_ret_t sos_paging_unmap(sos_vaddr_t vpage_vaddr); + +/** + * Undo the mapping from [vaddr .. vaddr + size[ to the underlying + * physical pages (if any) + * @param vpage_vaddr The address of the virtual page (page-aligned) + * @param size The size (in bytes) to unmap. MUST be page-aligned + */ +sos_ret_t sos_paging_unmap_interval(sos_vaddr_t base_vpage_vaddr, + sos_size_t size); + +/** + * Return the page protection flags (SOS_VM_MAP_PROT_*) associated + * with the address, or SOS_VM_MAP_PROT_NONE when page is not mapped + */ +sos_ui32_t sos_paging_get_prot(sos_vaddr_t vaddr); + +/** + * Change the page access rights + */ +sos_ret_t sos_paging_set_prot(sos_vaddr_t vaddr, + sos_ui32_t new_prot); + + +/** + * Get the "dirty" status of the page + */ +sos_bool_t sos_paging_is_dirty(sos_vaddr_t vaddr); + +/** + * Change the "dirty" status of the page + */ +sos_ret_t sos_paging_set_dirty(sos_vaddr_t vaddr, + sos_bool_t is_dirty); + + +/** + * Change the access rights of the mapping from [vaddr .. vaddr + + * size[ to the underlying physical pages (if any) + * @param vpage_vaddr The address of the virtual page (page-aligned) + * @param size The size (in bytes) to unmap. MUST be page-aligned + */ +sos_ret_t sos_paging_set_prot_of_interval(sos_vaddr_t vaddr, + sos_size_t size, + sos_ui32_t new_prot); + +/** + * Return the physical address of the given virtual address. Since page + * at physical addr 0 is not mapped, the NULL result means "page not + * mapped". + */ +sos_paddr_t sos_paging_get_paddr(sos_vaddr_t vaddr); + +/** + * Tell whether the address is physically mapped + */ +#define sos_paging_check_present(vaddr) \ + (sos_paging_get_paddr(vaddr) != NULL) + + +/* ************************************************* + * Functions restricted to mm_context module + */ + + +/** + * Release the references to all the referenced pages (and PT on + * x86). On x86, this applies only to the USER pages and PT. + */ +sos_ret_t sos_paging_dispose(sos_vaddr_t vaddr_PD); + + +/** + * Copy the MMU configuration related to the kernel virtual area + */ +sos_ret_t sos_paging_copy_kernel_space(sos_vaddr_t dest_vaddr_PD, + sos_vaddr_t src_vaddr_PD); + + +/** + * Copy the MMU configuration related to the user virtual area + */ +sos_ret_t sos_paging_copy_user_space(sos_vaddr_t dest_vaddr_PD, + sos_vaddr_t src_vaddr_PD); + + +/** + * Prepare the *current* address space for COW on the given *private* + * mapping + */ +sos_ret_t sos_paging_prepare_COW(sos_uaddr_t base_address, + sos_size_t length); + + +/** + * Try to resolve the given page fault exception by a COW. + * + * @param uaddr The user-space address (of the current MMU context) of + * the faulting access + * + * @return TRUE if the page fault was a real COW and could be handled, + * FALSE if the page fault is not subject to COW (no physical mem + * mapped at this address). <0 in case the given address is subject to + * COW BUT could not be resolved due to runtime errors + */ +sos_ret_t sos_paging_try_resolve_COW(sos_uaddr_t uaddr); + + +/** + * Retrieve the current physical address of the PD + */ +sos_paddr_t sos_paging_get_current_PD_paddr(void); + + +/** + * Change the current MMU configuration. + * + * @note DANGEROUS. Don't use it unless you know exactly what you're + * doing ! + */ +sos_ret_t sos_paging_set_current_PD_paddr(sos_paddr_t paddr_PD); + +#endif /* _SOS_PAGING_H_ */ diff --git a/hwcore/segment.h b/hwcore/segment.h new file mode 100644 index 0000000..a58b361 --- /dev/null +++ b/hwcore/segment.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2004 The SOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_HWSEGS_H_ +#define _SOS_HWSEGS_H_ + +/** + * @file segments.h + * + * Global and local (GDT/LDT) segment descriptor definition and + * structure. These segments map virtual addresses (ie + * data/instruction addresses, relative to these segment descriptors) + * to linear addresses (ie addresses in the paged-memory space). + * + * @see Intel x86 doc, vol 3 chapter 3. + */ + + +/* + * Global segment selectors (GDT) for SOS/x86. + * + * @see gdt.h + */ +#define SOS_SEG_NULL 0 /* NULL segment, unused by the procesor */ +#define SOS_SEG_KCODE 1 /* Kernel code segment */ +#define SOS_SEG_KDATA 2 /* Kernel data segment */ +#define SOS_SEG_UCODE 3 /* User code segment */ +#define SOS_SEG_UDATA 4 /* User data segment */ +#define SOS_SEG_KERNEL_TSS 5 /* Kernel TSS for CPL3 -> CPL0 privilege change */ + + +#ifndef ASM_SOURCE +/** + * Helper macro that builds a segment register's value + */ +#define SOS_BUILD_SEGMENT_REG_VALUE(desc_privilege,in_ldt,seg_index) \ + ( (((desc_privilege) & 0x3) << 0) \ + | (((in_ldt)?1:0) << 2) \ + | ((seg_index) << 3) ) +#else +/* + * Assembler-compliant version. + * + * Caution: In assembler code, "in_ldt" MUST be either 1 or 0, nothing + * else. + */ +#define SOS_BUILD_SEGMENT_REG_VALUE(desc_privilege,in_ldt,seg_index) \ + ( (((desc_privilege) & 0x3) << 0) \ + | ((in_ldt & 1) << 2) \ + | ((seg_index) << 3) ) +#endif + + +/* + * Local segment selectors (LDT) for SOS/x86 + */ +/* None */ + +#endif /* _SOS_HWSEGS_H_ */ diff --git a/hwcore/swintr.c b/hwcore/swintr.c new file mode 100644 index 0000000..48d3d7f --- /dev/null +++ b/hwcore/swintr.c @@ -0,0 +1,50 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include + +#include "idt.h" +#include "irq.h" + +#include "swintr.h" + +/** + * The Assembler low-level wrapper for the SOS syscalls + * + * This wrapper takes care of saving the state of the calling user + * thread before calling the "C" function sos_do_syscall(), passing it + * the correct arguments. + */ +extern void sos_syscall_wrapper(void); + + +sos_ret_t sos_swintr_subsystem_setup(void) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + retval + = sos_idt_set_handler(SOS_SWINTR_SOS_SYSCALL, + (sos_vaddr_t) sos_syscall_wrapper, + 3 /* CPL3 routine */); + + sos_restore_IRQs(flags); + + return retval; +} diff --git a/hwcore/swintr.h b/hwcore/swintr.h new file mode 100644 index 0000000..4a371d6 --- /dev/null +++ b/hwcore/swintr.h @@ -0,0 +1,43 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_SWINTR_H_ +#define _SOS_SWINTR_H_ + +/** + * @file swintr.h + * + * SOS handlers for the software interrupts. In SOS, we handle a + * single software interrupt: the syscall interrupt. + */ + + +/** + * The SOS software interrupt number for issueing syscalls + */ +#define SOS_SWINTR_SOS_SYSCALL 0x42 + +#if defined(KERNEL_SOS) && !defined(ASM_SOURCE) + +#include +#include + +sos_ret_t sos_swintr_subsystem_setup(void); + +#endif /* defined(KERNEL_SOS) && !defined(ASM_SOURCE) */ + +#endif /* _SOS_SWINTR_H_ */ diff --git a/hwcore/swintr_wrappers.S b/hwcore/swintr_wrappers.S new file mode 100644 index 0000000..64c8086 --- /dev/null +++ b/hwcore/swintr_wrappers.S @@ -0,0 +1,116 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "segment.h" + + +/** + * @file swintr_wrappers.S + * + * The SOS low-level handlers for the software interrupts. Currently + * only 1 wrapper: that for the SOS syscalls. + */ +.file "swintr_wrappers.S" + +.text + +/* The address of the real "C" syscall function */ +.extern sos_do_syscall + +/** Update the kernel TSS in case we are switching to a thread in user + mode in order to come back into the correct kernel stack */ +.extern sos_cpu_context_update_kernel_tss + +/* The address of the function to call to set back the user thread's + MMU configuration upon return to user context */ +.extern sos_thread_prepare_syscall_switch_back + +.p2align 2, 0x90 +.globl sos_syscall_wrapper +sos_syscall_wrapper: +.type sos_syscall_wrapper,@function + + /* Fake error code */ + pushl $0 + /* Backup the context */ + pushl %ebp + movl %esp, %ebp + + pushl %edi + pushl %esi + pushl %edx + pushl %ecx + pushl %ebx + pushl %eax + subl $2,%esp + pushw %ss + pushw %ds + pushw %es + pushw %fs + pushw %gs + + /* Set correct kernel segment descriptors' value */ + movw $SOS_BUILD_SEGMENT_REG_VALUE(0, 0, SOS_SEG_KDATA), %di + pushw %di ; popw %ds + pushw %di ; popw %es + pushw %di ; popw %fs + pushw %di ; popw %gs + + /* Prepare the call to do_syscall */ + pushl %esp /* user_ctxt */ + pushl %eax /* syscall ID */ + + call sos_do_syscall + /* Unallocate the stack used by the + do_syscall arguments */ + addl $8, %esp + + /* store the do_syscall return value into interrupted context */ + movl %eax, 12(%esp) + + /* Set the MMU configuration to that of the user thread's process */ + pushl %esp /* user_ctxt */ + call sos_thread_prepare_syscall_switch_back + addl $4, %esp /* Unallocate the stack */ + + /* Prepare kernel TSS because we are switching back to a user + thread: we make sure that we will come back into the kernel at a + correct stack location */ + pushl %esp /* Pass the location of the context we are + restoring to the function */ + call sos_cpu_context_update_kernel_tss + addl $4, %esp + + /* Restore the user context */ + popw %gs + popw %fs + popw %es + popw %ds + popw %ss + addl $2,%esp + popl %eax /* This is the return value of do_syscall (see above) */ + popl %ebx + popl %ecx + popl %edx + popl %esi + popl %edi + + popl %ebp + /* Remove fake error code */ + addl $4, %esp + iret diff --git a/sos/assert.c b/sos/assert.c new file mode 100644 index 0000000..a7b24bf --- /dev/null +++ b/sos/assert.c @@ -0,0 +1,47 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include // backtrace + +#include "assert.h" + +void sos_display_fatal_error(const char *format, /* args */...) +{ + char buff[256]; + va_list ap; + + asm("cli\n"); /* disable interrupts -- x86 only */ \ + + va_start(ap, format); + vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + sos_bochs_putstring(buff); sos_bochs_putstring("\n"); + sos_x86_videomem_putstring(23, 0, + SOS_X86_VIDEO_BG_BLACK + | SOS_X86_VIDEO_FG_LTRED , buff); + + sos_thread_dump_backtrace(TRUE, TRUE); + + /* Infinite loop: processor halted */ + for ( ; ; ) + asm("hlt\n"); +} diff --git a/sos/assert.h b/sos/assert.h new file mode 100644 index 0000000..9fcfec0 --- /dev/null +++ b/sos/assert.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_ASSERT_H_ +#define _SOS_ASSERT_H_ + + +void sos_display_fatal_error(const char *format, /* args */...) + __attribute__ ((format (printf, 1, 2), noreturn)); + + +/** + * If the expr is FALSE, print a message and halt the machine + */ +#define SOS_ASSERT_FATAL(expr) \ + ({ \ + int __res=(int)(expr); \ + if (! __res) \ + sos_display_fatal_error("%s@%s:%d Assertion " # expr " failed", \ + __PRETTY_FUNCTION__, __FILE__, __LINE__); \ + }) + + +#define SOS_FATAL_ERROR(fmt,args...) \ + ({ \ + sos_display_fatal_error("%s@%s:%d FATAL: " fmt, \ + __PRETTY_FUNCTION__, __FILE__, __LINE__, \ + ##args); \ + }) + +#endif /* _SOS_ASSERT_H_ */ diff --git a/sos/binfmt_elf32.c b/sos/binfmt_elf32.c new file mode 100644 index 0000000..30d4581 --- /dev/null +++ b/sos/binfmt_elf32.c @@ -0,0 +1,462 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include "binfmt_elf32.h" + + +/** + * The "C" structure of a user program image in the kernel. Structures + * like this are created by the Makefile in the userland/ directory + */ +struct userprog_entry +{ + const char *name; + sos_vaddr_t bottom_vaddr; + sos_vaddr_t top_vaddr; +}; + + +/** + * Symbol marking the start of the userprogs table, as setup by the + * ld script in the userland/ directory + */ +extern char _userprogs_table; + + +/** + * Structure of a mapped resource for an ELF32 program (ie a portion + * of the kernel space) + */ +struct elf32_mapped_program +{ + sos_vaddr_t vaddr; + sos_size_t size; + int ref_cnt; + + struct sos_umem_vmm_mapped_resource mr; +}; + + +/** Called after the virtual region has been inserted inside its + address space */ +static void elf32prog_ref(struct sos_umem_vmm_vr * vr) +{ + struct elf32_mapped_program * elf32prog_resource; + elf32prog_resource = (struct elf32_mapped_program*) sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + elf32prog_resource->ref_cnt ++; +} + + +/** Called when the virtual region is removed from its address + space */ +static void elf32prog_unref(struct sos_umem_vmm_vr * vr) +{ + struct elf32_mapped_program * elf32prog_resource; + elf32prog_resource + = (struct elf32_mapped_program*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + elf32prog_resource->ref_cnt --; + SOS_ASSERT_FATAL(elf32prog_resource->ref_cnt >= 0); + + /* Free the resource if it becomes unused */ + if (elf32prog_resource->ref_cnt == 0) + sos_kfree((sos_vaddr_t)elf32prog_resource); +} + + +/** Called when a legitimate page fault is occuring in the VR */ +static sos_ret_t elf32prog_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + struct elf32_mapped_program * elf32prog_resource; + sos_ret_t retval = SOS_OK; + sos_paddr_t ppage_paddr; + sos_uaddr_t upage_uaddr = SOS_PAGE_ALIGN_INF(uaddr); + sos_uoffset_t offset_in_prog; + sos_size_t size_to_copy; + sos_ui32_t access_rights = sos_umem_vmm_get_prot_of_vr(vr); + + elf32prog_resource + = (struct elf32_mapped_program*) + sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data; + + /* Compute the offset in program of the page, and the size to copy + in user space */ + offset_in_prog = upage_uaddr - sos_umem_vmm_get_start_of_vr(vr) + + sos_umem_vmm_get_offset_in_resource(vr); + size_to_copy = SOS_PAGE_SIZE; + if (offset_in_prog + size_to_copy > elf32prog_resource->size) + size_to_copy = elf32prog_resource->size - offset_in_prog; + + /* If the source page is also aligned, simply remap the kernel area + into user space */ + if (SOS_IS_PAGE_ALIGNED(elf32prog_resource->vaddr + offset_in_prog)) + { + sos_vaddr_t kern_vaddr = elf32prog_resource->vaddr + offset_in_prog; + + ppage_paddr = sos_paging_get_paddr(kern_vaddr); + + /* Remap it in user space, in read-only mode (to force COW) */ + retval = sos_paging_map(ppage_paddr, + upage_uaddr, + TRUE, + access_rights & ~SOS_VM_MAP_PROT_WRITE); + SOS_ASSERT_FATAL(SOS_OK == retval); + } + + /* Otherwise we need to allocate a new page */ + else + { + /* Allocate a new page that contains the code/data of the + program */ + ppage_paddr = sos_physmem_ref_physpage_new(FALSE); + if (! ppage_paddr) + return -SOS_ENOMEM; + + /* Map it in user space, in read/write mode for the kernel to copy + the data in the page */ + retval = sos_paging_map(ppage_paddr, + upage_uaddr, + TRUE, + access_rights | SOS_VM_MAP_PROT_WRITE); + SOS_ASSERT_FATAL(SOS_OK == retval); + sos_physmem_unref_physpage(ppage_paddr); + + /* Copy the program in it */ + memcpy((void*)upage_uaddr, + (void*)(elf32prog_resource->vaddr + offset_in_prog), + size_to_copy); + if (size_to_copy < SOS_PAGE_SIZE) + memset((void*)(upage_uaddr + size_to_copy), 0x0, + SOS_PAGE_SIZE - size_to_copy); + + /* Change it read-only if needed */ + if (! (access_rights & SOS_VM_MAP_PROT_WRITE)) + return sos_paging_set_prot(upage_uaddr, + access_rights & ~SOS_VM_MAP_PROT_WRITE); + } + + return retval; +} + + +static struct sos_umem_vmm_vr_ops elf32prog_ops = (struct sos_umem_vmm_vr_ops) +{ + .ref = elf32prog_ref, + .unref = elf32prog_unref, + .page_in = elf32prog_page_in, + .unmap = NULL /* ignored */ +}; + + +static sos_ret_t elf32prog_mmap(struct sos_umem_vmm_vr *vr) +{ + return sos_umem_vmm_set_ops_of_vr(vr, &elf32prog_ops); +} + + +/* + * Local functions + */ + + +/** + * Function to locate the given user program image in the kernel memory + */ +static struct userprog_entry * lookup_userprog(const char *name); + + +sos_uaddr_t sos_binfmt_elf32_map(struct sos_umem_vmm_as * dest_as, + const char * progname) +{ + int i; + + /** + * Typedefs, constants and structure definitions as given by the ELF + * standard specifications. + */ + typedef unsigned long Elf32_Addr; + typedef unsigned long Elf32_Word; + typedef unsigned short Elf32_Half; + typedef unsigned long Elf32_Off; + typedef signed long Elf32_Sword; + + /* Elf identification */ + +#define EI_NIDENT 16 + typedef struct { + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; + } __attribute__((packed)) Elf32_Ehdr_t; + +/* e_ident value */ +#define ELFMAG0 0x7f +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +/* e_ident offsets */ +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_PAD 7 + +/* e_ident[EI_CLASS] */ +#define ELFCLASSNONE 0 +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +/* e_ident[EI_DATA] */ +#define ELFDATANONE 0 +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +/* e_type */ +#define ET_NONE 0 /* No file type */ +#define ET_REL 1 /* Relocatable file */ +#define ET_EXEC 2 /* Executable file */ +#define ET_DYN 3 /* Shared object file */ +#define ET_CORE 4 /* Core file */ +#define ET_LOPROC 0xff00 /* Processor-specific */ +#define ET_HIPROC 0xffff /* Processor-specific */ + +/* e_machine */ +#define EM_NONE 0 /* No machine */ +#define EM_M32 1 /* AT&T WE 32100 */ +#define EM_SPARC 2 /* SPARC */ +#define EM_386 3 /* Intel 80386 */ +#define EM_68K 4 /* Motorola 68000 */ +#define EM_88K 5 /* Motorola 88000 */ +#define EM_860 7 /* Intel 80860 */ +#define EM_MIPS 8 /* MIPS RS3000 */ + +/* e_version */ +#define EV_NONE 0 /* invalid version */ +#define EV_CURRENT 1 /* current version */ + + typedef struct { + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; + } __attribute__((packed)) Elf32_Phdr_t; + +/* Reserved segment types p_type */ +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_SHLIB 5 +#define PT_PHDR 6 +#define PT_LOPROC 0x70000000 +#define PT_HIPROC 0x7fffffff + +/* p_flags */ +#define PF_X 1 +#define PF_W 2 +#define PF_R 4 + + + Elf32_Ehdr_t *elf_hdr; + Elf32_Phdr_t *elf_phdrs; + + struct elf32_mapped_program * mapped_prog; + struct userprog_entry * prog; + sos_uaddr_t prog_top_user_address = 0; + + mapped_prog + = (struct elf32_mapped_program*) + sos_kmalloc(sizeof(struct elf32_mapped_program), 0); + if (! mapped_prog) + return -SOS_ENOMEM; + + prog = lookup_userprog(progname); + if (! prog) + { + sos_kfree((sos_vaddr_t)mapped_prog); + return 0; + } + + /* Initialize mapped resource */ + memset(mapped_prog, 0x0, sizeof(*mapped_prog)); + mapped_prog->mr.custom_data = mapped_prog; + mapped_prog->mr.mmap = elf32prog_mmap; + mapped_prog->mr.allowed_access_rights + = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + mapped_prog->vaddr = prog->bottom_vaddr; + mapped_prog->size = prog->top_vaddr - prog->bottom_vaddr; + + elf_hdr = (Elf32_Ehdr_t*) prog->bottom_vaddr; + + /* Make sure the image is large enough to contain at least the ELF + header */ + if (prog->bottom_vaddr + sizeof(Elf32_Ehdr_t) > prog->top_vaddr) + { + sos_bochs_printf("ELF prog %s: incorrect header\n", prog->name); + return 0; + } + + /* Macro to check expected values for some fields in the ELF header */ +#define ELF_CHECK(hdr,field,expected_value) \ + ({ if ((hdr)->field != (expected_value)) \ + { \ + sos_bochs_printf("ELF prog %s: for %s, expected %x, got %x\n", \ + prog->name, \ + #field, \ + (unsigned)(expected_value), \ + (unsigned)(hdr)->field); \ + return 0; \ + } \ + }) + + ELF_CHECK(elf_hdr, e_ident[EI_MAG0], ELFMAG0); + ELF_CHECK(elf_hdr, e_ident[EI_MAG1], ELFMAG1); + ELF_CHECK(elf_hdr, e_ident[EI_MAG2], ELFMAG2); + ELF_CHECK(elf_hdr, e_ident[EI_MAG3], ELFMAG3); + ELF_CHECK(elf_hdr, e_ident[EI_CLASS], ELFCLASS32); + ELF_CHECK(elf_hdr, e_ident[EI_DATA], ELFDATA2LSB); + ELF_CHECK(elf_hdr, e_type, ET_EXEC); + ELF_CHECK(elf_hdr, e_version, EV_CURRENT); + + /* Get the begining of the program header table */ + elf_phdrs = (Elf32_Phdr_t*) (prog->bottom_vaddr + elf_hdr->e_phoff); + + /* Map the program segment in R/W mode. To make things clean, we + should iterate over the sections, not the program header */ + for (i = 0 ; i < elf_hdr->e_phnum ; i++) + { + sos_ui32_t prot_flags; + sos_uaddr_t uaddr; + + /* Ignore the empty program headers that are not marked "LOAD" */ + if (elf_phdrs[i].p_type != PT_LOAD) + { + if (elf_phdrs[i].p_memsz != 0) + { + sos_display_fatal_error("ELF: non-empty non-LOAD segments not supported yet"); + } + continue; + } + + if (! SOS_PAGING_IS_USER_AREA(elf_phdrs[i].p_vaddr, + elf_phdrs[i].p_memsz) ) + { + sos_display_fatal_error("User program has an incorrect address"); + } + + prot_flags = 0; + if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_READ) + prot_flags |= SOS_VM_MAP_PROT_READ; + if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_WRITE) + prot_flags |= SOS_VM_MAP_PROT_WRITE; + if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_EXEC) + prot_flags |= SOS_VM_MAP_PROT_EXEC; + + uaddr = elf_phdrs[i].p_vaddr; + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(uaddr)); + + /* First of all: map the region of the phdr which is also + covered by the file */ + SOS_ASSERT_FATAL(SOS_OK + == sos_umem_vmm_map(dest_as, &uaddr, + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz), + prot_flags, + /* PRIVATE */ SOS_VR_MAP_FIXED, + & mapped_prog->mr, + elf_phdrs[i].p_offset)); + + /* Then map the remaining by a zero resource */ + uaddr += SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz); + if (SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz) + < SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz)) + SOS_ASSERT_FATAL(SOS_OK + == sos_dev_zero_map(dest_as, &uaddr, + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz) + - SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz), + prot_flags, + /* PRIVATE */ SOS_VR_MAP_FIXED)); + + if (prog_top_user_address + < uaddr + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz)) + prog_top_user_address + = uaddr + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz); + } + + /* Now prepare the heap */ + sos_umem_vmm_init_heap(dest_as, prog_top_user_address); + + return elf_hdr->e_entry; +} + + +/** + * Lookup a user program located inside the kernel's image + */ +static struct userprog_entry * lookup_userprog(const char *name) +{ + struct userprog_entry *prog; + + if (! name) + return NULL; + + /* Walk through the table of user program description structures to + find the user program with the given name */ + for (prog = (struct userprog_entry*) & _userprogs_table ; + prog && (prog->name != NULL) ; + prog++) + { + if (0 == strcmp(name, prog->name)) + /* Found it ! */ + return prog; + } + + return NULL; +} diff --git a/sos/binfmt_elf32.h b/sos/binfmt_elf32.h new file mode 100644 index 0000000..d95bb3b --- /dev/null +++ b/sos/binfmt_elf32.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_BINFMT_ELF32_H_ +#define _SOS_BINFMT_ELF32_H_ + +/** + * @file binfmt_elf32.h + * + * Userspace mapper to load ELF32 programs. For the 7.x articles, the + * user programs are resident inside the kernel image, so that loading + * them in userspace consists in remapping them in userspace. + */ + +#include + +/** + * Map a user ELF32 program into user memory. Make sure the program + * is in a valid ELF format, map it into memory, and return the + * address of its entry point (ie _start function) + * + * @return 0 when the program is not a valid ELF. Return the start + * address of the program otherwise + */ +sos_uaddr_t sos_binfmt_elf32_map(struct sos_umem_vmm_as * dest_as, + const char * progname); + +#endif /* _SOS_BINFMT_ELF32_H_ */ diff --git a/sos/blkcache.c b/sos/blkcache.c new file mode 100644 index 0000000..dac27ca --- /dev/null +++ b/sos/blkcache.c @@ -0,0 +1,500 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "blkcache.h" + + +/** + * @note In the implementation below, we choose to transfer the blocks + * from list to list /before/ doing the disk access (if needed). The + * aim is to have the entries in the right list before any other + * thread can lock them. Actually, these "other threads" can lock the + * entry only during the disk accesses (kernel is non + * preemptible). So, putting the entries in the correct list /before/ + * doing the actual disk read/write access will lower the probability + * for the thread to learn that, at the end, the entry it was waiting + * for has been transferred on another list. + * + * @note What is extremely important however, is that the + * sos_hash_insert/remove operation be carried out BEFORE the + * bocck_read/write operations. If not, then it may be possible to + * insert an entry already inserted (which causes the system to halt), + * etc. + */ + + + +struct sos_block_cache_entry +{ + /** The key of the hash map */ + sos_luoffset_t block_index; + + /** Kernel address where the data for the block are stored */ + sos_vaddr_t block_contents; + + /** Is the block available, dirty or not ? */ + enum { ENTRY_FREE, ENTRY_SYNC, ENTRY_DIRTY } state; + + /** Lock to protect the entry */ + struct sos_kmutex lock; + + /** Linkage structure to keep the cache block in the hash map */ + struct sos_hash_linkage hlink; + + /** Links to insert the bloc into the free/sync/dirty lists */ + struct sos_block_cache_entry *prev, *next; +}; + + +/** The entries of all the cache entries are stored in a single SLAB pool */ +static struct sos_kslab_cache * cache_of_bkcache_entries; + + +struct sos_block_cache +{ + /** SLAB pool for the block contents */ + struct sos_kslab_cache * slab_cache; + + /** Dictionary offset -> block cache entry */ + struct sos_hash_table * lookup_table; + + /** Hardware read/write operations */ + struct sos_blockdev_operations * operations; + void * blkdev_instance_custom_data; + + /* Lists to look into in order to free a node. LRU is enforced */ + struct sos_block_cache_entry * free_list; /**< List of entries + still available */ + struct sos_block_cache_entry * sync_list; /**< Blocks in sync with + disk (LRU at end) */ + struct sos_block_cache_entry * dirty_list; /**< Dirty blocks (LRU + last) */ +}; + + +sos_ret_t sos_blkcache_subsystem_setup() +{ + cache_of_bkcache_entries + = sos_kmem_cache_create("blkcache_entry", + sizeof(struct sos_block_cache_entry), + 1, 0, + SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); + if (NULL == cache_of_bkcache_entries) + return -SOS_ENOMEM; + return SOS_OK; +} + + +struct sos_block_cache * +sos_blkcache_new_cache(void * blockdev_instance_custom_data, + sos_size_t block_size, + sos_count_t cache_size_in_blocks, + struct sos_blockdev_operations * blockdev_ops) +{ + sos_count_t idx; + struct sos_block_cache * blkcache; + + SOS_ASSERT_FATAL(block_size > 0); + SOS_ASSERT_FATAL(cache_size_in_blocks > 0); + + blkcache + = (struct sos_block_cache*) sos_kmalloc(sizeof(struct sos_block_cache), 0); + if (NULL == blkcache) + return NULL; + + blkcache->blkdev_instance_custom_data = blockdev_instance_custom_data; + blkcache->operations = blockdev_ops; + + /* Allocate the hash table */ + blkcache->lookup_table = sos_hash_create("blkcache", + struct sos_block_cache_entry, + sos_hash_ui64, + sos_hash_key_eq_ui64, + 17, block_index, hlink); + if (NULL == blkcache->lookup_table) + { + sos_kfree((sos_vaddr_t)blkcache); + return NULL; + } + + /* Allocate the slab cache for the blocks */ + blkcache->slab_cache = sos_kmem_cache_create("blkcache", block_size, + 4, 0, + SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO); + if (NULL == blkcache->slab_cache) + { + sos_hash_dispose(blkcache->lookup_table); + sos_kfree((sos_vaddr_t)blkcache); + return NULL; + } + + /* Allocate the cache blocks in this slab cache, and mark them as + "free" */ + for (idx = 0 ; idx < cache_size_in_blocks ; idx ++) + { + struct sos_block_cache_entry * bkcache_entry; + sos_vaddr_t bkcache_contents; + + bkcache_entry + = (struct sos_block_cache_entry*)sos_kmem_cache_alloc(cache_of_bkcache_entries, + 0); + if (NULL == bkcache_entry) + { + sos_blkcache_delete_cache(blkcache); + return NULL; + } + + bkcache_contents = sos_kmem_cache_alloc(blkcache->slab_cache, 0); + if (NULL == (void*)bkcache_contents) + { + sos_kmem_cache_free((sos_vaddr_t)bkcache_entry); + sos_blkcache_delete_cache(blkcache); + return NULL; + } + + /* Initialize the cache entry */ + bkcache_entry->block_contents = bkcache_contents; + bkcache_entry->state = ENTRY_FREE; + SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_init(& bkcache_entry->lock, + "bkcache_lock", + SOS_KWQ_ORDER_FIFO)); + + /* Now add it in the free list */ + list_add_head(blkcache->free_list, bkcache_entry); + } + + return blkcache; +} + + +/** Helper function used to collapse the sync/free lists */ +static sos_ret_t blkcache_collapse_list(struct sos_block_cache_entry ** l) +{ + while (! list_is_empty(*l)) + { + struct sos_block_cache_entry * entry = list_pop_head(*l); + if (NULL == entry) + break; + + /* The delete_cache function should be called only when the + block device is not currently in use */ + SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_dispose(& entry->lock)); + + if (NULL != (void*)entry->block_contents) + sos_kmem_cache_free(entry->block_contents); + + sos_kmem_cache_free((sos_vaddr_t)entry); + } + + return SOS_OK; +} + + +sos_ret_t +sos_blkcache_delete_cache(struct sos_block_cache * bc) +{ + sos_blkcache_flush(bc); /* The dirty list is expected to be empty */ + + sos_hash_dispose(bc->lookup_table); + blkcache_collapse_list(& bc->sync_list); + blkcache_collapse_list(& bc->free_list); + + sos_kfree((sos_vaddr_t)bc); + + return SOS_OK; +} + + +/** Helper function used to transfer a dirty block to the + sync_list. @note the entry is expected to be locked */ +static sos_ret_t blkcache_flush_entry(struct sos_block_cache * bc, + struct sos_block_cache_entry * entry, + sos_bool_t mark_as_free) +{ + sos_ret_t retval = SOS_OK; + + SOS_ASSERT_FATAL(TRUE == sos_kmutex_owned_by_me(& entry->lock)); + SOS_ASSERT_FATAL(entry->state == ENTRY_DIRTY); + + /* We prepare to transfer it to the sync/free list */ + if (mark_as_free) + { + list_delete(bc->dirty_list, entry); + entry->state = ENTRY_FREE; + sos_hash_remove(bc->lookup_table, entry); + list_add_head(bc->free_list, entry); + } + else + { + list_delete(bc->dirty_list, entry); + entry->state = ENTRY_SYNC; + list_add_head(bc->sync_list, entry); + } + + retval = bc->operations->write_block(bc->blkdev_instance_custom_data, + entry->block_contents, + entry->block_index); + if (SOS_OK != retval) + { + /* + * Putting the entry back to the dirty list may cause + * retrieve_block to loop infinitely (always trying to transfer + * this block to disk...) !... A solution would be to register + * this block in a dictionary of failing blocks and to force + * retrieve block to ignore requests on these blocks + */ + SOS_FATAL_ERROR("Not implemented yet: we don't support failed disk access (see comments in source file)"); + } + + return retval; +} + + +struct sos_block_cache_entry * +sos_blkcache_retrieve_block(struct sos_block_cache * bc, + sos_luoffset_t block_index, + sos_blkcache_access_type_t access_type, + sos_vaddr_t */*out*/ block_contents) +{ + struct sos_block_cache_entry * entry = NULL; + *block_contents = (sos_vaddr_t)NULL; + + /* Get and lock the entry */ + while (TRUE) + { + entry = sos_hash_lookup(bc->lookup_table, + & block_index); + if (NULL != entry) + { + /* The block was already in the cache */ + + sos_kmutex_lock(& entry->lock, NULL); + + /* We have the mutex. However, this entry can now be + dedicated to another block: it was stolen by another + thread during the blocking time */ + if ((ENTRY_FREE != entry->state) + && (entry->block_index == block_index)) + { + /* Great ! This entry is still the one we expect ! */ + + /* Write-only access: force it to be dirty NOW ! */ + if ( (access_type == SOS_BLKCACHE_WRITE_ONLY) + && (ENTRY_SYNC == entry->state) ) + { + list_delete(bc->sync_list, entry); + entry->state = ENTRY_DIRTY; + list_add_head(bc->dirty_list, entry); + } + + *block_contents = entry->block_contents; + return entry; + } + + /* Bad luck: someone stole the entry and used it in the + meantime */ + sos_kmutex_unlock(& entry->lock); + /* Go back and look it up again */ + continue; + } + + + /* Ok, the block is not yet in the cache: we need to allocate a new + one */ + + /* The simplest: there is a free slot in the free list (take the + most recently freed) */ + if (! list_is_empty(bc->free_list)) + { + entry = list_get_head(bc->free_list); + sos_kmutex_lock(& entry->lock, NULL); + + /* By the time we return from the lock, the entry could have + be stolen */ + if ((ENTRY_FREE != entry->state) + || (NULL != sos_hash_lookup(bc->lookup_table, & block_index))) + { + sos_kmutex_unlock(& entry->lock); + continue; + } + + /* Ok, we got a REALLY free entry ! */ + break; + } + + /* A little more complicated: there is a non-dirty entry in + synch with disk, transfer it to the free list. Take the least + recently used */ + if (! list_is_empty(bc->sync_list)) + { + entry = list_get_tail(bc->sync_list); + sos_kmutex_lock(& entry->lock, NULL); + + /* During blocking time, the block is not in the sync list + anymore ! We'd better redo a complete iteration to choose + another block, just in case the block we are looking for + was transferred in cache by someone else */ + if ((ENTRY_SYNC != entry->state) + || (NULL != sos_hash_lookup(bc->lookup_table, & block_index))) + { + sos_kmutex_unlock(& entry->lock); + continue; + } + + list_delete(bc->sync_list, entry); + sos_hash_remove(bc->lookup_table, entry); + entry->state = ENTRY_FREE; + list_add_head(bc->free_list, entry); + + /* Now we have a free entry for us ! */ + break; + } + + /* + * Bad luck: we have to flush a dirty entry back to disk + */ + SOS_ASSERT_FATAL(! list_is_empty(bc->dirty_list)); + entry = list_get_tail(bc->dirty_list); + + sos_kmutex_lock(& entry->lock, NULL); + + /* During blocking time, the block was transferred to the sync + list ! We'd better redo a complete iteration to choose + another block, if we want to keep this recently-accessed + block inside the cache */ + if ((ENTRY_DIRTY == entry->state) + && (NULL == sos_hash_lookup(bc->lookup_table, & block_index))) + { + /* entry still dirty: transfer it to the free list */ + + if (SOS_OK == blkcache_flush_entry(bc, entry, TRUE)) + /* We can use it now ! */ + break; + } + + sos_kmutex_unlock(& entry->lock); + + /* We can now go for another iteration because we blocked: maybe + the block was retrieved into the cache by someone else */ + } + + /* Here we have a locked block that needs to be initialised */ + + /* Remove it from the free list */ + list_delete(bc->free_list, entry); + + /* Prepare it to be inserted in the dictionnary */ + entry->block_index = block_index; + + /* Announce that we are preparing to add the block into the cache */ + if (SOS_OK != sos_hash_insert(bc->lookup_table, entry)) + SOS_FATAL_ERROR("Unexpected hash collision"); + + + /* No need to do anything for a write-only block, simply mark it + dirty */ + if (access_type == SOS_BLKCACHE_WRITE_ONLY) + { + entry->state = ENTRY_DIRTY; + list_add_head(bc->dirty_list, entry); + } + + /* Otherwise retrieve the block from disk NOW ! */ + else + { + entry->state = ENTRY_SYNC; + list_add_head(bc->sync_list, entry); + + if (SOS_OK + != bc->operations->read_block(bc->blkdev_instance_custom_data, + entry->block_contents, + entry->block_index)) + { + /* In case of failure, remove it from hash and return NULL */ + list_delete(bc->sync_list, entry); + sos_hash_remove(bc->lookup_table, entry); + entry->state = ENTRY_FREE; + list_add_head(bc->free_list, entry); + sos_kmutex_unlock(& entry->lock); + return NULL; + } + } + + *block_contents = entry->block_contents; + return entry; +} + + +sos_ret_t +sos_blkcache_release_block(struct sos_block_cache * bc, + struct sos_block_cache_entry * entry, + sos_bool_t is_dirty, + sos_bool_t force_flush) +{ + /* Enforce the least recently used policy */ + if (ENTRY_SYNC == entry->state) + { + list_delete(bc->sync_list, entry); + /* Eventually transfer the block to the dirty list */ + if (is_dirty) + entry->state = ENTRY_DIRTY; + } + else + list_delete(bc->dirty_list, entry); + + if (ENTRY_SYNC == entry->state) + list_add_head(bc->sync_list, entry); + else + list_add_head(bc->dirty_list, entry); + + if ( (ENTRY_DIRTY == entry->state) && force_flush) + blkcache_flush_entry(bc, entry, FALSE); + + sos_kmutex_unlock(& entry->lock); + return SOS_OK; +} + + +sos_ret_t +sos_blkcache_flush(struct sos_block_cache * bc) +{ + struct sos_block_cache_entry * entry; + while (NULL != (entry = list_get_head(bc->dirty_list)) ) + { + sos_ret_t retval = SOS_OK; + + sos_kmutex_lock(& entry->lock, NULL); + if (ENTRY_DIRTY == entry->state) + retval = blkcache_flush_entry(bc, entry, FALSE); + sos_kmutex_unlock(& entry->lock); + + if (SOS_OK != retval) + return retval; + } + + return SOS_OK; +} diff --git a/sos/blkcache.h b/sos/blkcache.h new file mode 100644 index 0000000..5c201e4 --- /dev/null +++ b/sos/blkcache.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_BLKCACHE_H_ +#define _SOS_BLKCACHE_H_ + + +/** + * @file blkcache.h + * + * Simple block cache interface. This implementation is based on a + * fixed-size block cache, the size being set at construction time. + */ +#include +#include + + +/** Opaque structure holding a cache */ +struct sos_block_cache; + +/** Opaque structure holding a cache entry (used as a cookie from + blkcache_retrieve to blkcache_release) */ +struct sos_block_cache_entry; + + +sos_ret_t sos_blkcache_subsystem_setup(void); + + +/** Create a new block cache */ +struct sos_block_cache * +sos_blkcache_new_cache(void * blockdev_instance_custom_data, + sos_size_t block_size, + sos_count_t cache_size_in_blocks, + struct sos_blockdev_operations * blockdev_ops); + + +/** Delete a block cache */ +sos_ret_t +sos_blkcache_delete_cache(struct sos_block_cache * bc); + + +/** + * "Write-only" cached blocks are expected to be *completely* + * overwritten by the blkdev code + */ +typedef enum { SOS_BLKCACHE_READ_ONLY = 0x4242, + SOS_BLKCACHE_READ_WRITE = 0x2442, + SOS_BLKCACHE_WRITE_ONLY = 0x4224 } sos_blkcache_access_type_t; + + +/** + * Retrieve a block from the cache + * @param block_contents is filled with the address of the kernel + * buffer holding the data + * @return opaque structure used by release_block, or NULL when the + * block could not be retrieved from disk + * + * @note once used and/or modified, the block must be released. + */ +struct sos_block_cache_entry * +sos_blkcache_retrieve_block(struct sos_block_cache * bc, + sos_luoffset_t block_index, + sos_blkcache_access_type_t access_type, + sos_vaddr_t */*out*/ block_contents); + + +/** + * Unreference a block previously retrieved. If is_dirty is TRUE, the + * block MUST have been retrieved with the "read_write" or + * "write_only" access type. + */ +sos_ret_t +sos_blkcache_release_block(struct sos_block_cache * bc, + struct sos_block_cache_entry * entry, + sos_bool_t is_dirty, + sos_bool_t force_flush); + + +/** Flush any modified blocks to disk */ +sos_ret_t +sos_blkcache_flush(struct sos_block_cache * bc); + +#endif diff --git a/sos/blkdev.c b/sos/blkdev.c new file mode 100644 index 0000000..c5f3e25 --- /dev/null +++ b/sos/blkdev.c @@ -0,0 +1,1197 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blkdev.h" + +struct sos_blockdev_instance +{ + /** + * Unique identifier (blockdev-wide) of this block device used by + * the sync_all_blockdev function. This enables sync_all_blockdev to + * be resilient to other register_partition/disk calls + */ + sos_ui64_t uid; + + + /** + * Size of a block in bytes. + */ + sos_size_t block_size; + + + /** + * Size of the device, in blocks + */ + sos_lcount_t number_of_blocks; + + + /** + * For a partition: reference to the master disk and index of the + * first block in it + */ + struct sos_blockdev_instance *parent_blockdev; + sos_luoffset_t index_of_first_block; + + /** Major/minor for the device */ + struct sos_fs_dev_id_t dev_id; + + struct sos_blockdev_operations * operations; + + /** + * Cache of blocks for this device + * @note Shared between a disk and all its (sub-(sub-(...)))partitions + */ + struct sos_block_cache * blk_cache; + + /** + * Cache of mapped pages for this device + * + * @note Proper to each disk and every + * (sub-(sub-(...)))partitions. It cannot be shared because the + * partitions on a disk may not start on a page boundary... but on a + * sector boundary. + */ + struct sos_fs_pagecache * map_cache; + + /* Yes, a block device can be mapped ! */ + struct sos_umem_vmm_mapped_resource mapres; + + void * custom_data; + + sos_count_t ref_cnt; + struct sos_blockdev_instance *next, *prev; +}; + + +/** The list of all block devices registered */ +static struct sos_blockdev_instance *registered_blockdev_instances; + + +/** Last UID delivered for the FS instances */ +static sos_ui64_t last_fs_instance_uid; + + +/* Forward declarations */ +static sos_ret_t +blockdev_sync_dirty_page(sos_luoffset_t offset, + sos_vaddr_t dirty_page, + void * custom_data); + +static sos_ret_t +blockdev_helper_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of); +static sos_ret_t +blockdev_helper_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of); + +static sos_ret_t +duplicate_opened_blockdev(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result_of); + +static sos_ret_t blockdev_new_mapping(struct sos_umem_vmm_vr *); + +static struct sos_fs_ops_opened_file blockdev_ops_opened_file; +static struct sos_fs_ops_opened_blockdev blockdev_ops_opened_blockdev; + + +/** The thread routine called to flush the cache contents to disk */ +static void bdflush_thread(void * unused) __attribute__((noreturn)); +static void bdflush_thread(void * unused) +{ + while (1) + { + struct sos_time t = (struct sos_time) { .sec=30, .nanosec=0 }; + sos_thread_sleep(&t); + sos_blockdev_sync_all_devices(); + } +} + + +sos_ret_t sos_blockdev_subsystem_setup() +{ + sos_ret_t retval; + last_fs_instance_uid = 42; + + retval = sos_blkcache_subsystem_setup(); + if (SOS_OK != retval) + return retval; + + /* Create the bdflush kernel thread */ + if (NULL == sos_create_kernel_thread("bdflush", + bdflush_thread, + NULL, + SOS_SCHED_PRIO_TS_LOWEST)) + return -SOS_ENOMEM; + + return SOS_OK; +} + + +/** Helper function used to increment the reference count for the + device */ +static sos_ret_t +blockdev_use_instance(struct sos_blockdev_instance * blockdev) +{ + SOS_ASSERT_FATAL(blockdev != NULL); + SOS_ASSERT_FATAL(blockdev->ref_cnt > 0); + blockdev->ref_cnt ++; + return SOS_OK; +} + + +/** Helper function used to decrement the reference count for the + device */ +sos_ret_t +sos_blockdev_release_instance(struct sos_blockdev_instance * blockdev) +{ + SOS_ASSERT_FATAL(blockdev->ref_cnt > 1); + blockdev->ref_cnt --; + return SOS_OK; +} + + +/** + * Return the device instance structure for the corresponding device + * class/instance, or NULL when none found. + */ +static struct sos_blockdev_instance* +lookup_blockdev_instance(sos_ui32_t device_class, sos_ui32_t device_instance) +{ + struct sos_blockdev_instance * blockdev; + int nb; + + list_foreach (registered_blockdev_instances, blockdev, nb) + { + if (blockdev->dev_id.device_class != device_class) + continue; + if (blockdev->dev_id.device_instance != device_instance) + continue; + + return blockdev; + } + + return NULL; +} + + +sos_ret_t +sos_blockdev_register_disk (sos_ui32_t device_class, + sos_ui32_t device_instance, + sos_size_t block_size, + sos_lcount_t number_of_blocks, + sos_count_t blkcache_size_in_blocks, + struct sos_blockdev_operations * blockdev_ops, + void * blockdev_instance_custom_data) +{ + struct sos_blockdev_instance * blockdev; + + blockdev = lookup_blockdev_instance(device_class, device_instance); + if (NULL != blockdev) + return -SOS_EBUSY; + + if (block_size <= 0) + return -SOS_EINVAL; + if (number_of_blocks <= 0) + return -SOS_EINVAL; + + blockdev = (struct sos_blockdev_instance*) + sos_kmalloc(sizeof(struct sos_blockdev_instance), 0); + if (NULL == blockdev) + return -SOS_ENOMEM; + + /* Create a new blk_cache */ + blockdev->blk_cache = sos_blkcache_new_cache(blockdev_instance_custom_data, + block_size, + blkcache_size_in_blocks, + blockdev_ops); + if (NULL == blockdev->blk_cache) + { + sos_kfree((sos_vaddr_t) blockdev); + return -SOS_ENOMEM; + } + + /* Create a new page cache for pages mapped */ + blockdev->map_cache + = sos_fs_pagecache_new_cache((sos_fs_pagecache_sync_function_t) + blockdev_sync_dirty_page, + (void*)blockdev); + if (NULL == blockdev->map_cache) + { + sos_blkcache_delete_cache(blockdev->blk_cache); + sos_kfree((sos_vaddr_t) blockdev); + return -SOS_ENOMEM; + } + + /* Description of the device */ + blockdev->dev_id.device_class = device_class; + blockdev->dev_id.device_instance = device_instance; + + /* Description of the storage */ + blockdev->block_size = block_size; + blockdev->number_of_blocks = number_of_blocks; + blockdev->parent_blockdev = NULL; + blockdev->index_of_first_block = 0; + + /* Prepare the blkcache related stuff */ + blockdev->operations = blockdev_ops; + blockdev->custom_data = blockdev_instance_custom_data; + + /* Prepare the mmap related stuff */ + blockdev->mapres.allowed_access_rights = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + blockdev->mapres.flags = 0; + list_init(blockdev->mapres.list_vr); + blockdev->mapres.custom_data = (void*)blockdev; + blockdev->mapres.mmap = blockdev_new_mapping; + + blockdev->ref_cnt = 1; + + blockdev->uid = last_fs_instance_uid ++; + list_add_tail(registered_blockdev_instances, blockdev); + + return SOS_OK; +} + + +sos_ret_t +sos_blockdev_register_partition(sos_ui32_t device_class, + sos_ui32_t device_instance, + struct sos_blockdev_instance * parent_bd, + sos_luoffset_t index_of_first_block, + sos_lcount_t number_of_blocks, + void * blockdev_instance_custom_data) +{ + struct sos_blockdev_instance * blockdev; + + if (NULL == parent_bd) + return -SOS_EINVAL; + + /* Make sure this partitions fits in parent partition or disk */ + if (index_of_first_block + number_of_blocks > + parent_bd->number_of_blocks) + return -SOS_EINVAL; + + blockdev = lookup_blockdev_instance(device_class, device_instance); + if (NULL != blockdev) + return -SOS_EBUSY; + + blockdev = (struct sos_blockdev_instance*) + sos_kmalloc(sizeof(struct sos_blockdev_instance), 0); + if (NULL == blockdev) + return -SOS_ENOMEM; + + /* Create the page cache for the partition */ + blockdev->map_cache + = sos_fs_pagecache_new_cache((sos_fs_pagecache_sync_function_t) + blockdev_sync_dirty_page, + (void*)blockdev); + if (NULL == blockdev->map_cache) + { + sos_kfree((sos_vaddr_t) blockdev); + return -SOS_ENOMEM; + } + + /* Increase parent's reference count */ + blockdev_use_instance(parent_bd); + + /* Description of the device */ + blockdev->dev_id.device_class = device_class; + blockdev->dev_id.device_instance = device_instance; + + /* Description of the storage */ + blockdev->block_size = parent_bd->block_size; + blockdev->number_of_blocks = number_of_blocks; + blockdev->parent_blockdev = parent_bd; + blockdev->index_of_first_block + = parent_bd->index_of_first_block + index_of_first_block; + + /* Prepare the blkcache related stuff */ + blockdev->operations = parent_bd->operations; + blockdev->blk_cache = parent_bd->blk_cache; + blockdev->custom_data = blockdev_instance_custom_data; + + /* Prepare the mmap related stuff */ + blockdev->mapres.allowed_access_rights = SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC; + blockdev->mapres.flags = 0; + list_init(blockdev->mapres.list_vr); + blockdev->mapres.custom_data = (void*)blockdev; + blockdev->mapres.mmap = blockdev_new_mapping; + + blockdev->ref_cnt = 1; + + blockdev->uid = last_fs_instance_uid ++; + list_add_tail(registered_blockdev_instances, blockdev); + + return SOS_OK; +} + + +sos_ret_t sos_blockdev_unregister_device (sos_ui32_t device_class, + sos_ui32_t device_instance) +{ + struct sos_blockdev_instance * blockdev; + + blockdev = lookup_blockdev_instance(device_class, device_instance); + if (NULL == blockdev) + return -SOS_ENODEV; + + if (blockdev->ref_cnt != 1) + return -SOS_EBUSY; + + /* Unallocate the page cache */ + sos_fs_pagecache_delete_cache(blockdev->map_cache); + + /* Unreference parent block device, if any */ + if (NULL != blockdev->parent_blockdev) + { + blockdev->parent_blockdev->ref_cnt --; + } + else + { + /* Otherwise: we are the parent BD => unallocate the block cache */ + sos_blkcache_delete_cache(blockdev->blk_cache); + } + + list_delete(registered_blockdev_instances, blockdev); + return sos_kfree((sos_vaddr_t)blockdev); +} + + +/** Generic read function working both with kernel and userspace + addresses. The pagecache is tried first, and the block cache if + the page cache does not have the data yet */ +static sos_ret_t +blockdev_generic_read(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset_in_device, + sos_genaddr_t buff_addr, + sos_size_t * /* in/out */len, + sos_bool_t bypass_pagecache) +{ + sos_size_t rdbytes = 0; + + while (rdbytes < *len) + { + sos_ret_t retval; + sos_size_t offset_in_block, wrbytes; + /* Get the block at the current offset */ + sos_luoffset_t block_id + = offset_in_device / blockdev->block_size; + SOS_GENADDR_DECL(gaddr, buff_addr.is_user, buff_addr.addr + rdbytes); + + /* reaching the end of the device ? */ + if (block_id >= blockdev->number_of_blocks) + break; + + wrbytes = *len - rdbytes; + + /* Trying to get (part of) the remaining from page cache */ + if (! bypass_pagecache) + retval = sos_fs_pagecache_read(blockdev->map_cache, + offset_in_device, + gaddr, + & wrbytes); + else + retval = -SOS_ENOENT; + + if (-SOS_ENOENT != retval) + { + /* (partial) success ! */ + rdbytes += wrbytes; + offset_in_device += wrbytes; + + if (SOS_OK == retval) + continue; /* Complete block read */ + else + break; /* Partial read */ + } + + /* Translating this block index into an offset inside the + disk */ + block_id += blockdev->index_of_first_block; + + /* Retrieve the block from block cache */ + sos_vaddr_t block_data; + struct sos_block_cache_entry * bkcache_entry + = sos_blkcache_retrieve_block(blockdev->blk_cache, + block_id, + SOS_BLKCACHE_READ_ONLY, + & block_data); + if (NULL == bkcache_entry) + break; + + /* Copy the data to user */ + offset_in_block + = offset_in_device % blockdev->block_size; + wrbytes + = blockdev->block_size - offset_in_block; + if (*len - rdbytes < wrbytes) + wrbytes = *len - rdbytes; + + retval = sos_memcpy_generic_to(gaddr, + block_data + offset_in_block, + wrbytes); + + /* Release this block back to the cache */ + sos_blkcache_release_block(blockdev->blk_cache, + bkcache_entry, + FALSE, FALSE); + if (retval > 0) + { + rdbytes += retval; + offset_in_device += retval; + } + if (retval != (sos_ret_t)wrbytes) + break; /* Partial read */ + } + + *len = rdbytes; + return SOS_OK; +} + + +/** Generic write function working both with kernel and userspace + addresses. The pagecache is tried first, and the block cache if + the page cache does not have the data yet */ +static sos_ret_t +blockdev_generic_write(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset_in_device, + sos_genaddr_t buff_addr, + sos_size_t * /* in/out */len, + sos_bool_t synchronous_write, + sos_bool_t bypass_pagecache) +{ + sos_size_t wrbytes = 0; + + while (wrbytes < *len) + { + sos_ret_t retval; + sos_size_t offset_in_block, usrbytes; + sos_blkcache_access_type_t access_type; + + /* Get the block at the current file offset */ + sos_luoffset_t block_id + = offset_in_device / blockdev->block_size; + SOS_GENADDR_DECL(gaddr, buff_addr.is_user, buff_addr.addr + wrbytes); + + /* reaching the end of the device ? */ + if (block_id >= blockdev->number_of_blocks) + break; + + usrbytes = *len - wrbytes; + + /* Trying to write (part of) the remaining into page cache */ + if (! bypass_pagecache) + retval = sos_fs_pagecache_write(blockdev->map_cache, + offset_in_device, + gaddr, + & usrbytes, + synchronous_write); + else + retval = -SOS_ENOENT; + + if (-SOS_ENOENT != retval) + { + /* Success ! */ + wrbytes += usrbytes; + offset_in_device += usrbytes; + + if (SOS_OK == retval) + continue; + else + break; /* Partial write */ + } + + /* Translating this block index into an offset inside the + disk */ + block_id += blockdev->index_of_first_block; + + /* Compute size of data to copy */ + offset_in_block + = offset_in_device % blockdev->block_size; + usrbytes + = blockdev->block_size - offset_in_block; + if (*len - wrbytes < usrbytes) + usrbytes = *len - wrbytes; + + if (usrbytes != blockdev->block_size) + /* Write partial block contents */ + access_type = SOS_BLKCACHE_READ_WRITE; + else + /* Overwrite full block ! */ + access_type = SOS_BLKCACHE_WRITE_ONLY; + + /* Retrieve the block from block cache */ + sos_vaddr_t block_data; + struct sos_block_cache_entry * bkcache_entry + = sos_blkcache_retrieve_block(blockdev->blk_cache, + block_id, access_type, + & block_data); + if (NULL == bkcache_entry) + break; + + /* Copy the data to user */ + retval = sos_memcpy_generic_from(block_data + offset_in_block, + gaddr, + usrbytes); + + /* Release this block back to the blk_cache and/or to disk */ + sos_blkcache_release_block(blockdev->blk_cache, + bkcache_entry, + TRUE, + synchronous_write); + + if (retval > 0) + { + wrbytes += retval; + offset_in_device += retval; + } + if (retval != (sos_ret_t)usrbytes) + break; /* Partial write */ + } + + *len = wrbytes; + return SOS_OK; +} + + +struct sos_blockdev_instance * +sos_blockdev_ref_instance(sos_ui32_t device_class, + sos_ui32_t device_instance) +{ + struct sos_blockdev_instance * blockdev; + blockdev = lookup_blockdev_instance(device_class, + device_instance); + if (NULL == blockdev) + return NULL; + + blockdev_use_instance(blockdev); + return blockdev; +} + + +sos_ret_t sos_blockdev_kernel_read(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset, + sos_vaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + SOS_GENADDR_DECL(gaddr, FALSE, dest_buf); + + blockdev_use_instance(blockdev); + retval = blockdev_generic_read(blockdev, offset, gaddr, len, FALSE); + sos_blockdev_release_instance(blockdev); + + return retval; +} + + +sos_ret_t sos_blockdev_kernel_write(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset, + sos_vaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + SOS_GENADDR_DECL(gaddr, FALSE, src_buf); + + blockdev_use_instance(blockdev); + retval = blockdev_generic_write(blockdev, offset, gaddr, len, FALSE, FALSE); + sos_blockdev_release_instance(blockdev); + + return retval; +} + + +/** Callback called by the pagecache to transfer a mapped page back to + disk */ +static sos_ret_t +blockdev_sync_dirty_page(sos_luoffset_t offset, + sos_vaddr_t dirty_page, + void * custom_data) +{ + sos_ret_t retval; + struct sos_blockdev_instance * blockdev + = (struct sos_blockdev_instance*) custom_data; + sos_size_t len = SOS_PAGE_SIZE; + + SOS_GENADDR_DECL(gaddr, FALSE, dirty_page); + + blockdev_use_instance(blockdev); + retval = blockdev_generic_write(blockdev, offset, gaddr, &len, FALSE, TRUE); + sos_blockdev_release_instance(blockdev); + + if (SOS_OK != retval) + return retval; + + if (SOS_PAGE_SIZE != len) + return -SOS_EIO; + + return SOS_OK; +} + + +sos_ret_t sos_blockdev_sync(struct sos_blockdev_instance * blockdev) +{ + sos_ret_t retval_bc, retval_pc; + + blockdev_use_instance(blockdev); + retval_pc = sos_fs_pagecache_sync(blockdev->map_cache); + retval_bc = sos_blkcache_flush(blockdev->blk_cache); + sos_blockdev_release_instance(blockdev); + + if (SOS_OK == retval_bc) + return retval_pc; + return retval_bc; +} + + +sos_ret_t sos_blockdev_sync_all_devices() +{ + int dummy = 0; + sos_ui64_t uid = 0; + + /* This scan will be exhaustive and resilient to addition/removal of + devices as long as new devices are added with list_add_tail + (because the scan is "forward", ie in order head -> tail) */ + while (1) + { + /* Iterate over the block devices */ + struct sos_blockdev_instance *blockdev; + int nbd; + + /* As long as we don't block, we can safely access the + prev/next fields of the device instance */ + list_foreach_forward(registered_blockdev_instances, blockdev, nbd) + { + if (blockdev->uid <= uid) + continue; + + uid = blockdev->uid; + sos_blockdev_sync(blockdev); + + /* We must NOT continue the loops because the + prev/next/current fs types/instances might have been + removed or added (sync blocks, by definition) ! */ + goto lookup_next_bd; + } + + /* Reached the end of the list */ + break; + + lookup_next_bd: + /* Loop over */ + dummy ++; + } + + return SOS_OK; +} + + +/* *************************** + * Callbacks needed by the VFS + */ + +sos_ret_t sos_blockdev_helper_ref_new_fsnode(struct sos_fs_node * this) +{ + struct sos_blockdev_instance * blockdev; + + blockdev = sos_blockdev_ref_instance(this->dev_id.device_class, + this->dev_id.device_instance); + this->block_device = blockdev; + this->new_opened_file = blockdev_helper_new_opened_file; + this->close_opened_file = blockdev_helper_close_opened_file; + return SOS_OK; +} + + +sos_ret_t sos_blockdev_helper_release_fsnode(struct sos_fs_node * this) +{ + if (NULL != this->block_device) + return sos_blockdev_release_instance(this->block_device); + + return SOS_OK; +} + + +sos_ret_t sos_blockdev_helper_sync_fsnode(struct sos_fs_node * this) +{ + if (NULL != this->block_device) + return sos_blockdev_sync(this->block_device); + + return SOS_OK; +} + + +static sos_ret_t +blockdev_helper_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of) +{ + /* Lookup the character device description structure */ + struct sos_blockdev_instance * blockdev = this->block_device; + if (NULL == blockdev) + return -SOS_ENODEV; + + /* Allocate the new "open file" description structure */ + *result_of = (struct sos_fs_opened_file*) + sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); + if (NULL == *result_of) + return -SOS_ENOMEM; + + memset(*result_of, 0x0, sizeof(struct sos_fs_opened_file)); + + /* Initialize the read/write/seek callbacks */ + (*result_of)->owner = owner; + (*result_of)->open_flags = open_flags; + (*result_of)->ops_file = & blockdev_ops_opened_file; + (*result_of)->ops_blockdev = & blockdev_ops_opened_blockdev; + + /* Specify the duplicate method */ + (*result_of)->duplicate = duplicate_opened_blockdev; + + return SOS_OK; +} + + +static sos_ret_t +blockdev_helper_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of) +{ + return sos_kfree((sos_vaddr_t) of); +} + + +static sos_ret_t +duplicate_opened_blockdev(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result_of) +{ + /* Allocate the new "open file" description structure */ + *result_of = (struct sos_fs_opened_file*) + sos_kmalloc(sizeof(struct sos_fs_opened_file), 0); + if (NULL == *result_of) + return -SOS_ENOMEM; + + memcpy(*result_of, this, sizeof(struct sos_fs_opened_file)); + (*result_of)->owner = for_owner; + (*result_of)->direntry = NULL; + + return SOS_OK; +} + + +void * +sos_blockdev_get_instance_custom_data(struct sos_blockdev_instance * blockdev) +{ + return blockdev->custom_data; +} + + +/* + * FS generic block device wrapper functions + */ + +static sos_ret_t blockdev_wrap_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + /* Make sure the device is supported by this driver */ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + + /* Artificiallly update the position in the "file" */ + sos_lsoffset_t ref_offs; + sos_lsoffset_t dev_size + = fsnode->block_device->block_size + * fsnode->block_device->number_of_blocks; + + *result_position = this->position; + switch (whence) + { + case SOS_SEEK_SET: + ref_offs = 0; + break; + + case SOS_SEEK_CUR: + ref_offs = this->position; + break; + + case SOS_SEEK_END: + ref_offs = dev_size; + break; + + default: + return -SOS_EINVAL; + } + + /* Forbid accesses "before" the start of the device */ + if (offset < -ref_offs) + return -SOS_EINVAL; + + /* Forbid accesses "after" the end of the device */ + else if (ref_offs + offset > dev_size) + return -SOS_EINVAL; + + this->position = ref_offs + offset; + *result_position = this->position; + return SOS_OK; +} + + +static sos_ret_t blockdev_wrap_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + struct sos_blockdev_instance * blockdev = fsnode->block_device; + + SOS_GENADDR_DECL(gaddr, TRUE, dest_buf); + sos_ret_t retval = blockdev_generic_read(blockdev, this->position, + gaddr, len, FALSE); + this->position += *len; + return retval; +} + + +static sos_ret_t blockdev_wrap_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + struct sos_blockdev_instance * blockdev = fsnode->block_device; + + SOS_GENADDR_DECL(gaddr, TRUE, src_buf); + sos_ret_t retval + = blockdev_generic_write(blockdev, this->position, + gaddr, len, + this->open_flags & SOS_FS_OPEN_SYNC, + FALSE); + this->position += *len; + return retval; +} + + +static sos_ret_t blockdev_wrap_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + struct sos_blockdev_instance * blockdev = fsnode->block_device; + + if (! SOS_IS_PAGE_ALIGNED(offset)) + return -SOS_EINVAL; + + return sos_umem_vmm_map(sos_process_get_address_space(this->owner), + uaddr, size, access_rights, + flags, & blockdev->mapres, offset); +} + + + +static sos_ret_t blockdev_wrap_fcntl(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg) +{ + return -SOS_ENOSYS; +} + + +static sos_ret_t blockdev_wrap_ioctl(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + struct sos_blockdev_instance * blockdev = fsnode->block_device; + + if (req_id == SOS_IOCTL_BLOCKDEV_SYNC) + return sos_blockdev_sync(blockdev); + + if (blockdev->operations->ioctl) + return blockdev->operations->ioctl(this, req_id, req_arg); + + return -SOS_ENOSYS; +} + + +static struct sos_fs_ops_opened_file blockdev_ops_opened_file + = (struct sos_fs_ops_opened_file) { + .seek = blockdev_wrap_seek, + .read = blockdev_wrap_read, + .write = blockdev_wrap_write, + .mmap = blockdev_wrap_mmap, + .fcntl = blockdev_wrap_fcntl + }; + + +static struct sos_fs_ops_opened_blockdev blockdev_ops_opened_blockdev + = (struct sos_fs_ops_opened_blockdev) { + .ioctl = blockdev_wrap_ioctl + }; + + +/* **************************** + * Callbacks needed by umem_vmm + */ + +inline static struct sos_blockdev_instance * +get_blockdev_of_vr(struct sos_umem_vmm_vr * vr) +{ + struct sos_umem_vmm_mapped_resource *mr + = sos_umem_vmm_get_mapped_resource_of_vr(vr); + + return (struct sos_blockdev_instance *)mr->custom_data; +} + + +static void blockdev_map_ref(struct sos_umem_vmm_vr * vr) +{ + struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); + blockdev_use_instance(blockdev); +} + + +static void blockdev_map_unref(struct sos_umem_vmm_vr * vr) +{ + struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); + sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); + sos_uaddr_t vr_size = sos_umem_vmm_get_size_of_vr(vr); + sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); + sos_ui32_t vr_flags = sos_umem_vmm_get_flags_of_vr(vr); + + /* For a shared mapping, un-reference the pagecache entries, if + any */ + if (vr_flags & SOS_VR_MAP_SHARED) + { + sos_uaddr_t uaddr; + for (uaddr = vr_start ; + uaddr < vr_start + vr_size ; + uaddr += SOS_PAGE_SIZE) + { + sos_paddr_t paddr; + sos_luoffset_t blockdev_offset; + paddr = sos_paging_get_paddr(uaddr); + if (! paddr) + /* No physical page mapped at this address yet */ + continue; + + /* Unreference the corresponding pagecache entry */ + blockdev_offset = SOS_PAGE_ALIGN_INF(uaddr) - vr_start; + blockdev_offset += start_offset; + + /* Flush any change to disk */ + if (sos_paging_is_dirty(paddr)) + sos_fs_pagecache_set_dirty(blockdev->map_cache, blockdev_offset, + FALSE); + + SOS_ASSERT_FATAL(SOS_OK == + sos_fs_pagecache_unref_page(blockdev->map_cache, + blockdev_offset)); + } + } + + sos_blockdev_release_instance(blockdev); +} + + +static sos_ret_t blockdev_map_page_in(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access) +{ + struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); + sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); + sos_uaddr_t vr_size = sos_umem_vmm_get_size_of_vr(vr); + sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); + sos_luoffset_t offset_in_device; + + sos_bool_t read_from_blkcache = TRUE; /**< Need to fill the page + from blkcache ? */ + sos_vaddr_t kernel_page; /** Use a (temporary) kernel-mapped page to + store the contents of the page */ + struct sos_fs_pagecache_entry * pagecache_entry = NULL; + sos_ret_t retval; + + SOS_ASSERT_FATAL(vr_size > 0); + + /* Make sure the access is within the block device limits */ + offset_in_device = uaddr - vr_start + sizeof(int) - 1; + offset_in_device += start_offset; + offset_in_device /= blockdev->block_size; + if (offset_in_device >= blockdev->number_of_blocks) + return -SOS_EFAULT; + + /* + * We have to map: + * - shared mappings: a fresh page from disk if it is not yet in the + * page cache + * - private mappings: a fresh page from disk (always) + */ + + /* Compute block index of beginning of page */ + offset_in_device = SOS_PAGE_ALIGN_INF(uaddr) - vr_start;; + offset_in_device += start_offset; + + if (sos_umem_vmm_get_flags_of_vr(vr) & SOS_VR_MAP_SHARED) + { + /* This is a shared mapping ! */ + sos_bool_t newly_allocated; + pagecache_entry = sos_fs_pagecache_ref_page(blockdev->map_cache, + offset_in_device, + & kernel_page, + & newly_allocated); + if (! pagecache_entry) + return -SOS_EFAULT; + + if (! newly_allocated) + read_from_blkcache = FALSE; + } + else + { + /* Allocate a temporary kernel page */ + kernel_page = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if ((sos_vaddr_t)NULL == kernel_page) + return -SOS_EFAULT; + } + + retval = SOS_OK; + + /* Need to fill the kernel page ? */ + if (read_from_blkcache) + { + SOS_GENADDR_DECL(gaddr, FALSE, kernel_page); + sos_size_t rdlen = SOS_PAGE_SIZE; + retval = blockdev_generic_read(blockdev, + offset_in_device, + gaddr, + & rdlen, + TRUE); + if (SOS_PAGE_SIZE != rdlen) + retval = -SOS_EIO; + } + + /* Make sure nobody mapped anything in the meantime */ + if ((sos_paddr_t)NULL != sos_paging_get_paddr(uaddr)) + retval = -SOS_EBUSY; + + if (SOS_OK == retval) + { + sos_paddr_t ppage_paddr = sos_paging_get_paddr(kernel_page); + if (0 != ppage_paddr) + retval = sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(uaddr), + TRUE, + sos_umem_vmm_get_prot_of_vr(vr)); + else + retval = -SOS_EFAULT; + } + + /* Shared mapping: unlock the entry and eventually remove it from + the cache in case of failure of the preceding operations */ + if (NULL != pagecache_entry) + { + sos_fs_pagecache_unlock_page(blockdev->map_cache, + pagecache_entry, + (SOS_OK != retval)); + } + /* Private mappings: remove the remporary kernel page */ + else + sos_kmem_vmm_free((sos_vaddr_t)kernel_page); + + return retval; +} + + +/** + * umem_vmm callback for msync + */ +static sos_ret_t blockdev_map_sync_page(struct sos_umem_vmm_vr * vr, + sos_uaddr_t page_uaddr, + sos_ui32_t flags) +{ + struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); + sos_uaddr_t vr_start = sos_umem_vmm_get_start_of_vr(vr); + sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); + sos_luoffset_t offset; + + /* Make sure the access is within the block device limits */ + offset = page_uaddr - vr_start; + offset += start_offset; + if (offset >= blockdev->number_of_blocks*blockdev->block_size) + return -SOS_EFAULT; + + /* Mark the page as non dirty now */ + sos_paging_set_dirty(page_uaddr, FALSE); + + /* Simply mark the corresponding pagecache entry as dirty */ + return sos_fs_pagecache_set_dirty(blockdev->map_cache, + offset, + flags & SOS_MSYNC_SYNC); +} + + +static struct sos_umem_vmm_vr_ops blockdev_map_ops + = (struct sos_umem_vmm_vr_ops){ + .ref = blockdev_map_ref, + .unref = blockdev_map_unref, + .page_in = blockdev_map_page_in, + .sync_page = blockdev_map_sync_page + }; + + +/** Callback called each time an area of the device is mapped into + user space */ +static sos_ret_t blockdev_new_mapping(struct sos_umem_vmm_vr * vr) +{ + struct sos_blockdev_instance * blockdev = get_blockdev_of_vr(vr); + sos_luoffset_t start_offset = sos_umem_vmm_get_offset_in_resource(vr); + sos_size_t map_size = sos_umem_vmm_get_size_of_vr(vr); + sos_luoffset_t stop_offset; + sos_luoffset_t block_index; + + if (map_size <= 0) + return -SOS_EINVAL; + + /* Make sure request lies inside the device */ + stop_offset = start_offset + map_size - 1; + block_index = stop_offset / blockdev->block_size; + if (block_index >= blockdev->number_of_blocks) + return -SOS_EINVAL; + + return sos_umem_vmm_set_ops_of_vr(vr, &blockdev_map_ops); +} + diff --git a/sos/blkdev.h b/sos/blkdev.h new file mode 100644 index 0000000..41deac5 --- /dev/null +++ b/sos/blkdev.h @@ -0,0 +1,225 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_BLKDEV_H_ +#define _SOS_BLKDEV_H_ + +/** + * @file blkdev.h + * + * Interface between the VFS and the "block" devices (real devices and + * their partitions). The following functions provide the mechanisms + * to bind the "block device" nodes (@see mknod) to their device + * driver. + * + * The "blkdev" layer is to be perceived as a FS-agnostic layer both + * below and on top of the FS that binds the special "block device" + * nodes to a set of system-wide block_read/block_write functions and + * which supports the cache of blocks for the device. + * + * The differences between the char device and the block device layers are: + * - A character device is byte-stream-oriented: one can easily fetch + * the data character by character. Theoretically, this stream has + * no limitation in size: it has a beginning but not necessarly an + * end. One can not necessarly rewind the stream, that is seek + * anywhere in it (as for a stream of audio data coming from a + * soundcard for example). But for some character devices, it is + * possible, though (it is possible to seek anywhere in /dev/zero + * for example). For some other devices, it is possible to seek only + * in some authorized regions (as for /dev/kmem for example: only + * the mapped kernel regions are available). + * - A block device is block-oriented: one can easily fetch fixed-size + * blocks of data anywhere inside the device, possibly in a random + * order. The capacity of the device is limited: there is a defined + * number of contiguous blocks reachable in the device, one cannot + * seek beyond its limits. + * - A block device is well suited to a cache layer: it is possible to + * cache the most used blocks inside memory to speed up the accesses + * to data. It doesn't make sense to do so on stream-oriented devices + * like character devices. + * - A character device is limited to the interaction between + * user-mode programs and the devices themselves. The kernel only acts + * as an intermediary and does not take care of the operations + * carried out. + * - A block device is used as the underlying data support for a + * filesystem. As a result, it must be managed by the kernel itself + * since the FS code resides inside the kernel. It must offer an API + * compatible with kernel-space interaction. + * + * A partition has the same properties as a disk: both are block + * devices. The main difference is that a disk really "owns" its + * operations (ie the read/write/ioctl operations are defined by him) + * and its block cache, whereas a partition shares them with its + * parent disk/partition. The partitions may be nested: a disk can + * have partitions, which can have sub-partitions, which can have + * sub-sub-partittions, etc. As a consequence, a parent disk/partition + * cannot be unregistered as long as it owns any child partitions. The + * only constraint being that the partitions must fit completely + * inside their parent partition/disk. However, no check is made + * regarding partitions overlapping one another... + * + * This implementation is based on two subsystems: + * - the block cache to accelerate accesses to the hardware by caching + * the most frequently used blocks in main memory + * - the page cache to guarantee consistency between read/write + * accesses and MMU access of mapped pages (mmap API). This is a + * simple dictionary of the device's mapped pages: offset -> mapped + * page. The read/write vs MMU consistency is achieved in a simple + * manner: before each read/write accesses, we first try to use the + * page cache, and eventually the block cache if the data is not + * mapped by anybody. + */ + +/* Forward declaration */ +struct sos_blockdev_instance; + +#include + +/** + * The fundamental callbacks for a real block device (ie not a + * partition). + */ +struct sos_blockdev_operations { + + /** @note MANDATORY */ + sos_ret_t (*read_block)(void * blockdev_instance_custom_data, + sos_vaddr_t dest_buf /* Kernel address */, + sos_luoffset_t block_offset); + + + /** @note Optional (may be NULL) */ + sos_ret_t (*write_block)(void * blockdev_instance_custom_data, + sos_vaddr_t src_buf /* Kernel address */, + sos_luoffset_t block_offset); + + + /** + * @note Optional (may be NULL) + * @note Also called when an ioctl is made to a partition + */ + sos_ret_t (*ioctl)(void * blockdev_instance_custom_data, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); +}; + + +/* + * Functions restricted to block device driver code + */ + + +sos_ret_t sos_blockdev_subsystem_setup(void); + + +/** + * Contrary to Character devices, block devices are registered + * individually: any single registration corresponds to a driver for a + * single device instance + */ +sos_ret_t +sos_blockdev_register_disk (sos_ui32_t device_class, + sos_ui32_t device_instance, + sos_size_t block_size, + sos_lcount_t number_of_blocks, + sos_count_t cache_size_in_blocks, + struct sos_blockdev_operations * blockdev_ops, + void * blockdev_instance_custom_data); + + +/** + * @param index_of_first_block is the index of the first block + * relative to parent_bd, NOT relative to the top-most disk block + * device + */ +sos_ret_t +sos_blockdev_register_partition(sos_ui32_t device_class, + sos_ui32_t device_instance, + struct sos_blockdev_instance * parent_bd, + sos_luoffset_t index_of_first_block, + sos_lcount_t number_of_blocks, + void * blockdev_instance_custom_data); + + +sos_ret_t sos_blockdev_unregister_device (sos_ui32_t device_class, + sos_ui32_t device_instance); + + +/** + * Flush all caches of all devices to disk + */ +sos_ret_t sos_blockdev_sync_all_devices(void); + + +/** + * Increments the instance's reference counter: this will make any + * blockdev_unregister return BUSY. As a consequence, the following + * operations on blockdev instances should be as fast as possible and + * call release_instance as early as possible + */ +struct sos_blockdev_instance * +sos_blockdev_ref_instance(sos_ui32_t device_class, + sos_ui32_t device_instance); + + +sos_ret_t +sos_blockdev_release_instance(struct sos_blockdev_instance * blockdev); + + +/** Read data from disk and use it directly in the kernel. Mostly used + by the partition drivers to identify the partitions of a disk and + register them */ +sos_ret_t sos_blockdev_kernel_read(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset, + sos_vaddr_t dest_buf, + sos_size_t * /* in/out */len); + + +/** + * Write data to disk directly from the kernel. Mostly used by the + * partition drivers to modify the partitions of a disk and register + * them. + * + * @note The operation is normally NOT needed (fdisk is a userspace + * program) and is NOT synchronous + */ +sos_ret_t sos_blockdev_kernel_write(struct sos_blockdev_instance * blockdev, + sos_luoffset_t offset, + sos_vaddr_t src_buf, + sos_size_t * /* in/out */len); + + +/** + * Flush the modified blocks back to hardware (both block and page + * cache are flusehd) + */ +sos_ret_t sos_blockdev_sync(struct sos_blockdev_instance * blockdev); + + +/* + * Callbacks and functions restricted to fs.c internals + */ + +/** + * Update the FS node ops_blockdev callbacks after an FS + * allocate_new_node or fetch_node_from_disk, in order to point to + * the block layer API functions + */ +sos_ret_t sos_blockdev_helper_ref_new_fsnode(struct sos_fs_node * this); +sos_ret_t sos_blockdev_helper_release_fsnode(struct sos_fs_node * this); +sos_ret_t sos_blockdev_helper_sync_fsnode(struct sos_fs_node * this); + +#endif diff --git a/sos/calcload.c b/sos/calcload.c new file mode 100644 index 0000000..5394fab --- /dev/null +++ b/sos/calcload.c @@ -0,0 +1,396 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + + +/** + * Multiplicative factor to display digits after the decimal dot. The + * higher the value, the higher the precision, but the higher the risk + * that the value you get is incorrect (integer overflow). + * + * The CPU ratios will be correctly displayed as long as: + * 2^32 > (900 * HZ * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR) + * The "900" above means 900s because the load is computed over 15mn (900s). + * HZ is the frequency of the timer tick because the load is updated + * at each timer tick. + * The "100" above is the multiplication factor to get the ratio value + * between 0 and 100 (instead of 0-1). + * + * The maximum CPU sustainable load that will be correctly displayed + * is given by the formula: + * (2^32 - 1) / (900 * HZ * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR) + * With HZ=100, these maximum sustainable loads are respectively + * 47.721, 477.21 and 4772.1 with + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR being respectively 1000, 100 + * and 10. + * + * Hence, among these formulaes, the most limitative one is that + * concerning the CPU ratios (because of the "100" factor). Actually, + * for HZ=100, the only correct value is 10. + */ +#define SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR 10 /* 1/10 resolution */ + + +/** + * To compute the load, at each clock tick we store the number of + * threads ready in kernel/user mode, and the kind of the thread that + * is executing (user or kernel mode): this is stored in + * current_load_entry. We then compute the sum of these numbers over 3 + * periods of time: 1 minute, 5 minutes, 15 minutes. This is the role + * of the sliding windows data structures. A "sliding window" is only + * the synthetic sum of these figures, not a real sliding window of + * load_entries. At each timer tick and everytime the load is updated, + * the computations are in O(1). + * + * All the sliding windows share the main "recorded_load" array of + * load_entries for that; its role is to store the last 15mns of load + * data, which encompasses the data for the 1mn, 5mn and 15mn sliding + * windows. + */ + +/* Max number of seconds that we record (ie number of entries in + recorded_loads) */ +#define NB_SECS 900 + + +/* An entry in the longest sliding window */ +struct load_entry +{ + sos_ui32_t nb_ticks; + + sos_ui32_t nb_user_running; + sos_ui32_t nb_kernel_running; + + sos_ui32_t nb_user_ready; + sos_ui32_t nb_kernel_ready; +}; + +struct load_entry current_load_entry; + + +/* The longest sliding window */ +struct recorded_loads +{ + sos_ui32_t most_recent; + sos_ui32_t max_entries; + + struct load_entry *load_entries; +}; + +#define LOAD_GET_ENTRY(loads,age) \ + (&((loads).load_entries[( (loads).max_entries + (loads).most_recent - (age))\ + % ((loads).max_entries)])) + +/* A sliding window, we manage one for each time interval */ +struct sliding_window +{ + sos_ui32_t max_entries; + sos_ui32_t nb_entries; + + sos_ui32_t sigma_nb_ticks; + sos_ui32_t sigma_nb_user_running; + sos_ui32_t sigma_nb_kernel_running; + sos_ui32_t sigma_nb_user_ready; + sos_ui32_t sigma_nb_kernel_ready; +}; + + +/* The main sliding window */ +static struct recorded_loads recorded_loads; + +/* The sliding windows for 3 tims intervals: 1min, 5min, 15min */ +static struct sliding_window load_1mn, load_5mn, load_15mn; + +/* Forward declaration */ +static struct sos_timeout_action calcload_timeout; +static void calcload_routine(struct sos_timeout_action *a); + + +static void _reinit_load_subsystem(void) +{ + memset(& recorded_loads, 0x0, sizeof(recorded_loads)); + memset(& current_load_entry, 0x0, sizeof(struct load_entry)); + memset(& load_1mn, 0x0, sizeof(load_1mn)); + memset(& load_5mn, 0x0, sizeof(load_5mn)); + memset(& load_15mn, 0x0, sizeof(load_15mn)); +} + + +sos_ret_t sos_load_subsystem_setup(void) +{ + struct sos_time period; + _reinit_load_subsystem(); + + if (recorded_loads.load_entries) + sos_kfree((sos_vaddr_t) recorded_loads.load_entries); + _reinit_load_subsystem(); + + /* Allocate 900 entries to store 15mn of data (because 15minutes = + 900s) */ + recorded_loads.max_entries = NB_SECS; + recorded_loads.load_entries + = (struct load_entry*) sos_kmalloc(NB_SECS * sizeof(struct load_entry), + 0); + if (! recorded_loads.load_entries) + { + return -SOS_ENOMEM; + } + + /* Compute the number of entries in each sliding window */ + load_1mn.max_entries = 60; + load_5mn.max_entries = 300; + load_15mn.max_entries = 900; + + /* Program the load computation action */ + sos_time_init_action(& calcload_timeout); + period.sec = 1; period.nanosec = 0; + return sos_time_register_action_relative(& calcload_timeout, + & period, + calcload_routine, + NULL); +} + + +/* Shift the given sliding window to record the current_load_entry */ +static void update_sliding_window(struct sliding_window *w) +{ + /* + * Compute the value of the sum over the sliding window + */ + + /* Take the new value into account */ + w->sigma_nb_ticks += current_load_entry.nb_ticks; + w->sigma_nb_user_running += current_load_entry.nb_user_running; + w->sigma_nb_kernel_running += current_load_entry.nb_kernel_running; + w->sigma_nb_user_ready += current_load_entry.nb_user_ready; + w->sigma_nb_kernel_ready += current_load_entry.nb_kernel_ready; + + /* Remove the oldest entry, if it is going to be popped out of the + sliding window */ + if (w->nb_entries < w->max_entries) + { + w->nb_entries ++; + } + else + { + struct load_entry * oldest_entry; + oldest_entry = LOAD_GET_ENTRY(recorded_loads, w->nb_entries - 1); + w->sigma_nb_ticks -= oldest_entry->nb_ticks; + w->sigma_nb_user_running -= oldest_entry->nb_user_running; + w->sigma_nb_kernel_running -= oldest_entry->nb_kernel_running; + w->sigma_nb_user_ready -= oldest_entry->nb_user_ready; + w->sigma_nb_kernel_ready -= oldest_entry->nb_kernel_ready; + } +} + + +/* The timeout action responsible for computing the CPU load */ +static void calcload_routine(struct sos_timeout_action *a) +{ + struct load_entry * new_head; + struct sos_time delay; + + if (! recorded_loads.load_entries) + return; + + /* Update the sliding windows */ + update_sliding_window(& load_1mn); + update_sliding_window(& load_5mn); + update_sliding_window(& load_15mn); + + /* Move the head of the list forward */ + recorded_loads.most_recent + = (recorded_loads.most_recent + 1) % recorded_loads.max_entries; + + /* Update the new head */ + new_head = & recorded_loads.load_entries[recorded_loads.most_recent]; + memcpy(new_head, & current_load_entry, sizeof(current_load_entry)); + + /* Reset the current load entry */ + memset(& current_load_entry, 0x0, sizeof(current_load_entry)); + + /* Program next occurence of the action */ + delay.sec = 1; + delay.nanosec = 0; + sos_time_register_action_relative(a, & delay, calcload_routine, NULL); +} + + +sos_ret_t sos_load_do_timer_tick(sos_bool_t cur_is_user, + sos_ui32_t nb_user_ready, + sos_ui32_t nb_kernel_ready) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + current_load_entry.nb_ticks ++; + current_load_entry.nb_user_ready += nb_user_ready; + current_load_entry.nb_kernel_ready += nb_kernel_ready; + if (cur_is_user) + current_load_entry.nb_user_running ++; + else + current_load_entry.nb_kernel_running ++; + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +void sos_load_to_string(char dest[11], sos_ui32_t load_value) +{ + sos_bool_t print0 = FALSE; + sos_ui32_t d; + +#define PUTCH(c) ({ *dest = (c); dest ++; }) + + for (d = 1000000000UL ; d > 0 ; d /= 10) + { + sos_ui32_t digit = (load_value / d) % 10; + + if (digit > 0) + { + PUTCH(digit + '0'); + print0 = TRUE; + } + else if (print0) + PUTCH('0'); + + if (d == SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR) + { + if (! print0) + PUTCH('0'); + + PUTCH('.'); + print0 = TRUE; + } + } + *dest = '\0'; +} + + +void sos_load_get_uload(sos_ui32_t * _load_1mn, + sos_ui32_t * _load_5mn, + sos_ui32_t * _load_15mn) +{ + sos_ui32_t flags; + + if (load_1mn.sigma_nb_ticks < 1) + return; + + sos_disable_IRQs(flags); + *_load_1mn = ( load_1mn.sigma_nb_user_ready + + load_1mn.sigma_nb_user_running) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_1mn.sigma_nb_ticks; + *_load_5mn = ( load_5mn.sigma_nb_user_ready + + load_5mn.sigma_nb_user_running) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_5mn.sigma_nb_ticks; + *_load_15mn = ( load_15mn.sigma_nb_user_ready + + load_15mn.sigma_nb_user_running) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_15mn.sigma_nb_ticks; + sos_restore_IRQs(flags); +} + + +void sos_load_get_sload(sos_ui32_t * _load_1mn, + sos_ui32_t * _load_5mn, + sos_ui32_t * _load_15mn) +{ + sos_ui32_t flags; + + if (load_1mn.sigma_nb_ticks < 1) + return; + + /* The "IDLE" thread is always either ready or running by definition */ + SOS_ASSERT_FATAL(load_1mn.sigma_nb_kernel_ready + + load_1mn.sigma_nb_kernel_running + >= load_1mn.sigma_nb_ticks); + + /* Remove the IDLE thread from the load calculation */ + sos_disable_IRQs(flags); + *_load_1mn = ( load_1mn.sigma_nb_kernel_ready + + load_1mn.sigma_nb_kernel_running + - load_1mn.sigma_nb_ticks) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_1mn.sigma_nb_ticks; + *_load_5mn = ( load_5mn.sigma_nb_kernel_ready + + load_5mn.sigma_nb_kernel_running + - load_5mn.sigma_nb_ticks) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_5mn.sigma_nb_ticks; + *_load_15mn = ( load_15mn.sigma_nb_kernel_ready + + load_15mn.sigma_nb_kernel_running + - load_15mn.sigma_nb_ticks) + * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_15mn.sigma_nb_ticks; + sos_restore_IRQs(flags); +} + + +void sos_load_get_uratio(sos_ui32_t * _load_1mn, + sos_ui32_t * _load_5mn, + sos_ui32_t * _load_15mn) +{ + sos_ui32_t flags; + + if (load_1mn.sigma_nb_ticks < 1) + return; + + sos_disable_IRQs(flags); + *_load_1mn = load_1mn.sigma_nb_user_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_1mn.sigma_nb_ticks; + *_load_5mn = load_5mn.sigma_nb_user_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_5mn.sigma_nb_ticks; + *_load_15mn = load_15mn.sigma_nb_user_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_15mn.sigma_nb_ticks; + sos_restore_IRQs(flags); +} + + +void sos_load_get_sratio(sos_ui32_t * _load_1mn, + sos_ui32_t * _load_5mn, + sos_ui32_t * _load_15mn) +{ + sos_ui32_t flags; + + if (load_1mn.sigma_nb_ticks < 1) + return; + + /* Don't remove the CPU occupation ration of the IDLE thread + here... */ + sos_disable_IRQs(flags); + *_load_1mn = load_1mn.sigma_nb_kernel_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_1mn.sigma_nb_ticks; + *_load_5mn = load_5mn.sigma_nb_kernel_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_5mn.sigma_nb_ticks; + *_load_15mn = load_15mn.sigma_nb_kernel_running + * 100 * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + / load_15mn.sigma_nb_ticks; + sos_restore_IRQs(flags); +} diff --git a/sos/calcload.h b/sos/calcload.h new file mode 100644 index 0000000..7d63f7d --- /dev/null +++ b/sos/calcload.h @@ -0,0 +1,117 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_CPULOAD_H_ +#define _SOS_CPULOAD_H_ + +#include +#include +#include + + +/** + * @file calcload.h + * + * Management of the CPU load in the system. For three intervals + * (1min, 5min, 15min), we maintain the user/kernel loads (ie number + * of threads in user/kernel mode ready or running) and the + * user/kernel CPU occupation ratio. + */ + + +/** + * Reinitialize the calcload subsystem. Must be called after the time + * subsystem has been initialized + */ +sos_ret_t sos_load_subsystem_setup(void); + + +/** + * Get the current USER load for each of the intervals. Definition: + * the USER load is the mean number of threads in USER mode which are + * ready or running over the period. + * + * @return the current USER load * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + */ +void sos_load_get_uload(sos_ui32_t * load_1mn, + sos_ui32_t * load_5mn, + sos_ui32_t * load_15mn); + + +/** + * Get the current KERNEL load for each of the intervals. Definition: + * the KERNEL load is the mean number of threads in KERNEL mode which are + * ready or running over the period. + * + * @note The load of the IDLE thread is removed from this computation ! + * + * @return the current KERNEL load * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + */ +void sos_load_get_sload(sos_ui32_t * load_1mn, + sos_ui32_t * load_5mn, + sos_ui32_t * load_15mn); + + +/** + * Get the current User CPU occupation ratio + * + * @return the current User/Kernel CPU occupation ratio + * * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + */ +void sos_load_get_uratio(sos_ui32_t * load_1mn, + sos_ui32_t * load_5mn, + sos_ui32_t * load_15mn); + + +/** + * Get the current Kernel CPU occupation ratio + * + * @note The load of the IDLE thread is NOT removed from this computation ! + * + * @return the current User/Kernel CPU occupation ratio + * * SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR + */ +void sos_load_get_sratio(sos_ui32_t * load_1mn, + sos_ui32_t * load_5mn, + sos_ui32_t * load_15mn); + + +/** + * Generate the "dest" string with the string corresponding to + * load_value / SOS_LOAD_DISPLAY_MULTIPLICATION_FACTOR with decimal + * digits + */ +void sos_load_to_string(char dest[11], sos_ui32_t load_value); + + +/* ****************************************************** + * Restricted function. Used only by sched.c/time.c + */ + + +/** + * Restricted callback called from the scheduler subsystem to update + * the load parameters. + * + * @note This is RESTRICTED function to be used by time.c + */ +sos_ret_t sos_load_do_timer_tick(sos_bool_t cur_is_user, + sos_ui32_t nb_user_ready, + sos_ui32_t nb_kernel_ready); + + +#endif /* _SOS_CPULOAD_H_ */ diff --git a/sos/chardev.c b/sos/chardev.c new file mode 100644 index 0000000..c2a78e3 --- /dev/null +++ b/sos/chardev.c @@ -0,0 +1,463 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "chardev.h" + +struct sos_chardev_class +{ + sos_ui32_t device_class; + struct sos_chardev_ops *ops; + + /** This corresponds to the chardev_class_custom_data field passed + to open/read/etc. and to sos_chardev_register_class() */ + void *custom_data; + + sos_count_t ref_cnt; /**< increased each time a FS + node is opened */ + + struct sos_chardev_class *next, *prev; +}; + + +struct sos_chardev_opened_file +{ + /** "normal" VFS opened file structure is available in this + structure */ + struct sos_fs_opened_file super; + + /** Additional information for this opened file: the device's class + structure */ + struct sos_chardev_class *class; +}; + + +/** The list of registered classes, ie the dictionary major number -> + device class description */ +static struct sos_chardev_class *registered_chardev_classes; + + +/* Forward declarations */ +static struct sos_fs_ops_opened_file chardev_ops_opened_file; +static struct sos_fs_ops_opened_chardev chardev_ops_opened_chardev; + +static sos_ret_t +chardev_helper_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of); +static sos_ret_t +chardev_helper_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of); +static sos_ret_t +duplicate_opened_chardev(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result); + + +/** + * Return the device descriptionn structure for the corresponding + * device class, or NULL when none found. + */ +static struct sos_chardev_class * lookup_chardev_class(sos_ui32_t device_class) +{ + struct sos_chardev_class *chardev; + int nb; + + list_foreach (registered_chardev_classes, chardev, nb) + { + if (chardev->device_class == device_class) + return chardev; + } + + return NULL; +} + + +sos_ret_t sos_chardev_register_class (sos_ui32_t device_class, + struct sos_chardev_ops *ops, + void * chardev_class_custom_data) +{ + struct sos_chardev_class *chardev; + + /* Make sure this device class is not already registered */ + chardev = lookup_chardev_class(device_class); + if (NULL != chardev) + return -SOS_EBUSY; + + /* Allocate and initialize a new device description */ + chardev = (struct sos_chardev_class *) + sos_kmalloc(sizeof(struct sos_chardev_class), 0); + if (chardev == NULL) + return -SOS_ENOMEM; + + chardev->device_class = device_class; + chardev->custom_data = chardev_class_custom_data; + chardev->ops = ops; + chardev->ref_cnt = 1; + + /* insert it into the list */ + list_add_tail (registered_chardev_classes, chardev); + + return SOS_OK; +} + + +sos_ret_t sos_chardev_unregister_class (sos_ui32_t device_class) +{ + struct sos_chardev_class *chardev; + + /* Make sure this device class is already registered */ + chardev = lookup_chardev_class(device_class); + if (NULL == chardev) + return -SOS_ENODEV; + + /* Make sure no files are already opened for it */ + if (chardev->ref_cnt != 1) + return -SOS_EBUSY; + + /* remove it from the list */ + list_delete (registered_chardev_classes, chardev); + return sos_kfree((sos_vaddr_t)chardev); +} + + + +sos_ret_t sos_chardev_helper_ref_new_fsnode(struct sos_fs_node * this) +{ + this->new_opened_file = chardev_helper_new_opened_file; + this->close_opened_file = chardev_helper_close_opened_file; + + return SOS_OK; +} + + +sos_ret_t sos_chardev_helper_release_fsnode(struct sos_fs_node * this) +{ + return SOS_OK; +} + + +/** Callback called each time an FS-node is opened by a user process: + create a new sos_chardev_opened_file object associated to the + correct major number, and call the device driver's open method */ +static sos_ret_t +chardev_helper_new_opened_file(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of) +{ + sos_ret_t retval; + struct sos_chardev_opened_file *chardev_of; + + /* Lookup the character device description structure */ + struct sos_chardev_class * chardev + = lookup_chardev_class(this->dev_id.device_class); + if (NULL == chardev) + return -SOS_ENODEV; + + /* Alloocate the new "open file" description structure */ + chardev_of = (struct sos_chardev_opened_file*) + sos_kmalloc(sizeof(struct sos_chardev_opened_file), 0); + if (NULL == chardev_of) + return -SOS_ENOMEM; + + memset(chardev_of, 0x0, sizeof(struct sos_chardev_opened_file)); + chardev_of->class = chardev; + *result_of = & chardev_of->super; + + /* Increase the reference coount for that node */ + SOS_ASSERT_FATAL(chardev->ref_cnt >= 1); + chardev->ref_cnt ++; + + /* Initialize the read/write/seek callbacks */ + (*result_of)->owner = owner; + (*result_of)->open_flags = open_flags; + (*result_of)->ops_file = & chardev_ops_opened_file; + (*result_of)->ops_chardev = & chardev_ops_opened_chardev; + + /* Call the open callback */ + retval = chardev->ops->open(this, & chardev_of->super, chardev->custom_data); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t) chardev_of); + chardev->ref_cnt --; + *result_of = NULL; + return retval; + } + + /* Specify the duplicate method */ + (*result_of)->duplicate = duplicate_opened_chardev; + + return retval; +} + + +/** Callback called each time an opened file is closed. Un-allocate + the associated sos_chardev_opened_file object */ +static sos_ret_t +chardev_helper_close_opened_file(struct sos_fs_node * this, + struct sos_fs_opened_file * of) +{ + sos_ret_t retval; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)of); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + + /* Free the new "open file" description structure */ + if (NULL != chardev->ops->close) + retval = chardev->ops->close(& chardev_of->super, chardev->custom_data); + else + retval = SOS_OK; + if (SOS_OK != retval) + return retval; + + /* Decrease the reference coount for that node */ + SOS_ASSERT_FATAL(chardev->ref_cnt > 1); + chardev->ref_cnt --; + + sos_kfree((sos_vaddr_t) chardev_of); + return retval; +} + + +/** + * Callback called each time a process is "forked": create a new + * sos_chardev_opened_file for the new process. + * + * @note Almost identical to the open callback. + */ +static sos_ret_t +duplicate_opened_chardev(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result) +{ + sos_ret_t retval; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + struct sos_chardev_opened_file *new_chardev_of; + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(this->direntry); + + *result = NULL; + + /* Lookup the character device description structure */ + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + + /* Allocate a new duplicate copy of the original opened file */ + new_chardev_of = (struct sos_chardev_opened_file*) + sos_kmalloc(sizeof(struct sos_chardev_opened_file), 0); + if (NULL == new_chardev_of) + return -SOS_ENOMEM; + + memcpy(new_chardev_of, chardev_of, sizeof(*new_chardev_of)); + new_chardev_of->super.owner = for_owner; + new_chardev_of->super.direntry = NULL; /* Reset the direntry + for the new opened file */ + SOS_ASSERT_FATAL(chardev->ref_cnt > 1); + chardev->ref_cnt ++; + + retval = chardev->ops->open(fsnode, + & new_chardev_of->super, chardev->custom_data); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t) new_chardev_of); + chardev->ref_cnt --; + return retval; + } + + /* Make sure the required methods are overloaded */ + SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file); + SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file->seek); + SOS_ASSERT_FATAL(NULL != new_chardev_of->super.ops_file->read); + + *result = & new_chardev_of->super; + return retval; +} + + +/* + * FS generic character device wrapper functions + */ + +/** + * Callback called to change the position in the opened file: call the + * seek method of the device driver. + */ +static sos_ret_t chardev_wrap_seek(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->seek) + retval = chardev->ops->seek(this, offset, whence, result_position); + + return retval; +} + + +/** + * Callback called to read the contents of the opened file: call the + * read method of the device driver. + */ +static sos_ret_t chardev_wrap_read(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->read) + retval = chardev->ops->read(this, dest_buf, len); + + return retval; +} + + +/** + * Callback called to write bytes to the opened file: call the write + * method of the device driver. + */ +static sos_ret_t chardev_wrap_write(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->write) + retval = chardev->ops->write(this, src_buf, len); + + return retval; +} + + +/** + * Callback called to map the contents of the opened file: call the + * map method of the device driver. + */ +static sos_ret_t chardev_wrap_mmap(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->mmap) + retval = chardev->ops->mmap(this, uaddr, size, + access_rights, flags, offset); + + return retval; +} + + +/** + * Callback called to change the state of the opened file: call the + * fcntl method of the device driver. + */ +static sos_ret_t chardev_wrap_fcntl(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->fcntl) + retval = chardev->ops->fcntl(this, req_id, req_arg); + + return retval; +} + + +/** + * Callback called to control the underlying device: call the ioctl + * method of the device driver. + */ +static sos_ret_t chardev_wrap_ioctl(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg) +{ + sos_ret_t retval = -SOS_ENOSYS; + struct sos_chardev_opened_file *chardev_of + = ((struct sos_chardev_opened_file*)this); + + struct sos_chardev_class * chardev = chardev_of->class; + SOS_ASSERT_FATAL(NULL != chardev); + SOS_ASSERT_FATAL(NULL != chardev->ops); + + if (NULL != chardev->ops->ioctl) + retval = chardev->ops->ioctl(this, req_id, req_arg); + + return retval; +} + + +/** + * Gather the callbacks for a "character device" opened file + */ +static struct sos_fs_ops_opened_file chardev_ops_opened_file + = (struct sos_fs_ops_opened_file) { + .seek = chardev_wrap_seek, + .read = chardev_wrap_read, + .write = chardev_wrap_write, + .mmap = chardev_wrap_mmap, + .fcntl = chardev_wrap_fcntl + }; + + +static struct sos_fs_ops_opened_chardev chardev_ops_opened_chardev + = (struct sos_fs_ops_opened_chardev) { + .ioctl = chardev_wrap_ioctl + }; diff --git a/sos/chardev.h b/sos/chardev.h new file mode 100644 index 0000000..f13675b --- /dev/null +++ b/sos/chardev.h @@ -0,0 +1,155 @@ +/* Copyright (C) 2005 David Decotigny, Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_CHARDEV_H_ +#define _SOS_CHARDEV_H_ + +/** + * @file chardev.h + * + * Interface between the VFS and the "character" devices. The + * following functions provide the mechanisms to bind the "character + * device" nodes (@see mknod) to their device driver. + * + * The VFS layer must be perceived as an uniform access library on top + * of a set of specialized FS. The "chardev" layer is to be perceived + * as a FS-agnostic layer on top of the FS that binds the special + * "character device" nodes to a set of system-wide read/write/seek + * functions. + */ + +#include "fs.h" + +/** + * The fundamental callbacks for a character device: they are common + * to all the character devices of the same class. One is free to do + * whatever she likes with the "custom_data" of the opened file passed + * as argument + */ +struct sos_chardev_ops { + /** + * @note also called upon a "duplicate_opened_file", aka upon a + * fork() + * @note When this callback is called, of is NOT bound to any + * nscache_node, so don't ever call any sos_fs_nscache_* function ! + * @note To get the fsnode associated to "of", don't call + * sos_fs_nscache_get_fs_node(): it is already given by the "fsnode" + * argument + * @note MANDATORY ! + */ + sos_ret_t (*open)(struct sos_fs_node * fsnode, + struct sos_fs_opened_file * of, + void * chardev_class_custom_data); + + /** + * Called each time an opened "character device" is declared unused + * by user space + * @note Optional (might be NULL) + */ + sos_ret_t (*close)(struct sos_fs_opened_file * of, + void * chardev_class_custom_data); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + */ + sos_ret_t (*seek)(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + */ + sos_ret_t (*read)(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*write)(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*mmap)(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*fcntl)(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*ioctl)(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); +}; + + +/** + * Associate the given set of operations to the given major number. + * + * @note Character device drivers are registered for a complete class + * of character devices (up to 4 billion devices per class) + */ +sos_ret_t sos_chardev_register_class (sos_ui32_t device_class, + struct sos_chardev_ops * ops, + void * chardev_class_custom_data); + + +/** + * Unbind the given set of operations with the given major number + * + * @return SOS_EBUSY when the character device is still opened by any + * process. + */ +sos_ret_t sos_chardev_unregister_class (sos_ui32_t device_class); + + +/* + * Callbacks restricted to fs.c internals + */ + +/** + * Update the FS node ops_blockdev callbacks after an FS + * allocate_new_node or fetch_node_from_disk, in order to point to + * the block layer API functions + */ +sos_ret_t sos_chardev_helper_ref_new_fsnode(struct sos_fs_node * this); +sos_ret_t sos_chardev_helper_release_fsnode(struct sos_fs_node * this); + +#endif diff --git a/sos/errno.h b/sos/errno.h new file mode 100644 index 0000000..5a25a2e --- /dev/null +++ b/sos/errno.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2004 The SOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_ERRNO_H_ +#define _SOS_ERRNO_H_ + +/** + * @file errno.h + * + * SOS return value codes and errors. + */ + +/* Positive values of the error codes */ +#define SOS_OK 0 /* No error */ +#define SOS_EINVAL 1 /* Invalid argument */ +#define SOS_ENOSUP 2 /* Operation not supported */ +#define SOS_ENOMEM 3 /* No available memory */ +#define SOS_EBUSY 4 /* Object or device still in use */ +#define SOS_EINTR 5 /* Wait/Sleep has been interrupted */ +#define SOS_EAGAIN 6 /* Temporary resource exhaustion */ +#define SOS_EPERM 7 /* Mutex/files ownership error */ +#define SOS_EFAULT 8 /* Unresolved virtual memory fault */ +#define SOS_ENOENT 9 /* No such file or directory */ +#define SOS_ELOOP 10 /* symlink resolution loop / too recursive */ +#define SOS_EEXIST 11 /* File already exists */ +#define SOS_EACCES 12 /* Permission denied */ +#define SOS_ENOTDIR 13 /* Dir does not exist */ +#define SOS_ENAMETOOLONG 14 +#define SOS_EXDEV 15 /* Cannot link entries across different FS */ +#define SOS_EISDIR 16 /* Directories not allowed in operation */ +#define SOS_ENOTEMPTY 17 +#define SOS_ENODEV 18 /* No such device */ +#define SOS_EBADF 19 /* Bad file descriptor */ +#define SOS_EMFILE 20 /* Reached maximal opened file for process */ +#define SOS_ENOSYS 21 /* Operation not implemented */ +#define SOS_EIO 22 /* Input/output error */ +#define SOS_EFATAL 255 /* Internal fatal error */ + +/* A negative value means that an error occured. For + * example -SOS_EINVAL means that the error was "invalid + * argument" */ +typedef int sos_ret_t; + +#endif /* _SOS_ERRNO_H_ */ diff --git a/sos/fs.c b/sos/fs.c new file mode 100644 index 0000000..0848557 --- /dev/null +++ b/sos/fs.c @@ -0,0 +1,2263 @@ +/* Copyright (C) 2005,2006 David Decotigny + Copyright (C) 2000-2005 The KOS Team (Thomas Petazzoni, David + Decotigny, Julien Munier) + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include /* For the FCNTL commands */ +#include "chardev.h" + +#include "fs.h" + + +/** List of available filesystems registered in the system */ +static struct sos_fs_manager_type * fs_list = NULL; + +/** Last UID delivered for the FS instances */ +static sos_ui64_t last_fs_instance_uid; + + +/* ********************************************************** + * Forward declarations + */ +static sos_ret_t fs_fetch_node(struct sos_fs_manager_instance *fs, + sos_ui64_t storage_location, + struct sos_fs_node ** result_fsnode); + +static sos_ret_t +fs_allocate_node(struct sos_fs_manager_instance * fs, + sos_fs_node_type_t type, + sos_ui32_t flags, + const struct sos_process * creator, + sos_ui32_t access_rights, + struct sos_fs_node ** result_fsnode); + +static sos_ret_t mark_dirty_fsnode(struct sos_fs_node * fsnode, + sos_bool_t force_sync); + +static sos_ret_t sos_fs_sync_node(struct sos_fs_node * fsnode); + +static sos_ret_t sos_fs_sync_fs(struct sos_fs_manager_instance * fs); + +static sos_ret_t +fs_lookup_node(const struct sos_fs_pathname * path, + sos_bool_t follow_symlinks, + const struct sos_fs_nscache_node * root_nsnode, + const struct sos_fs_nscache_node * start_nsnode, + struct sos_fs_nscache_node ** result_nsnode, + struct sos_fs_pathname * result_remaining_path, + int lookup_recursion_level); + +static sos_ret_t +fs_resolve_symlink(const struct sos_fs_nscache_node * root_nsnode, + const struct sos_fs_nscache_node * symlink_nsnode, + struct sos_fs_nscache_node ** target_nsnode, + int lookup_recursion_level); + +static sos_ret_t +fs_register_child_node(const struct sos_process * creator, + struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + struct sos_fs_node * fsnode, + sos_ui32_t flags, + struct sos_fs_nscache_node ** result_nsnode); + +static sos_ret_t +fs_create_child_node(struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + sos_fs_node_type_t type, + sos_ui32_t flags, + const struct sos_process * creator, + sos_ui32_t access_rights, + struct sos_fs_nscache_node ** result_nsnode); + +static sos_ret_t +fs_connect_existing_child_node(const struct sos_process * creator, + struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + struct sos_fs_nscache_node * nsnode); + +static sos_ret_t +fs_create_node(const struct sos_fs_pathname * _path, + const struct sos_process * creator, + sos_ui32_t access_rights, + sos_fs_node_type_t type, + struct sos_fs_nscache_node ** result_nsnode); + +static sos_ret_t +fs_remove_node(const struct sos_process * actor, + struct sos_fs_nscache_node * nsnode); + + +/* ********************************************************** + * Basic low-level memory & co related functions + */ + +sos_ret_t +sos_fs_subsystem_setup(const char * root_device, + const char * fsname, + const char * mount_args, + struct sos_fs_manager_instance ** result_rootfs) +{ + sos_ret_t retval; + struct sos_fs_manager_type * fs_type; + struct sos_fs_manager_instance * new_fs; + struct sos_fs_node * rdev_fsnode; + int nb_fstypes; + + /* root_device is ignored for now */ + rdev_fsnode = NULL; + + last_fs_instance_uid = 0; + *result_rootfs = NULL; + + retval = sos_fs_nscache_subsystem_setup(); + if (SOS_OK != retval) + return retval; + + /* Look for the FS manager type */ + list_foreach(fs_list, fs_type, nb_fstypes) + { + if (! strcmp(fsname, fs_type->name)) + break; + } + if (! list_foreach_early_break(fs_list, fs_type, nb_fstypes)) + return -SOS_ENODEV; + + retval = fs_type->mount(fs_type, + rdev_fsnode, + mount_args, & new_fs); + if (SOS_OK != retval) + { + if (rdev_fsnode) + sos_fs_unref_fsnode(rdev_fsnode); + return retval; + } + + /* Update some reserved fields */ + sos_fs_nscache_get_fs_node(new_fs->root)->fs = new_fs; + + *result_rootfs = new_fs; + return SOS_OK; +} + + +sos_ret_t sos_fs_ref_fsnode(struct sos_fs_node * fsnode) +{ + fsnode->inmem_ref_cnt ++; + return SOS_OK; +} + + +sos_ret_t _sos_fs_unref_fsnode(struct sos_fs_node * node) +{ + SOS_ASSERT_FATAL(node->inmem_ref_cnt > 0); + + /* Commit the changes the the FS when the last reference is being + removed */ + if ((node->inmem_ref_cnt == 1) && (node->dirty)) + { + SOS_ASSERT_FATAL(SOS_OK == sos_fs_sync_node(node)); + } + + node->inmem_ref_cnt --; + + if (node->inmem_ref_cnt == 0) + { + if (SOS_FS_NODE_DEVICE_CHAR == node->type) + sos_chardev_helper_release_fsnode(node); + else if (SOS_FS_NODE_DEVICE_BLOCK == node->type) + sos_blockdev_helper_release_fsnode(node); + sos_hash_remove(node->fs->nodecache, node); + node->destructor(node); + } + + return SOS_OK; +} + + +sos_ret_t sos_fs_ref_opened_file(struct sos_fs_opened_file * of) +{ + of->ref_cnt ++; + return SOS_OK; +} + + +sos_ret_t _sos_fs_unref_opened_file(struct sos_fs_opened_file ** _of) +{ + struct sos_fs_opened_file * of = *_of; + *_of = NULL; + + SOS_ASSERT_FATAL(of->ref_cnt > 0); + of->ref_cnt --; + + if (0 == of->ref_cnt) + { + sos_ret_t retval; + struct sos_fs_nscache_node * nsnode = of->direntry; + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(nsnode); + + retval = fsnode->close_opened_file(fsnode, of); + if (SOS_OK != retval) + return retval; + + return sos_fs_nscache_unref_node(nsnode); + } + + return SOS_OK; +} + + +/* ********************************************************** + * Some helper functions + */ + +/** Fetch the given fsnode from hash first, or from disk when not in + hash */ +static sos_ret_t fs_fetch_node(struct sos_fs_manager_instance *fs, + sos_ui64_t storage_location, + struct sos_fs_node ** result_fsnode) +{ + sos_ret_t retval; + + /* If it is already loaded in memory, no need to look further */ + *result_fsnode = (struct sos_fs_node*) + sos_hash_lookup(fs->nodecache, + & storage_location); + if (*result_fsnode) + return SOS_OK; + + /* Otherwise, call the appropriate method of the FS */ + retval = fs->fetch_node_from_disk(fs, storage_location, result_fsnode); + if (SOS_OK != retval) + return retval; + + (*result_fsnode)->generation = 0; + + /* Special case for device nodes */ + if (SOS_FS_NODE_DEVICE_CHAR == (*result_fsnode)->type) + SOS_ASSERT_FATAL(SOS_OK + == sos_chardev_helper_ref_new_fsnode(*result_fsnode)); + else if (SOS_FS_NODE_DEVICE_BLOCK == (*result_fsnode)->type) + SOS_ASSERT_FATAL(SOS_OK + == sos_blockdev_helper_ref_new_fsnode(*result_fsnode)); + + sos_hash_insert(fs->nodecache, *result_fsnode); + return SOS_OK; +} + + +/** + * Helper function to allocate a new on-disk node + */ +static sos_ret_t +fs_allocate_node(struct sos_fs_manager_instance * fs, + sos_fs_node_type_t type, + sos_ui32_t flags, + const struct sos_process * creator, + sos_ui32_t access_rights, + struct sos_fs_node ** result_fsnode) +{ + sos_ret_t retval; + + /* Make sure FS is writable ! */ + if (fs->flags & SOS_FS_MOUNT_READONLY) + return -SOS_EPERM; + + /* Allocate the node on disk */ + retval = fs->allocate_new_node(fs, type, + creator, access_rights, + flags, + result_fsnode); + if (SOS_OK != retval) + return retval; + + /* Update some resrved fields */ + (*result_fsnode)->fs = fs; + + /* Special case for device nodes */ + if (SOS_FS_NODE_DEVICE_CHAR == (*result_fsnode)->type) + { + SOS_ASSERT_FATAL(SOS_OK + == sos_chardev_helper_ref_new_fsnode(*result_fsnode)); + } + else if (SOS_FS_NODE_DEVICE_BLOCK == (*result_fsnode)->type) + { + SOS_ASSERT_FATAL(SOS_OK + == sos_blockdev_helper_ref_new_fsnode(*result_fsnode)); + } + + + /* insert it in the node cache */ + retval = sos_hash_insert(fs->nodecache, *result_fsnode); + if (SOS_OK != retval) + { + sos_fs_unref_fsnode(*result_fsnode); + return retval; + } + + /* Success: Consider the node as dirty */ + mark_dirty_fsnode(*result_fsnode, FALSE); + return retval; +} + + +/** Helper function to add the given node in the dirty list, or to + write it directly to the disk if the system is mounted in SYNC + mode */ +static sos_ret_t mark_dirty_fsnode(struct sos_fs_node * fsnode, + sos_bool_t force_sync) +{ + sos_ret_t retval; + sos_bool_t was_dirty = fsnode->dirty; + + fsnode->dirty = TRUE; + fsnode->generation ++; + retval = SOS_OK; + + /* If the fsnode is newly dirty, add it to the dirty list of the + FS */ + if (!was_dirty && fsnode->dirty) + list_add_tail_named(fsnode->fs->dirty_nodes, fsnode, + prev_dirty, next_dirty); + + if (force_sync || (fsnode->fs->flags & SOS_FS_MOUNT_SYNC)) + return sos_fs_sync_node(fsnode); + + return retval; +} + + +/** Remove the given node from the dirty list of the FS */ +static sos_ret_t sos_fs_sync_node(struct sos_fs_node * fsnode) +{ + sos_ret_t retval; + + if (! fsnode->dirty) + return SOS_OK; + + retval = fsnode->ops_file->sync(fsnode); + if (SOS_OK != retval) + return retval; + + /* Remove it from the dirty list */ + list_delete_named(fsnode->fs->dirty_nodes, fsnode, + prev_dirty, next_dirty); + fsnode->dirty = FALSE; + + /* Commit the FS changes to the device */ + if (fsnode->fs->device) + { + retval = sos_blockdev_helper_sync_fsnode(fsnode->fs->device); + if (SOS_OK != retval) + { + /* We got a problem ! Mark the node dirty again */ + list_add_tail_named(fsnode->fs->dirty_nodes, fsnode, + prev_dirty, next_dirty); + fsnode->dirty = TRUE; + return retval; + } + } + + return SOS_OK; +} + + +/** Collapse the whole dirty list of the FS and commit the changes to + the underlying device */ +static sos_ret_t sos_fs_sync_fs(struct sos_fs_manager_instance * fs) +{ + struct sos_fs_node * fsnode; + while (NULL != (fsnode = list_get_head_named(fs->dirty_nodes, + prev_dirty, next_dirty))) + { + sos_ret_t retval = sos_fs_sync_node(fsnode); + if (SOS_OK != retval) + return retval; + } + + return SOS_OK; +} + + +/** + * Resolve the given symlink: return the nsnode for the destination + * of the symlink, or error status for dangling symlinks + * + * @note result_nsnode is a NEW reference to the node. It should be + * unreferenced when unused + */ +static sos_ret_t +fs_resolve_symlink(const struct sos_fs_nscache_node * root_nsnode, + const struct sos_fs_nscache_node * symlink_nsnode, + struct sos_fs_nscache_node ** target_nsnode, + int lookup_recursion_level) +{ + sos_ret_t retval; + const struct sos_fs_nscache_node * start_nsnode; + struct sos_fs_node * symlink_fsnode; + struct sos_fs_pathname path; + struct sos_fs_pathname remaining; + + symlink_fsnode = sos_fs_nscache_get_fs_node(symlink_nsnode); + retval = symlink_fsnode->ops_symlink->expand(symlink_fsnode, + & path.contents, + & path.length); + if (SOS_OK != retval) + return retval; + if (path.length <= 0) + return -SOS_ENOENT; + + /* Absolute path for target ? */ + if (path.contents[0] == '/') + start_nsnode = root_nsnode; + else + { + retval = sos_fs_nscache_get_parent(symlink_nsnode, + (struct sos_fs_nscache_node**)& start_nsnode); + if (SOS_OK != retval) + return retval; + } + + retval = fs_lookup_node(& path, TRUE, root_nsnode, start_nsnode, + target_nsnode, + & remaining, lookup_recursion_level); + if (SOS_OK != retval) + return retval; + + /* The target of the symlink could not be completely opened ! */ + if (remaining.length != 0) + { + sos_fs_nscache_unref_node(*target_nsnode); + return -SOS_ENOENT; + } + + return SOS_OK; +} + + +#define MAX_LOOKUP_RECURSION_LEVEL 5 + + +/** + * @return OK in any case, except if 1/ a symlink could not be + * resolved, or 2/ a path "a/b" is given where "a" is not a directory, + * or 3/ a fsnode that should exist could not be retrieved from disk. + * + * @param result_remaining_path contains the path that could not be resolved + * + * @param result_nsnode a NEW reference to the farthest node that + * could be resolved. It should be unreferenced when unused + */ +static sos_ret_t +fs_lookup_node(const struct sos_fs_pathname * path, + sos_bool_t follow_symlinks, + const struct sos_fs_nscache_node * root_nsnode, + const struct sos_fs_nscache_node * start_nsnode, + struct sos_fs_nscache_node ** result_nsnode, + struct sos_fs_pathname * result_remaining_path, + int lookup_recursion_level) +{ + sos_ret_t retval; + struct sos_fs_nscache_node * current_nsnode; + + /* Make sure we did not go too deep while resolving symlinks */ + lookup_recursion_level ++; + if (lookup_recursion_level > MAX_LOOKUP_RECURSION_LEVEL) + { + return -SOS_ELOOP; + } + + if (path->length <= 0) + return -SOS_ENOENT; + + *result_nsnode = NULL; + memcpy(result_remaining_path, path, sizeof(*path)); + + current_nsnode = (struct sos_fs_nscache_node *)start_nsnode; + sos_fs_nscache_ref_node(current_nsnode); + while (1) + { + struct sos_fs_pathname current_component, remaining; + struct sos_fs_nscache_node * next_nsnode = NULL; + sos_bool_t slashes_after_first_component; + +/* dbg_dump_pathname("Before", result_remaining_path); */ + + /* Extract the next component of the path */ + slashes_after_first_component + = sos_fs_pathname_split_path(result_remaining_path, + & current_component, & remaining); +/* dbg_dump_pathname("After", result_remaining_path); */ +/* dbg_dump_pathname("Cur", & current_component); */ +/* dbg_dump_pathname("Rem", & remaining); */ +/* sos_bochs_printf("Slash after=%d\n", slashes_after_first_component); */ + + /* Could resolve the whole path ? */ + if (current_component.length == 0) + { + /* Ok, fine, we got it ! */ + memcpy(result_remaining_path, & remaining, sizeof(remaining)); + *result_nsnode = current_nsnode; + return SOS_OK; + } + + /* Otherwise: make sure we reached a DIR node */ + if (sos_fs_nscache_get_fs_node(current_nsnode)->type + != SOS_FS_NODE_DIRECTORY) + { + sos_fs_nscache_unref_node(current_nsnode); + return -SOS_ENOENT; + } + + /* Make sure this directory is "executable" */ + if (! (sos_fs_nscache_get_fs_node(current_nsnode)->access_rights + & SOS_FS_EXECUTABLE) ) + { + sos_fs_nscache_unref_node(current_nsnode); + return -SOS_EACCES; + } + + /* If we can find the entry in the namespace cache, it is really + fine ! */ + retval = sos_fs_nscache_lookup(current_nsnode, + & current_component, + root_nsnode, + & next_nsnode); + if (SOS_OK != retval) + { + struct sos_fs_node * current_fsnode, * next_fsnode; + sos_ui64_t storage_location; + + /* + * Not found in the namespace cache. Must read from the + * disk... + */ + current_fsnode = sos_fs_nscache_get_fs_node(current_nsnode); + + retval = current_fsnode->ops_dir + ->lookup(current_fsnode, + current_component.contents, + current_component.length, + & storage_location); + if (SOS_OK != retval) + { + /* Well, we cannot go further, stop here */ + *result_nsnode = current_nsnode; + return SOS_OK; + } + + /* Now retrieve this node from disk or from the cache into + memory */ + retval = fs_fetch_node(current_fsnode->fs, + storage_location, & next_fsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(current_nsnode); + return retval; + } + + /* Integrate it in the nscache */ + retval = sos_fs_nscache_add_new_child_node(current_nsnode, + & current_component, + next_fsnode, + & next_nsnode); + sos_fs_nscache_unref_node(current_nsnode); + if (SOS_OK != retval) + return retval; + } + else + sos_fs_nscache_unref_node(current_nsnode); + + /* Reaching a symlink ? */ + if (sos_fs_nscache_get_fs_node(next_nsnode)->type + == SOS_FS_NODE_SYMLINK) + { + /* Expand the link only for non-terminal nodes, or for the + terminal node only if follow_symlinks is TRUE */ + if ( (remaining.length != 0) + || follow_symlinks ) + { + struct sos_fs_nscache_node * symlink_target; + + retval = fs_resolve_symlink(root_nsnode, next_nsnode, + & symlink_target, + lookup_recursion_level); + sos_fs_nscache_unref_node(next_nsnode); + if (SOS_OK != retval) + return retval; /* Dangling symlink */ + + next_nsnode = symlink_target; + } + } + + /* Make sure there was no slash after this component, unless + this component is a directory */ + if (slashes_after_first_component + && + ( sos_fs_nscache_get_fs_node(next_nsnode)->type + != SOS_FS_NODE_DIRECTORY) ) + { + sos_fs_nscache_unref_node(next_nsnode); + return -SOS_ENOTDIR; + } + + /* Ok, fine, we got it, update the path we still have to explore */ + memcpy(result_remaining_path, & remaining, sizeof(remaining)); + current_nsnode = next_nsnode; + } + + sos_display_fatal_error("Should not get there"); + return -SOS_EFATAL; +} + + +/** + * It is assumed that parent does not already have a child with the + * given name. We make sure that the "path" is a single entity (ie + * not "a/b") + * @return Error if fsnode is not on the same FS as parent_nsnode + */ +static sos_ret_t +fs_register_child_node(const struct sos_process * creator, + struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + struct sos_fs_node * fsnode, + sos_ui32_t flags, + struct sos_fs_nscache_node ** result_nsnode) +{ + sos_ret_t retval; + struct sos_fs_node * parent_fsnode; + struct sos_fs_pathname first_component, remaining; + sos_bool_t slashes_after_first_component = FALSE; + + *result_nsnode = NULL; + + parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode); + if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY) + return -SOS_ENOTDIR; + + if (name->length <= 0) + return -SOS_EINVAL; + + slashes_after_first_component + = sos_fs_pathname_split_path(name, & first_component, & remaining); + + if (fsnode->type != SOS_FS_NODE_DIRECTORY) + { + /* Make sure the given name is exactly a single path component + (ie no '/') */ + if (slashes_after_first_component) + return -SOS_EINVAL; + } + else + { + /* Make sure there aren't any other component behind the '/'s, if + any */ + if (remaining.length > 0) + return -SOS_EINVAL; + } + + /* Make sure the parent directory is writeable */ + if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) ) + return -SOS_EACCES; + + /* Make sure that the entries are located on the same FS */ + if (fsnode->fs != parent_fsnode->fs) + return -SOS_EXDEV; + + /* Make sure that the nsnode won't be destroyed */ + sos_fs_nscache_ref_node(parent_nsnode); + + /* Allocate the node in directory */ + retval = parent_fsnode->ops_dir->link(parent_fsnode, + creator, + first_component.contents, + first_component.length, + fsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(parent_nsnode); + return retval; + } + + /* Success: Consider the directory as dirty */ + mark_dirty_fsnode(parent_fsnode, FALSE); + + /* Allocate the node in nscache cache */ + retval = sos_fs_nscache_add_new_child_node(parent_nsnode, & first_component, + fsnode, result_nsnode); + + sos_fs_nscache_unref_node(parent_nsnode); + return retval; +} + + +/** It is assumed that parent does not already have a child with the + given name. We make sure that the "path" is a single entity (ie + not "a/b"). Return a NEW reference to the newly-created NS node */ +static sos_ret_t +fs_create_child_node(struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + sos_fs_node_type_t type, + sos_ui32_t flags, + const struct sos_process * creator, + sos_ui32_t access_rights, + struct sos_fs_nscache_node ** result_nsnode) +{ + sos_ret_t retval; + struct sos_fs_node * fsnode, * parent_fsnode; + + parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode); + if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY) + return -SOS_ENOTDIR; + + /* Make sure that the nsnode won't be destroyed */ + sos_fs_nscache_ref_node(parent_nsnode); + + retval = fs_allocate_node(parent_fsnode->fs, type, flags, creator, + access_rights, & fsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(parent_nsnode); + return retval; + } + + retval = fs_register_child_node(creator, + parent_nsnode, name, fsnode, flags, + result_nsnode); + sos_fs_nscache_unref_node(parent_nsnode); + + /* The function does not need it anymore */ + sos_fs_unref_fsnode(fsnode); + + return retval; +} + + +/** + * It is assumed that parent does not already have a child with the + * given name, and that the new child does not have a parent yet. We + * make sure that the "path" is a single entity (ie not "a/b") @return + * Error if fsnode is not on the same FS as parent_nsnode + */ +static sos_ret_t +fs_connect_existing_child_node(const struct sos_process * creator, + struct sos_fs_nscache_node * parent_nsnode, + const struct sos_fs_pathname * name, + struct sos_fs_nscache_node * nsnode) +{ + sos_ret_t retval; + struct sos_fs_node * parent_fsnode, * fsnode; + struct sos_fs_pathname first_component, remaining; + sos_bool_t slashes_after_first_component = FALSE; + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode); + if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY) + return -SOS_ENOTDIR; + + if (name->length <= 0) + return -SOS_EINVAL; + + slashes_after_first_component + = sos_fs_pathname_split_path(name, & first_component, & remaining); + + if (fsnode->type != SOS_FS_NODE_DIRECTORY) + { + /* Make sure the given name is exactly a single path component + (ie no '/') */ + if (slashes_after_first_component) + return -SOS_EINVAL; + } + else + { + /* Make sure there aren't any other component behind the '/'s, if + any */ + if (remaining.length > 0) + return -SOS_EINVAL; + } + + /* Make sure the parent directory is writeable */ + if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) ) + return -SOS_EACCES; + + /* Make sure that the entries are located on the same FS */ + if (fsnode->fs != parent_fsnode->fs) + return -SOS_EXDEV; + + /* Make sure that the nsnode won't be destroyed */ + sos_fs_nscache_ref_node(parent_nsnode); + + /* Allocate the node in directory */ + retval = parent_fsnode->ops_dir->link(parent_fsnode, + creator, + first_component.contents, + first_component.length, + fsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(parent_nsnode); + return retval; + } + + /* Success: Consider the directory as dirty */ + mark_dirty_fsnode(parent_fsnode, FALSE); + + /* Allocate the node in nscache cache */ + retval = sos_fs_nscache_add_existing_child_node(parent_nsnode, + & first_component, + nsnode); + sos_fs_nscache_unref_node(parent_nsnode); + return retval; +} + + +/** Return a new reference to the new node inserted unless + result_nsnode is NULL */ +static sos_ret_t +fs_create_node(const struct sos_fs_pathname * _path, + const struct sos_process * creator, + sos_ui32_t access_rights, + sos_fs_node_type_t type, + struct sos_fs_nscache_node ** result_nsnode) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node *nsnode, *new_nsnode; + + path.contents = _path->contents; + path.length = _path->length; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(creator)->direntry; + else + nsnode = sos_process_get_cwd(creator)->direntry; + + retval = fs_lookup_node(& path, + TRUE, + sos_process_get_root(creator)->direntry, + nsnode, + & nsnode, + & path, + 0); + if (SOS_OK != retval) + return retval; + + if (path.length <= 0) + { + /* Found the exact match ! */ + sos_fs_nscache_unref_node(nsnode); + return -SOS_EEXIST; + } + + /* Create a new entry in the file system */ + retval = fs_create_child_node(nsnode, + & path, + type, + /* flags */0, + creator, access_rights, + & new_nsnode); + sos_fs_nscache_unref_node(nsnode); + + /* node not needed by this function ? */ + if (NULL == result_nsnode) + sos_fs_nscache_unref_node(new_nsnode); + else + *result_nsnode = new_nsnode; + + return retval; +} + + +static sos_ret_t +fs_remove_node(const struct sos_process * actor, + struct sos_fs_nscache_node * nsnode) +{ + sos_ret_t retval; + struct sos_fs_nscache_node * parent_nsnode; + struct sos_fs_node * parent_fsnode; + struct sos_fs_pathname childname; + + /* Refuse to do anything if this is the root of a mounted FS */ + if (nsnode == sos_fs_nscache_get_fs_node(nsnode)->fs->root) + return -SOS_EBUSY; + + retval = sos_fs_nscache_get_parent(nsnode, & parent_nsnode); + if (SOS_OK != retval) + return retval; + parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode); + + /* Make sure FS is writable ! */ + if (parent_fsnode->fs->flags & SOS_FS_MOUNT_READONLY) + return -SOS_EPERM; + + /* Make sure the parent directory is writeable */ + if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) ) + return -SOS_EACCES; + + sos_fs_nscache_ref_node(parent_nsnode); + + sos_fs_nscache_get_name(nsnode, & childname); + retval = parent_fsnode->ops_dir->unlink(parent_fsnode, actor, + childname.contents, + childname.length); + if (SOS_OK == retval) + sos_fs_nscache_disconnect_node(nsnode); + + /* Unallocate the node */ + if (SOS_OK == retval) + mark_dirty_fsnode(parent_fsnode, FALSE); + + sos_fs_nscache_unref_node(parent_nsnode); + return retval; +} + + +/* ********************************************************** + * Exported functions + */ + +sos_ret_t sos_fs_new_opened_file(const struct sos_process * owner, + struct sos_fs_nscache_node * nsnode, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of) +{ + sos_ret_t retval; + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(nsnode); + + retval = fsnode->new_opened_file(fsnode, owner, open_flags, result_of); + if (SOS_OK != retval) + return retval; + + (*result_of)->ref_cnt = 1; + (*result_of)->generation = 1; + + retval = sos_fs_nscache_register_opened_file(nsnode, *result_of); + if (SOS_OK != retval) + { + fsnode->close_opened_file(fsnode, *result_of); + return retval; + } + + (*result_of)->open_flags = open_flags; + return SOS_OK; +} + + +sos_ret_t +sos_fs_duplicate_opened_file(struct sos_fs_opened_file * src_of, + const struct sos_process * dst_proc, + struct sos_fs_opened_file ** result_of) +{ + sos_ret_t retval = src_of->duplicate(src_of, dst_proc, result_of); + if (SOS_OK != retval) + return retval; + + SOS_ASSERT_FATAL((*result_of)->owner == dst_proc); + (*result_of)->ref_cnt = 1; + (*result_of)->generation = 1; + retval = sos_fs_nscache_register_opened_file(src_of->direntry, *result_of); + if (SOS_OK != retval) + { + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(src_of->direntry); + fsnode->close_opened_file(fsnode, *result_of); + return retval; + } + + return retval; +} + + +sos_ret_t sos_fs_open(const struct sos_process *owner, + const char *_path, + sos_size_t _pathlen, + sos_ui32_t open_flags, + sos_ui32_t creat_access_rights, + struct sos_fs_opened_file ** of) +{ + sos_ret_t retval; + struct sos_fs_nscache_node *nsnode; + struct sos_fs_node * fsnode; + struct sos_fs_pathname path; + + /* O_DIR | O_CREAT combination not supported */ + if ( (open_flags & SOS_FS_OPEN_DIRECTORY) + && ( (open_flags & (SOS_FS_OPEN_CREAT + | SOS_FS_OPEN_TRUNC)) ) ) + return -SOS_EINVAL; + + if (_pathlen <= 0) + return -SOS_ENOENT; + + path.contents = _path; + path.length = _pathlen; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(owner)->direntry; + else + nsnode = sos_process_get_cwd(owner)->direntry; + + retval = fs_lookup_node(& path, + ! (open_flags & SOS_FS_OPEN_NOFOLLOW), + sos_process_get_root(owner)->direntry, + nsnode, + & nsnode, + & path, + 0); + if (SOS_OK != retval) + return retval; + + if (path.length <= 0) + { + /* Found the exact match ! */ + + /* Handle O_EXCL flag */ + if (open_flags & SOS_FS_OPEN_EXCL) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_EEXIST; + } + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + if ((open_flags & SOS_FS_OPEN_DIRECTORY) + && (fsnode->type != SOS_FS_NODE_DIRECTORY)) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOTDIR; + } + + /* Handle O_TRUNC flag */ + if ((open_flags & SOS_FS_OPEN_TRUNC) + && fsnode->ops_file->truncate) + { + retval = fsnode->ops_file->truncate(fsnode, 0); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(nsnode); + return retval; + } + } + } + else + { + struct sos_fs_nscache_node * parent_nsnode = nsnode; + + /* Did not find an exact match. Should create the node ! */ + if (! (open_flags & SOS_FS_OPEN_CREAT)) + { + sos_fs_nscache_unref_node(parent_nsnode); + return -SOS_ENOENT; + } + + /* Create a new entry in the file system */ + retval = fs_create_child_node(parent_nsnode, + & path, + SOS_FS_NODE_REGULAR_FILE, + open_flags, + owner, + creat_access_rights, + & nsnode); + sos_fs_nscache_unref_node(parent_nsnode); + if (SOS_OK != retval) + { + return retval; + } + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + } + + /* Recompute access rights */ + open_flags &= ~(SOS_FS_OPEN_CREAT + | SOS_FS_OPEN_EXCL + | SOS_FS_OPEN_NOFOLLOW + | SOS_FS_OPEN_DIRECTORY); + if (! (fsnode->access_rights & SOS_FS_WRITABLE)) + open_flags &= ~(SOS_FS_OPEN_WRITE); + if (! (fsnode->access_rights & SOS_FS_READABLE)) + open_flags &= ~(SOS_FS_OPEN_READ); + if (fsnode->fs->flags & SOS_FS_MOUNT_READONLY) + open_flags &= ~(SOS_FS_OPEN_READ); + + /* + * Ok, open it right now ! + */ + retval = sos_fs_new_opened_file(owner, nsnode, open_flags, of); + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +sos_ret_t sos_fs_close(struct sos_fs_opened_file * of) +{ + return sos_fs_unref_opened_file(of); +} + + +sos_ret_t sos_fs_mark_dirty(struct sos_fs_opened_file * of) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + + /* This function should never get called if the FS is read-only */ + SOS_ASSERT_FATAL(! (fsnode->fs->flags & SOS_FS_MOUNT_READONLY)); + + return mark_dirty_fsnode(fsnode, of->open_flags & SOS_FS_OPEN_SYNC); +} + + +sos_ret_t sos_fs_read(struct sos_fs_opened_file * of, + sos_uaddr_t dest_buf, + sos_size_t * /* in/ou */len) +{ + if (! (of->open_flags & SOS_FS_OPEN_READ)) + return -SOS_EPERM; + + if (! of->ops_file->read) + return -SOS_ENOSYS; + + return of->ops_file->read(of, dest_buf, len); +} + + +sos_ret_t sos_fs_write(struct sos_fs_opened_file * of, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len) +{ + if (! (of->open_flags & SOS_FS_OPEN_WRITE)) + return -SOS_EPERM; + + if (! of->ops_file->write) + return -SOS_ENOSYS; + + return of->ops_file->write(of, src_buf, len); +} + + +sos_ret_t sos_fs_seek(struct sos_fs_opened_file *of, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + sos_lsoffset_t * result_position) +{ + if (! of->ops_file->seek) + return -SOS_ENOSYS; + + return of->ops_file->seek(of, offset, whence, result_position); +} + + +sos_ret_t sos_fs_ftruncate(struct sos_fs_opened_file *of, + sos_lsoffset_t length) +{ + sos_ret_t retval; + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + + if (! (of->open_flags & SOS_FS_OPEN_WRITE)) + return -SOS_EPERM; + + if (! fsnode->ops_file->truncate) + return -SOS_ENOSYS; + + retval = fsnode->ops_file->truncate(fsnode, length); + if (SOS_OK == retval) + mark_dirty_fsnode(fsnode, FALSE); + + return retval; +} + + +sos_ret_t sos_fs_mmap(struct sos_fs_opened_file *of, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset) +{ + sos_ui32_t required_access = 0; + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + + if (! of->ops_file->mmap) + return -SOS_ENOSYS; + + /* Translate VM requested rights into FS equivalent */ + if (access_rights & SOS_VM_MAP_PROT_READ) + required_access |= SOS_FS_OPEN_READ; + if ( (access_rights & SOS_VM_MAP_PROT_WRITE) && (flags & SOS_VR_MAP_SHARED) ) + required_access |= SOS_FS_OPEN_WRITE; + if (access_rights & SOS_VM_MAP_PROT_EXEC) + required_access |= SOS_FS_OPEN_READ; + + /* Make sure that the opened file allowed this access */ + if ((of->open_flags & required_access) != required_access) + return -SOS_EPERM; + + if ( (access_rights & SOS_VM_MAP_PROT_EXEC) + && (fsnode->fs->flags & SOS_FS_MOUNT_NOEXEC) ) + return -SOS_EPERM; + + return of->ops_file->mmap(of, uaddr, size, access_rights, flags, offset); +} + + +sos_ret_t sos_fs_fcntl(struct sos_fs_opened_file *of, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */) +{ + if (! of->ops_file->fcntl) + return sos_fs_basic_fcntl_helper(of, req_id, req_arg); + + return of->ops_file->fcntl(of, req_id, req_arg); +} + + +sos_ret_t sos_fs_ioctl(struct sos_fs_opened_file *of, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + + if (fsnode->type == SOS_FS_NODE_DEVICE_CHAR) + { + if (! of->ops_chardev->ioctl) + return -SOS_ENOSYS; + + return of->ops_chardev->ioctl(of, req_id, req_arg); + } + else if (fsnode->type == SOS_FS_NODE_DEVICE_BLOCK) + { + if (! of->ops_blockdev->ioctl) + return -SOS_ENOSYS; + + return of->ops_blockdev->ioctl(of, req_id, req_arg); + } + + return -SOS_ENOSYS; +} + + +sos_ret_t sos_fs_readdir(struct sos_fs_opened_file * of, + struct sos_fs_dirent * result) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + + if (fsnode->type != SOS_FS_NODE_DIRECTORY) + return -SOS_ENOTDIR; + + return of->ops_dir->readdir(of, result); +} + + +sos_ret_t sos_fs_creat(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights) +{ + struct sos_fs_pathname path; + + path.contents = _path; + path.length = _pathlen; + + return fs_create_node(& path, creator, access_rights, + SOS_FS_NODE_REGULAR_FILE, NULL); +} + + +sos_ret_t sos_fs_link(const struct sos_process * creator, + const char * _old_path, + sos_size_t _old_pathlen, + const char * _new_path, + sos_size_t _new_pathlen) +{ + sos_ret_t retval; + struct sos_fs_nscache_node *old_nsnode, *dest_parent_nsnode, *new_nsnode; + struct sos_fs_node * fsnode; + struct sos_fs_pathname old_path, new_path; + + if (_old_pathlen <= 0) + return -SOS_ENOENT; + if (_new_pathlen <= 0) + return -SOS_ENOENT; + + /* Resolve target FS node using "old_path" */ + old_path.contents = _old_path; + old_path.length = _old_pathlen; + + if (old_path.contents[0] == '/') + old_nsnode = sos_process_get_root(creator)->direntry; + else + old_nsnode = sos_process_get_cwd(creator)->direntry; + + retval = fs_lookup_node(& old_path, + FALSE /* don't follow symlink */, + sos_process_get_root(creator)->direntry, + old_nsnode, + & old_nsnode, + & old_path, + 0); + if (SOS_OK != retval) + return retval; + + if (old_path.length > 0) + { + /* Could not resolve full path ! */ + sos_fs_nscache_unref_node(old_nsnode); + return -SOS_ENOENT; + } + + fsnode = sos_fs_nscache_get_fs_node(old_nsnode); + + /* Not allowed to link directories ! */ + if (fsnode->type == SOS_FS_NODE_DIRECTORY) + { + sos_fs_nscache_unref_node(old_nsnode); + return -SOS_ENOENT; + } + + /* Resolve destination path */ + new_path.contents = _new_path; + new_path.length = _new_pathlen; + + if (new_path.contents[0] == '/') + dest_parent_nsnode = sos_process_get_root(creator)->direntry; + else + dest_parent_nsnode = sos_process_get_cwd(creator)->direntry; + + retval = fs_lookup_node(& new_path, + TRUE /* Follow symlink */, + sos_process_get_root(creator)->direntry, + dest_parent_nsnode, + & dest_parent_nsnode, + & new_path, + 0); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(old_nsnode); + return retval; + } + + if (new_path.length == 0) + { + /* Found the exact match ! Not allowed to overwrite it ! */ + sos_fs_nscache_unref_node(dest_parent_nsnode); + sos_fs_nscache_unref_node(old_nsnode); + return -SOS_EEXIST; + } + + /* Create a new entry in the file system */ + retval = fs_register_child_node(creator, dest_parent_nsnode, & new_path, + fsnode, 0, + & new_nsnode); + + sos_fs_nscache_unref_node(dest_parent_nsnode); + sos_fs_nscache_unref_node(old_nsnode); + sos_fs_nscache_unref_node(new_nsnode); + return retval; +} + + +sos_ret_t sos_fs_rename(const struct sos_process * actor, + const char * _old_path, + sos_size_t _old_pathlen, + const char * _new_path, + sos_size_t _new_pathlen) +{ + sos_ret_t retval; + struct sos_fs_nscache_node *old_nsnode, *old_parent_nsnode, + *dest_parent_nsnode, *replaced_nsnode; + struct sos_fs_pathname old_name, replaced_name; + struct sos_fs_pathname old_path, new_path; + + old_nsnode = old_parent_nsnode = dest_parent_nsnode = replaced_nsnode = NULL; + + if (_old_pathlen <= 0) + return -SOS_ENOENT; + if (_new_pathlen <= 0) + return -SOS_ENOENT; + + /* Resolve target FS node using "old_path" */ + old_path.contents = _old_path; + old_path.length = _old_pathlen; + + if (old_path.contents[0] == '/') + old_nsnode = sos_process_get_root(actor)->direntry; + else + old_nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& old_path, + FALSE /* don't follow symlink */, + sos_process_get_root(actor)->direntry, + old_nsnode, + & old_nsnode, + & old_path, + 0); + if (SOS_OK != retval) + return retval; + + if (old_path.length > 0) + { + /* Could not resolve full path ! */ + sos_fs_nscache_unref_node(old_nsnode); + return -SOS_ENOENT; + } + + /* Not allowed to rename mountpoints ! */ + if (sos_fs_nscache_is_mountnode(old_nsnode)) + { + sos_fs_nscache_unref_node(old_nsnode); + return -SOS_ENOENT; + } + + /* Keep a reference on this node's parent, in case we must undo the + rename */ + retval = sos_fs_nscache_get_parent(old_nsnode, & old_parent_nsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(old_nsnode); + return retval; + } + sos_fs_nscache_ref_node(old_parent_nsnode); + + /* Resolve destination path */ + replaced_nsnode = NULL; + new_path.contents = _new_path; + new_path.length = _new_pathlen; + + if (new_path.contents[0] == '/') + dest_parent_nsnode = sos_process_get_root(actor)->direntry; + else + dest_parent_nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& new_path, + TRUE /* Follow symlink */, + sos_process_get_root(actor)->direntry, + dest_parent_nsnode, + & dest_parent_nsnode, + & new_path, + 0); + if (SOS_OK != retval) + { + goto undo_rename; + } + + /* Nothing to do ? */ + if (old_nsnode == dest_parent_nsnode) + { + sos_fs_nscache_unref_node(old_nsnode); + sos_fs_nscache_unref_node(old_parent_nsnode); + sos_fs_nscache_unref_node(dest_parent_nsnode); + return SOS_OK; + } + + /* Remove old nsnode from file ns cache */ + sos_fs_nscache_get_name(old_nsnode, & old_name); + retval = fs_remove_node(actor, old_nsnode); + if (SOS_OK != retval) + { + sos_fs_nscache_unref_node(old_nsnode); + sos_fs_nscache_unref_node(old_parent_nsnode); + sos_fs_nscache_unref_node(dest_parent_nsnode); + return -SOS_ENOENT; + } + + if (new_path.length == 0) + { + /* Found the exact match ! We disconnect it from the namespace, + but keep it aside */ + + /* Not allowed to replace directories */ + if (sos_fs_nscache_get_fs_node(dest_parent_nsnode)->type + == SOS_FS_NODE_DIRECTORY) + { + retval = -SOS_EBUSY; + goto undo_rename; + } + + replaced_nsnode = dest_parent_nsnode; + dest_parent_nsnode = NULL; + + /* Retrieve the parent of the node to replace */ + retval = sos_fs_nscache_get_parent(replaced_nsnode, + & dest_parent_nsnode); + if (SOS_OK != retval) + { + dest_parent_nsnode = replaced_nsnode; + goto undo_rename; + } + + sos_fs_nscache_ref_node(dest_parent_nsnode); + + /* Disconnect this node from its parent */ + sos_fs_nscache_get_name(replaced_nsnode, & replaced_name); + retval = fs_remove_node(actor, replaced_nsnode); + if (SOS_OK != retval) + goto undo_rename; + } + + /* Create a new entry in the file system */ + retval = fs_connect_existing_child_node(actor, dest_parent_nsnode, + & new_path, + old_nsnode); + if (SOS_OK != retval) + goto undo_rename; + + sos_fs_nscache_unref_node(old_nsnode); + sos_fs_nscache_unref_node(old_parent_nsnode); + sos_fs_nscache_unref_node(dest_parent_nsnode); + if (NULL != replaced_nsnode) + sos_fs_nscache_unref_node(replaced_nsnode); + + return retval; + + undo_rename: + + /* Handle special case: the node replaced something. In case the + previous operation failed, we try to reinsert the replaced + node */ + if (NULL != replaced_nsnode) + { + fs_connect_existing_child_node(actor, dest_parent_nsnode, + & replaced_name, + replaced_nsnode); + sos_fs_nscache_unref_node(replaced_nsnode); + } + + fs_connect_existing_child_node(actor, old_parent_nsnode, + & old_name, + old_nsnode); + sos_fs_nscache_unref_node(old_nsnode); + sos_fs_nscache_unref_node(old_parent_nsnode); + + if (NULL != dest_parent_nsnode) + sos_fs_nscache_unref_node(dest_parent_nsnode); + + return retval; +} + + +sos_ret_t sos_fs_unlink(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + + path.contents = _path; + path.length = _pathlen; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(actor)->direntry; + else + nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& path, FALSE, + sos_process_get_root(actor)->direntry, + nsnode, + & nsnode, & path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (path.length > 0) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOENT; + } + + if (sos_fs_nscache_get_fs_node(nsnode)->type == SOS_FS_NODE_DIRECTORY) + retval = -SOS_EISDIR; + else + retval = fs_remove_node(actor, nsnode); + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +sos_ret_t sos_fs_symlink(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_uaddr_t symlink_target, + sos_size_t symlink_target_len) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_node * fsnode; + struct sos_fs_nscache_node * symlink; + struct sos_fs_opened_file * tmp_of; + sos_size_t len; + + path.contents = _path; + path.length = _pathlen; + + retval = fs_create_node(& path, creator, SOS_FS_S_IRWXALL, + SOS_FS_NODE_SYMLINK, & symlink); + if (SOS_OK != retval) + return retval; + + /* Artificially open the symlink to change its contents */ + fsnode = sos_fs_nscache_get_fs_node(symlink); + retval = fsnode->new_opened_file(fsnode, creator, + SOS_FS_OPEN_WRITE, & tmp_of); + if (SOS_OK != retval) + { + fs_remove_node(creator, symlink); + sos_fs_nscache_unref_node(symlink); + return retval; + } + + tmp_of->ref_cnt = 1; + retval = sos_fs_nscache_register_opened_file(symlink, tmp_of); + if (SOS_OK != retval) + { + fsnode->close_opened_file(fsnode, tmp_of); + fs_remove_node(creator, symlink); + sos_fs_nscache_unref_node(symlink); + return retval; + } + + len = symlink_target_len; + retval = sos_fs_write(tmp_of, symlink_target, & len); + mark_dirty_fsnode(fsnode, FALSE); + fsnode->close_opened_file(fsnode, tmp_of); + + if ((SOS_OK != retval) || (len != symlink_target_len)) + { + fs_remove_node(creator, symlink); + if (SOS_OK == retval) + retval = -SOS_ENAMETOOLONG; + } + + sos_fs_nscache_unref_node(symlink); + return retval; +} + + +sos_ret_t sos_fs_mkdir(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights) +{ + struct sos_fs_pathname path; + + path.contents = _path; + path.length = _pathlen; + + return fs_create_node(& path, creator, access_rights, + SOS_FS_NODE_DIRECTORY, NULL); +} + + +sos_ret_t sos_fs_rmdir(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + + path.contents = _path; + path.length = _pathlen; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(actor)->direntry; + else + nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& path, FALSE, + sos_process_get_root(actor)->direntry, + nsnode, + & nsnode, & path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (path.length > 0) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOENT; + } + + /* Cannot rmdir non-dir nodes */ + if (sos_fs_nscache_get_fs_node(nsnode)->type != SOS_FS_NODE_DIRECTORY) + retval = -SOS_ENOTDIR; + + /* Cannot remove directory if it is still used by somebody else */ + else if (sos_fs_nscache_get_ref_cnt(nsnode) > 1) + retval = -SOS_EBUSY; + + /* Cannot remove directory if it is still has children stored on + disk */ + else if (sos_fs_nscache_get_fs_node(nsnode)->ondisk_lnk_cnt > 1) + retval = -SOS_ENOTEMPTY; + + /* Otherwise, yes : suppress the node on disk */ + else + retval = fs_remove_node(actor, nsnode); + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +sos_ret_t sos_fs_mknod(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_fs_node_type_t type /* only block/char allowed */, + sos_ui32_t access_rights, + const struct sos_fs_dev_id_t * devid) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + struct sos_fs_node * fsnode; + + if ((type != SOS_FS_NODE_DEVICE_BLOCK) + && (type != SOS_FS_NODE_DEVICE_CHAR)) + return -SOS_EINVAL; + + path.contents = _path; + path.length = _pathlen; + + retval = fs_create_node(& path, creator, access_rights, + type, & nsnode); + if (SOS_OK != retval) + return retval; + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + fsnode->dev_id.device_class = devid->device_class; + fsnode->dev_id.device_instance = devid->device_instance; + + sos_fs_nscache_unref_node(nsnode); + return SOS_OK; +} + + +sos_ret_t sos_fs_stat(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + int nofollow, + struct sos_fs_stat * result) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + struct sos_fs_node * fsnode; + + path.contents = _path; + path.length = _pathlen; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(actor)->direntry; + else + nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& path, (nofollow != 0), + sos_process_get_root(actor)->direntry, + nsnode, + & nsnode, & path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (path.length > 0) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOENT; + } + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + retval = fsnode->ops_file->stat(fsnode, result); + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +sos_ret_t sos_fs_fsync(struct sos_fs_opened_file * of) +{ + struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry); + return sos_fs_sync_node(fsnode); +} + + +sos_ret_t sos_fs_chmod(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + struct sos_fs_node * fsnode; + + path.contents = _path; + path.length = _pathlen; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(actor)->direntry; + else + nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& path, TRUE, + sos_process_get_root(actor)->direntry, + nsnode, + & nsnode, & path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (path.length > 0) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOENT; + } + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + retval = fsnode->ops_file->chmod(fsnode, access_rights); + if (SOS_OK == retval) + mark_dirty_fsnode(fsnode, FALSE); + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +sos_ret_t sos_fs_vfstat(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + struct sos_fs_statfs * result) +{ + sos_ret_t retval; + struct sos_fs_pathname path; + struct sos_fs_nscache_node * nsnode; + struct sos_fs_node * fsnode; + + path.contents = _path; + path.length = _pathlen; + + if (path.length <= 0) + return -SOS_ENOENT; + + if (path.contents[0] == '/') + nsnode = sos_process_get_root(actor)->direntry; + else + nsnode = sos_process_get_cwd(actor)->direntry; + + retval = fs_lookup_node(& path, FALSE, + sos_process_get_root(actor)->direntry, + nsnode, + & nsnode, & path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (path.length > 0) + { + sos_fs_nscache_unref_node(nsnode); + return -SOS_ENOENT; + } + + fsnode = sos_fs_nscache_get_fs_node(nsnode); + if (fsnode->fs->statfs) + retval = fsnode->fs->statfs(fsnode->fs, result); + else + retval = -SOS_ENOSYS; + + sos_fs_nscache_unref_node(nsnode); + return retval; +} + + +/** This function is resilient against mounting/unmounting of other FS */ +sos_ret_t sos_fs_sync_all_fs() +{ + int dummy = 0; + sos_ui64_t uid = 0; + + while (1) + { + /* Iterate over the FS types */ + struct sos_fs_manager_type * fstype; + int ntype; + list_foreach(fs_list, fstype, ntype) + { + /* Iterate over the FS instances */ + struct sos_fs_manager_instance * fs; + int ninst; + + /* This scan will be exhaustive and resilient to + addition/removal of file systems as long as new FS are + added with list_add_tail (because the scan is "forward", + ie in order head -> tail) */ + + /* As long as we don't block, we can safely access the + prev/next fields of the FS instance */ + list_foreach_forward(fstype->instances, fs, ninst) + { + if (fs->uid <= uid) + continue; + + uid = fs->uid; + sos_fs_sync_fs(fs); + + /* We must NOT continue the loops because the + prev/next/current fs types/instances might have been + removed or added (sync blocks, by definition) ! */ + goto lookup_next_fs; + } + } + + /* Reached the end of the list */ + break; + + lookup_next_fs: + /* Loop over */ + dummy ++; + } + + /* Now flush all the block devices to disk */ + return sos_blockdev_sync_all_devices(); +} + + +/* ************************************************************* + * client FS helper functions + */ +sos_ret_t sos_fs_basic_fcntl_helper(struct sos_fs_opened_file * of, + int req_id, sos_ui32_t req_arg) +{ + sos_ret_t result = -SOS_ENOSUP; + + switch(req_id) + { + case SOS_FCNTL_DUPFD: + { + result = sos_fs_ref_opened_file(of); + if (SOS_OK != result) + break; + + result = sos_process_register_opened_file((struct sos_process*)of->owner, + of, req_arg); + } + break; + + case SOS_FCNTL_GETFD: + { + result = of->open_flags & SOS_FS_OPEN_CLOSEONEXEC; + } + break; + + case SOS_FCNTL_SETFD: + { + of->open_flags &= ~SOS_FS_OPEN_CLOSEONEXEC; + of->open_flags |= (req_arg & SOS_FS_OPEN_CLOSEONEXEC); + result = SOS_OK; + } + break; + + case SOS_FCNTL_GETFL: + { + result = of->open_flags; + } + break; + + case SOS_FCNTL_SETFL: + { + /* Not supported */ + } + break; + + default: + break; + } + + return result; +} + + +/* ************************************************************* + * mount/umount stuff + */ + + +sos_ret_t sos_fs_register_fs_instance(struct sos_fs_manager_instance * fs, + struct sos_fs_node * root_fsnode) +{ + sos_ret_t retval; + struct sos_fs_nscache_node * nsnode_root; + + retval = sos_fs_nscache_create_mounted_root(root_fsnode, & nsnode_root); + if (SOS_OK != retval) + return retval; + + fs->uid = ++last_fs_instance_uid; + fs->root = nsnode_root; + sos_hash_insert(fs->nodecache, root_fsnode); + + list_add_tail(fs->fs_type->instances, fs); + return SOS_OK; +} + + +sos_ret_t sos_fs_unregister_fs_instance(struct sos_fs_manager_instance * fs) +{ + fs->uid = 0; + list_delete(fs->fs_type->instances, fs); + return SOS_OK; +} + + +sos_ret_t sos_fs_register_fs_type(struct sos_fs_manager_type * fstype) +{ + struct sos_fs_manager_type * iterator; + int nbtypes; + + list_foreach_forward(fs_list, iterator, nbtypes) + { + if (! strncmp(fstype->name, iterator->name, SOS_FS_MANAGER_NAME_MAXLEN)) + return -SOS_EEXIST; + } + + list_add_tail(fs_list, fstype); + return SOS_OK; +} + + +sos_ret_t sos_fs_unregister_fs_type(struct sos_fs_manager_type * fstype) +{ + struct sos_fs_manager_type * iterator; + int nbtypes; + + if (! list_is_empty(fstype->instances)) + return -SOS_EBUSY; + + list_foreach_forward(fs_list, iterator, nbtypes) + { + if (! strncmp(fstype->name, iterator->name, SOS_FS_MANAGER_NAME_MAXLEN)) + { + list_delete(fs_list, fstype); + return SOS_OK; + } + } + + return -SOS_EINVAL; +} + + +/** + * _src_path may be empty + */ +sos_ret_t sos_fs_mount(struct sos_process * actor, + const char * _src_path, + sos_size_t _src_pathlen, + const char * _dst_path, + sos_size_t _dst_pathlen, + const char * fsname, + sos_ui32_t mountflags, + const char * args, + struct sos_fs_manager_instance ** result_fs) +{ + sos_ret_t retval; + struct sos_fs_pathname src_path, dst_path; + struct sos_fs_nscache_node * src_nsnode, * dst_nsnode; + struct sos_fs_manager_type * fs_type; + struct sos_fs_manager_instance * new_fs; + int nb_fstypes; + + if (_dst_pathlen <= 0) + return -SOS_ENOENT; + + /* Look for the FS manager type */ + list_foreach(fs_list, fs_type, nb_fstypes) + { + if (! strcmp(fsname, fs_type->name)) + break; + } + if (! list_foreach_early_break(fs_list, fs_type, nb_fstypes)) + return -SOS_ENODEV; + + src_path.contents = _src_path; + src_path.length = _src_pathlen; + + /* Compute the start_nsnode for the source */ + if (src_path.length <= 0) + src_nsnode = NULL; + else if (src_path.contents[0] == '/') + src_nsnode = sos_process_get_root(actor)->direntry; + else + src_nsnode = sos_process_get_cwd(actor)->direntry; + + /* Lookup the source node */ + if (src_nsnode) + { + retval = fs_lookup_node(& src_path, TRUE, + sos_process_get_root(actor)->direntry, + src_nsnode, + & src_nsnode, & src_path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (src_path.length > 0) + { + sos_fs_nscache_unref_node(src_nsnode); + return -SOS_ENOENT; + } + } + + dst_path.contents = _dst_path; + dst_path.length = _dst_pathlen; + + /* Compute the start_nsnode for the destination */ + if (dst_path.contents[0] == '/') + dst_nsnode = sos_process_get_root(actor)->direntry; + else + dst_nsnode = sos_process_get_cwd(actor)->direntry; + + /* Lookup the destination node */ + retval = fs_lookup_node(& dst_path, TRUE, + sos_process_get_root(actor)->direntry, + dst_nsnode, + & dst_nsnode, & dst_path, 0); + if ((SOS_OK != retval) || (dst_path.length > 0)) + { + if (src_nsnode) + sos_fs_nscache_unref_node(src_nsnode); + if (dst_path.length > 0) + retval = -SOS_ENOENT; + return retval; + } + + /* Actually call the mount callback of the FS */ + retval + = fs_type->mount(fs_type, + (src_nsnode)?sos_fs_nscache_get_fs_node(src_nsnode):NULL, + args, & new_fs); + if (SOS_OK != retval) + { + if (src_nsnode) + sos_fs_nscache_unref_node(src_nsnode); + sos_fs_nscache_unref_node(dst_nsnode); + return retval; + } + + /* Make sure the nodecache was created */ + SOS_ASSERT_FATAL(NULL != new_fs->nodecache); + SOS_ASSERT_FATAL(NULL != new_fs->root); + + /* Update some reserved fields */ + sos_fs_nscache_get_fs_node(new_fs->root)->fs = new_fs; + + /* Register the mountpoint in the nscache */ + retval = sos_fs_nscache_mount(dst_nsnode, new_fs->root); + SOS_ASSERT_FATAL(SOS_OK == retval); + + /* Un-reference the temporary nsnodes */ + if (src_nsnode) + sos_fs_nscache_unref_node(src_nsnode); + sos_fs_nscache_unref_node(dst_nsnode); + + if (result_fs) + *result_fs = new_fs; + + return SOS_OK; +} + + +sos_ret_t sos_fs_umount(struct sos_process * actor, + const char * _mounted_root_path, + sos_size_t _mounted_root_pathlen) +{ + sos_ret_t retval; + struct sos_fs_pathname mounted_root_path; + struct sos_fs_nscache_node * mounted_root_nsnode; + struct sos_fs_manager_instance * fs; + + if (_mounted_root_pathlen <= 0) + return -SOS_ENOENT; + + mounted_root_path.contents = _mounted_root_path; + mounted_root_path.length = _mounted_root_pathlen; + + /* Compute the start_nsnode for the mounted_root */ + if (mounted_root_path.contents[0] == '/') + mounted_root_nsnode = sos_process_get_root(actor)->direntry; + else + mounted_root_nsnode = sos_process_get_cwd(actor)->direntry; + + /* Lookup the mounted_root node */ + retval = fs_lookup_node(& mounted_root_path, TRUE, + sos_process_get_root(actor)->direntry, + mounted_root_nsnode, + & mounted_root_nsnode, & mounted_root_path, 0); + if (SOS_OK != retval) + return retval; + + /* Make sure the whole path has been resolved */ + if (mounted_root_path.length > 0) + { + sos_fs_nscache_unref_node(mounted_root_nsnode); + return -SOS_ENOENT; + } + + /* Make sure this node is the real root of the FS */ + fs = sos_fs_nscache_get_fs_node(mounted_root_nsnode)->fs; + if (fs->root != mounted_root_nsnode) + { + sos_fs_nscache_unref_node(mounted_root_nsnode); + return -SOS_ENOENT; + } + + /* Disconnect this FS mounted_root from namespace cache */ + retval = sos_fs_nscache_umount(mounted_root_nsnode); + + /* Mounted_Root not needed anymore */ + sos_fs_nscache_unref_node(mounted_root_nsnode); + if (SOS_OK != retval) + return retval; + + fs->root = NULL; + + /* Flush any changes to disk */ + retval = sos_fs_sync_fs(fs); + if (SOS_OK != retval) + { + return retval; + } + + retval = fs->fs_type->umount(fs->fs_type, fs); + return retval; +} diff --git a/sos/fs.h b/sos/fs.h new file mode 100644 index 0000000..431fdeb --- /dev/null +++ b/sos/fs.h @@ -0,0 +1,1140 @@ +/* Copyright (C) 2005,2006 David Decotigny + Copyright (C) 2000-2005 The KOS Team (Thomas Petazzoni, David + Decotigny, Julien Munier) + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FS_H_ +#define _SOS_FS_H_ + + +/** + * @file fs.h + * + * (Virtual) Filesystem management. + * + * SOS provides a complete Unix-like file system service. Supported + * features of this service are: + * - mountpoints + * - generic file system support (FS) through object-oriented + * specialization (so-called VFS) + * - hard & symbolic links + * - regular files and directories + * - block and character device special files (from article 9 onward) + * - file mapping + * - basic permission management ("rwx" only, no notion of owner) + * - chroot + * - separation of disk node and namespace notions allowing hard links + * and to move/rename/remove files or directories that are in use + * - basic FS interface (open/read/seek/creat/mkdir/rename/link + * / symlink/chmod/mount/fcntl/ioctl...) + * - deferred writes (ie file caching). @see sync(3) + * + * Among the unsupported features: + * - no user-based permission (uid/gid, ACLS) + * - no modification / access time accounting + * - no Fifo/socket special files (yet) + * - no generic support library for common fcntl commands + * (F_SETOWN/GETLEASE/NOTIFY, etc.) + * - no flock-style functions + * + * Rationale + * ========= + * The VFS layer is based on 3 central concepts: + * + * - The meta-information for each file stored on disk: size, + * permissions, ... (struct sos_fs_node for SOS, inode for Unix) + * + * It is sufficient to know where this meta-information is located + * on disk (a simple sector number most of the time) to build the + * corresponding struct sos_fs_node into memory and to retrieve the + * data of the file from the disk + * + * For example, consider that we know a sector that holds the meta + * information is located at sector 64589 on disk. By retrieving + * this meta information directly from disk, we can build the + * struct sos_fs_node, which would (for example) tell that the + * corresponding file spans (for example) over sectors 4345, 5645, + * 4539 and 6575, is 1.7kB long + * + * Everything is stored this way on the disk, even the + * directories. From the disk contents' point of view, a directory + * is simply a file whose contents represent a list of mappings + * "name" -> meta-information location + * + * - One or many nodes in the file hierarchy pointing to this data + * (struct sos_fs_nscache_node for SOS, struct dentry for Linux). This + * tells that the entry "toto" in directory "/home/zorglub" + * corresponds to the given struct sos_fs_node + * + * Actually, with the struct sos_fs_node above, we can reach any + * file in the system. However, dealing with mountpoints requires + * an intermediary data structure because a directory on a disk + * cannot make reference to children struct sos_fs_node on other + * disk. This is one of the reasons why there is this struct + * sos_fs_nscache_node. Another reason is that we kind-of "cache" the + * most used struct sos_fs_node: those that lead from the global + * root ("/") to the files and directories currently being used + * (hence the name "nscache" for "namespace cache"). This speeds-up + * the path-resolving process (aka "lookup"), as the most-used path + * are already in-memory and the struct sos_fs_node are already + * in-memory too. + * + * A struct sos_fs_nscache_node can have at most 1 parent (the ".." + * entry). It can also have 0 parent in case the node is being used + * by a process (file is opened or mapped), but the file is + * actually "removed", ie un-reachable from any directory. + * + * Many such structures can reference the same underlying struct + * sos_fs_node, which enables the support of "hard links". + * + * - The "opened file" strucures. They store the information + * pertaining to a particular usage of a file. The most important + * thing they store is the "file pointer", which holds the + * location in the file where the next read/write operation should + * start + * + * Each process has at least 2 such opened files: its "current + * working directory" (RTFM chdir) and its "process root" (RTFM + * chroot). Those are heritated across fork() and can be changed by + * appropriate syscalls (resp. chdir/chroot). The main "root" of + * the system is the process root of the "init" process. The usual + * opened files (think of open() and opendir()) are stored in the + * file descriptor array (fds[]). This is the index in this array + * that is commonly called a "file descriptor". + * + * + * The whole VFS layer comprises a series of objects that can be + * specialized to implement different FS support (fat, ext2, ffs, ...): + * + * - The notion of "file system manager", which basically is a + * container to a FS name (eg "FAT", "EXT2", etc...) and a series of + * functions responsible for initializing a particular "mounting" of + * a FS (the "mount" method). This is SOS's struct sos_fs_manager_type + * + * - The notion of "file system instance" which contains the data + * proper to a particular mounting of an FS. Its most important job + * is to allocate new struct sos_fs_node on disk, or to retrieve the + * meta-information (ie struct sos_fs_node) located at the given + * location on disk. This is roughly THE primary physical interface + * between the VFS and the disks. This is SOS's struct + * sos_fs_manager_instance, aka the Linux's superblock + * + * For each struct sos_fs_node that it allocates, or that is loads + * from disk into memory, this "instance manager" is responsible + * for inidicating the functions that implement the FS-dedicated + * routine such as read/write/mmap/ioctl/... for this precise node. + * + * The nodes (struct sos_fs_node) of a struct + * sos_fs_manager_instance that are currently loaded in memory are + * stored in a hash-table. The key of this map is the location of the + * meta-information on disk. That way, it is very fast to look for + * the given meta-information whose location on disk is knows: if + * it has already been loaded into memory, its memory address is + * quickly resolved thanks to this hash table. + */ + +#include +#include +#include +#include + +/* Forward declarations (structures defined in this file) */ +struct sos_fs_manager_type; +struct sos_fs_manager_instance; +struct sos_fs_statfs; +struct sos_fs_node; +struct sos_fs_opened_file; +struct sos_fs_stat; + +#include "fs_nscache.h" +#include + +/** + * The type of filesystem object. + * + * Each struct sos_fs_node has a type. Here are the supported types. + */ +typedef enum { + SOS_FS_NODE_REGULAR_FILE = 0x42, + SOS_FS_NODE_DIRECTORY = 0x24, + SOS_FS_NODE_SYMLINK = 0x84, + SOS_FS_NODE_DEVICE_CHAR = 0x48, + SOS_FS_NODE_DEVICE_BLOCK = 0x12 +} sos_fs_node_type_t; + + +#define SOS_FS_MANAGER_NAME_MAXLEN 32 +/** + * Description of a supported Filesystem type. + * + * These descriptions are listed in an internal list (see + * fs.c:fs_list), and each time we want to mount a FS, we precise a + * name (eg "FAT", "EXT2", ...). The VFS will look for this name into + * the list of supported filesystem types, and, when found, call its + * sos_fs_manager_type::mount() method. + * + * New filesystem types are registered using sos_fs_register_fs_type() + */ +struct sos_fs_manager_type +{ + char name[SOS_FS_MANAGER_NAME_MAXLEN]; + + /** + * Responsible for making sure the underlying device (if any) really + * stores the correct filesystem format, for creating the hash of fs + * nodes and for calling sos_fs_register_fs_instance + * + * @param device May be NULL + * + * @note mandatory, may block + */ + sos_ret_t (*mount)(struct sos_fs_manager_type * this, + struct sos_fs_node * device, + const char * args, + struct sos_fs_manager_instance ** mounted_fs); + + /** + * Responsible for de-allocating the hash of fs nodes and for + * calling sos_fs_unregister_fs_instance + * + * @note mandatory, may block + */ + sos_ret_t (*umount)(struct sos_fs_manager_type * this, + struct sos_fs_manager_instance * mounted_fs); + + /** Free of use */ + void * custom_data; + + /** List of filesystem instances of this type currently mounted + somewhere in the system */ + struct sos_fs_manager_instance * instances; + + /** Linkage for the list of filesystem types registered in the + system */ + struct sos_fs_manager_type *prev, *next; +}; + + +/** + * Data related to a particular "mounting" of a file system. A + * so-called "superblock" under Linux + * + * This holds the FUNDAMENTAL functions responsible for loading struct + * sos_fs_node from disk, or for allocating thom on disk. It also + * holds the hash-table of struct sos_fs_node already loaded into + * memory. + */ +struct sos_fs_manager_instance +{ + /** + * @note Publicly readable. Written only by sos_fs_manager_type::mount() + */ + struct sos_fs_manager_type * fs_type; + + /** + * Usually, a filesystem relies on a device (disk, network, ram, + * ...) to fetch its data. This is the location of the device. + * + * @note Publicly readable. Written only by fs.c + */ + struct sos_fs_node * device; + +#define SOS_FS_MOUNT_SYNC (1 << 0) +#define SOS_FS_MOUNT_READONLY (1 << 1) +#define SOS_FS_MOUNT_NOEXEC (1 << 2) + /** + * Is this FS read-only, without EXEC file permission, write-through + * ? Or-red combination of the SOS_FS_MOUNT_ flags + * + * @note Publicly readable. Written only by fs.c + */ + sos_ui32_t flags; + + /** + * The namespace node that is the root of THIS file system mounting + * + * @note Publicly readable. Written only by fs.c + */ + struct sos_fs_nscache_node * root; + + /** + * List of dirty nodes. These are the nodes that need to be written + * back to disk. With FS supporting deferred-writes, the + * sos_fs_sync() function will use this list to flush the dirty + * nodes back to disk. + * + * @note Reserved to fs.c + */ + struct sos_fs_node * dirty_nodes; + + /** + * Build a fresh new FS node at the given location. This implies + * the allocation of a new sos_fs_node structure in memory + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*fetch_node_from_disk)(struct sos_fs_manager_instance * this, + sos_ui64_t storage_location, + struct sos_fs_node ** result); + + /** + * Build a fresh new FS node ON THE DISK of the given type (dir, + * plain file, symlink, ...), completely empty ; return a newly + * allocated IN-MEMORY node structure representing it + * + * @param open_creat_flags is the open_flags parameter passed to + * sos_fs_open() when O_CREAT is set. 0 when allocated trough + * creat/mkdir/mknod/symlink + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*allocate_new_node)(struct sos_fs_manager_instance * this, + sos_fs_node_type_t type, + const struct sos_process * creator, + sos_ui32_t access_rights, + sos_ui32_t open_creat_flags, + struct sos_fs_node ** result); + + /** + * Return filesystem status (RTFM df) + * + * @note Optional, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*statfs)(struct sos_fs_manager_instance * this, + struct sos_fs_statfs * result); + + /** + * Comparison callback called when looking for file/dirs in the + * namespace cache. Normally, a usual lexicographical comparison is + * done (when this function points to NULL). But for some FS, it + * might be useful to use another comparison function (eg for + * case-insensitive FS) + * + * @note Optional (may be NULL), must NOT block + */ + sos_bool_t (*nsnode_same_name)(const char * name1, sos_ui16_t namelen1, + const char * name2, sos_ui16_t namelen2); + + /** + * Hash table of the struct sos_fs_node of this filesystem instance + * loaded in memory: key=storage_location, element=sos_fs_node + */ + struct sos_hash_table * nodecache; + + /** + * Unique identifier of this FS (used in sync method, updated by + * fs.c). This enables sync_all_fs to be resilient to mount/umount + * and (un)register_fs_type/instance + */ + sos_ui64_t uid; + + void * custom_data; + + /** Linkage for the list of instances for the underlying fs type */ + struct sos_fs_manager_instance * prev, * next; +}; + + +/** + * The CENTRAL data structure of the whole thing. A so-called "inode" + * + * This represents the meta-information related to a file on disk: its + * permission, where its data is located. Actually, in SOS, these + * information are not stored in this structure. Instead, we define a + * series of methods in this structure that MUST be implemented by the + * FS and that realize the higher level operations needed by the + * OS. These operations will rely on the meta-information that the FS + * code MUST define and manage by itself (hence the + * sos_fs_node::custom_data field). + */ +struct sos_fs_node +{ + /** + * An struct sos_fs_node always belong to exactly ONE file system + */ + struct sos_fs_manager_instance * fs; + + /** + * The so-called "inode": location of this node inside the FS + * instance. Updated by struct + * sos_fs_manager_instance::fetch_node_from_disk() + */ + sos_ui64_t storage_location; + + /** + * Number of ON-DISK links to this node. + * + * - For everything but directories: the number of hard links to the file + * - For directories: 1 + the number of children nodes + * + * @note Publicly readable. Written only by + * sos_fs_node_ops_dir::link() and sos_fs_node_ops_dir::unlink() + */ + sos_count_t ondisk_lnk_cnt; + + /** + * Number of IN-MEMORY nscache_nodes referencing this FS node. + * + * Corresponds to the number of struct sos_fs_nscache_node pointing + * to this node. This could be as much as ondisk_lnk_cnt + 1, but is + * usually less + * + * @note Reserved to fs.c + */ + sos_count_t inmem_ref_cnt; + + /** + * Directory, symlink, ... + * + * @see sos_fs_node_type_t + * + * @note Publicly readable. Written only by fs.c + */ + sos_fs_node_type_t type; + +#define SOS_FS_READABLE 00400 +#define SOS_FS_WRITABLE 00200 +#define SOS_FS_EXECUTABLE 00100 + /** + * read/write, ... @see the SOS_FS_*ABLE flags + * @note Publicly readable. Written only by fs.c + */ + sos_ui32_t access_rights; + + /** + * @note Reserved to fs.c + */ + sos_bool_t dirty; + + /** + * Incremented each time one of the opened files for this node is + * modified + * @note Publicly readable. Written only by fs.c + */ + sos_lcount_t generation; + + /** + * @note Available only for device files (char/block) + * @note Publicly readable. Written only by + * sos_fs_manager_instance::fetch_node_from_disk() and mknod() + */ + struct sos_fs_dev_id_t + { + sos_ui32_t device_class; /**< aka "major" */ + sos_ui32_t device_instance; /**< aka "minor" */ + } dev_id; + + /** Operations common to all node types */ + struct sos_fs_node_ops_file *ops_file; + + /** Operations specific to some node types */ + union + { + /** when type == SOS_FS_NODE_DIRECTORY */ + struct sos_fs_node_ops_dir *ops_dir; + + /** + * when type == SOS_FS_NODE_DEVICE_BLOCK + * The FS node has a link to some data pertaining to the device, + * not to any special operations + * @see blkdev.c for a definition of this structure + */ + struct sos_blockdev_instance *block_device; + + /** when type == SOS_FS_NODE_SYMLINK */ + struct sos_fs_node_ops_symlink *ops_symlink; + }; /* Anonymous union (gcc extension) */ + + + /** + * Simply free this FS node from the kernel memory: it does NOT + * mean that the corresponding on-disk node is free ! Actually, the + * corresponding ON-DISK node is free iff ondisk_lnk_cnt == 0. No + * need to sync anything to disk, as the VFS will sync the node + * before calling this method + * + * @note Mandatory, may block, no special locking needed + */ + sos_ret_t (*destructor)(struct sos_fs_node * this); + + /** + * Called when a process opens the node + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + * @note FS-specific EXCEPT for device special files (char & + * block) because they are handled in an uniform way by the + * chardev/blockdev subsystems + * @note As a consequence, FS code can safely assume that "this" is + * never a character or block device + */ + sos_ret_t (*new_opened_file)(struct sos_fs_node * this, + const struct sos_process * owner, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of); + + /** + * Called when a process closes the node + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + * @note FS-specific EXCEPT for device special files (char & + * block) because they are handled in an uniform way by the + * chardev/blockdev subsystems + * @note As a consequence, FS code can safely assume that "this" is + * never a character or block device + */ + sos_ret_t (*close_opened_file)(struct sos_fs_node * this, + struct sos_fs_opened_file * of); + + /** + * This should hold the meta information for this node as needed by + * the FS instance. + */ + void * custom_data; + + /** Hash linkage entry for this FS node in the nodecache + dictionary */ + struct sos_hash_linkage hlink_nodecache; + + /** Linkage to list the dirty nodes of the given FS */ + struct sos_fs_node *prev_dirty, *next_dirty; +}; + + + +/** + * The list of methods implementing the basic VFS operations on the + * given struct sos_fs_node + * + * @see sos_fs_node::ops_file + */ +struct sos_fs_node_ops_file +{ + /** + * Change size of file + * + * @note Optional, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*truncate)(struct sos_fs_node *this, + sos_lsoffset_t length); + + /** + * Retrieve the status (eg size) of the file + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*stat)(struct sos_fs_node * this, + struct sos_fs_stat * result); + + /** + * Change the sos_fs_node::access_rights attribute + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*chmod)(struct sos_fs_node * this, + sos_ui32_t new_access_rights); + + + /** + * Flush any change to the node back to the file system backing store + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*sync)(struct sos_fs_node *this); +}; + + +/** + * The list of methods implementing the basic VFS symlink operations + * + * @see sos_fs_node::ops_symlink + */ +struct sos_fs_node_ops_symlink +{ + /** + * Used by the _kernel_ to resolve the symlinks. To change/create a + * symlink target, it is needed only from userland: the read/write + * methods are made for this + * + * @param target Pointer to the string representing the target's + * path, allocated for the fs_node's lifetime ! + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*expand)(struct sos_fs_node *this, + char const ** target, + sos_size_t * target_len); +}; + + +/** + * The list of methods implementing the basic VFS directory operations + * + * @see sos_fs_node::ops_dir + */ +struct sos_fs_node_ops_dir +{ + /** + * Look for the on-disk location of the sos_fs_node having the given + * name + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + */ + sos_ret_t (*lookup)(struct sos_fs_node *this, + const char * name, sos_ui16_t namelen, + sos_ui64_t * result_storage_location); + + /** + * Add a new reference in the current sos_fs_node to the on-disk + * location of the given sos_fs_node + * + * @note Responsible for updating this->ondisk_lnk_cnt + * @note Mandatory for writable directories, may block. Appropriate + * locking MUST be implemented + */ + sos_ret_t (*link)(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen, + struct sos_fs_node * node); + + /** + * Remove the entry in the current sos_fs_node for the on-disk + * location with the given name + * + * @note Responsible for updating this->ondisk_lnk_cnt + * @note Mandatory for writable directories, may block. Appropriate + * locking MUST be implemented + */ + sos_ret_t (*unlink)(struct sos_fs_node *this, + const struct sos_process *actor, + const char * entry_name, sos_ui16_t entry_namelen); +}; + + +/** + * The data structure holding information and method related to a + * particular usage of a file. A so-called "struct file" + * + * This represents the kernel structure behind a "file descriptor" or + * behind a chdir/chroot. Among other things, it holds the methods + * responsible for reading/writing into the file, and for moving the + * file pointer (see @sos_fs_opened_file::position) inside it. + */ +struct sos_fs_opened_file +{ + /** The process that opened the file/dir */ + const struct sos_process * owner; + + /** + * The reference to the sos_fs_nscache_node and, hence, to the underlying sos_fs_node. + * + * Used to cache the in-memory fs nodes + */ + struct sos_fs_nscache_node * direntry; + + /** Use for memory-management */ + sos_count_t ref_cnt; + + /** + * Always > 0 (ie max size = 2^63-1 = 9.2 10^18). We make it + * "signed" here to limit its range. Because we must be able to + * seek to the begining of the file with SEEK_END and a negative + * offset, so the max size of the file must be reachable by a lseek + * offset + * + * @note reserved to filesystem instance code. Not modified nor used + * by fs.c + */ + sos_lsoffset_t position; + + /** + * Incremented each time this opened file is modified + * + * Used to implement a readdir method resilient to + * creat/mkdir/rmdir/unlink + */ + sos_lcount_t generation; + + /** + * @see SOS_FS_OPEN_* flags + */ + sos_ui32_t open_flags; + + /** Operations common to all node types */ + struct sos_fs_ops_opened_file * ops_file; + + /** Operations specific to some node types */ + union + { + /** when direntry->fs_node->type == SOS_FS_NODE_DIRECTORY */ + struct sos_fs_ops_opened_dir * ops_dir; + + /** when direntry->fs_node->type == SOS_FS_NODE_DEVICE_CHAR */ + struct sos_fs_ops_opened_chardev * ops_chardev; + + /** when direntry->fs_node->type == SOS_FS_NODE_DEVICE_BLOCK */ + struct sos_fs_ops_opened_blockdev * ops_blockdev; + }; /* Anonymous union (gcc extension) */ + + /** + * Called upon fork() to duplicate all the opened files + * + * @note FS-specific EXCEPT for device special files (char & + * block) because they are handled in an uniform way by the + * chardev/blockdev subsystems + * @note As a consequence, FS code can safely assume that "this" is + * never a character or block device + */ + sos_ret_t (*duplicate)(struct sos_fs_opened_file *this, + const struct sos_process * for_owner, + struct sos_fs_opened_file **result); + + void * custom_data; +}; + + +/** + * Reference position for sos_fs_seek + */ +typedef enum { SOS_SEEK_SET=42, + SOS_SEEK_CUR=24, + SOS_SEEK_END=84 } sos_seek_whence_t; +/** + * The list of methods implementing the basic VFS opened file + * operations + * + * See the Unix manual pages, they basically form the interfaces to to + * these functions + * + * @see sos_fs_opened_file::ops_file + */ +struct sos_fs_ops_opened_file +{ + /** + * @note Mandatory, may block. Appropriate locking MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*seek)(struct sos_fs_opened_file *this, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + /* out */ sos_lsoffset_t * result_position); + + /** + * @note Mandatory, may block. Appropriate locking MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*read)(struct sos_fs_opened_file *this, + sos_uaddr_t dest_buf, + sos_size_t * /* in/out */len); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*write)(struct sos_fs_opened_file *this, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*mmap)(struct sos_fs_opened_file *this, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset); + + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*fcntl)(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); +}; + + +/** + * The list of methods implementing the basic VFS opened character device + * operations + * + * @see sos_fs_opened_file::ops_file + */ +struct sos_fs_ops_opened_chardev +{ + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*ioctl)(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); +}; + + +/** + * The list of methods implementing the basic VFS opened block device + * operations + * + * @see sos_fs_opened_file::ops_file + */ +struct sos_fs_ops_opened_blockdev +{ + /** + * @note Optional (might be NULL), may block. Appropriate locking + * MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*ioctl)(struct sos_fs_opened_file *this, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); +}; + + +/** Data structure that is to be filled by readdir */ +struct sos_fs_dirent +{ + sos_ui64_t storage_location; + sos_si64_t offset_in_dirfile; + sos_ui32_t type; + sos_ui16_t namelen; + +#define SOS_FS_DIRENT_NAME_MAXLEN 128 + char name[SOS_FS_DIRENT_NAME_MAXLEN]; +}; + + +/** + * The list of methods implementing the basic VFS opened directory + * operations + * + * @see sos_fs_opened_file::ops_file + */ +struct sos_fs_ops_opened_dir +{ + /** + * Each time it is called, responsible for filling the sos_fs_dirent + * structure, return -SOS_ENOENT when done. + * + * @note Mandatory, may block. Appropriate locking MUST be implemented + * @note Please call sos_fs_mark_dirty() if disk contents is changed + */ + sos_ret_t (*readdir)(struct sos_fs_opened_file *this, + struct sos_fs_dirent * result); +}; + + + +/** + * Used by the stat calls + * + * @see sos_fs_node_ops_file::stat + */ +struct sos_fs_stat +{ + struct sos_fs_dev_id_t st_rdev; + sos_fs_node_type_t st_type; + sos_ui64_t st_storage_location; + sos_ui32_t st_access_rights; + sos_count_t st_nlink; + sos_si64_t st_size; +}; + + +/** + * Used by the statvfs calls + * + * @see sos_fs_manager_instance::statfs + */ +struct sos_fs_statfs +{ + struct sos_fs_dev_id_t f_rdev; + sos_size_t f_sz_total; /**< Total size */ + sos_size_t f_sz_free; /**< Size left on device */ + sos_count_t f_node_total;/**< Total allocatable FS nodes */ + sos_count_t f_node_avail;/**< Number of available free FS nodes */ + sos_ui32_t f_flags; +}; + + +/** + * Must be called AFTER the FS manager types needed to mount the root + * filesystem have been registered + */ +sos_ret_t sos_fs_subsystem_setup(const char * root_device, + const char * fs_type, + const char * mount_args, + struct sos_fs_manager_instance ** result_rootfs); + + +/* *************************************************************** + * The Following functions are relatively standard + * + * @see Unix manual pages for details + */ + + +/** + * mount a file system + * + * @param actor process calling mount + * @param _src_path(len) may be NULL (as for virtfs or /proc) + * @fsname the name of the filesystem type to mount + * @args any args passed to the sos_fs_manager_type::mount method + * @result_fs the resulting filesystem instance + */ +sos_ret_t sos_fs_mount(struct sos_process * actor, + const char * _src_path, + sos_size_t _src_pathlen, + const char * _dst_path, + sos_size_t _dst_pathlen, + const char * fsname, + sos_ui32_t mountflags, + const char * args, + struct sos_fs_manager_instance ** /*out*/result_fs); + +/** + * unmount the filesystem at the given location + */ +sos_ret_t sos_fs_umount(struct sos_process * actor, + const char * _mountpoint_path, + sos_size_t _mountpoint_pathlen); + +/** + * Flush all the dirty nodes of all the FS to disk + */ +sos_ret_t sos_fs_sync_all_fs(void); + +/** + * Retrieve filesystem status, or return -SOS_ENOSYS if filesystem + * cannot report this + */ +sos_ret_t sos_fs_vfstat(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + struct sos_fs_statfs * result); + +/** + * Open flags + */ +#define SOS_FS_OPEN_EXCL (1 << 0) +#define SOS_FS_OPEN_CREAT (1 << 1) +#define SOS_FS_OPEN_TRUNC (1 << 2) +#define SOS_FS_OPEN_NOFOLLOW (1 << 3) +#define SOS_FS_OPEN_DIRECTORY (1 << 4) /* Incompatible with CREAT/TRUNC */ +#define SOS_FS_OPEN_SYNC (1 << 5) +#define SOS_FS_OPEN_CLOSEONEXEC (1 << 6) /* By default, files are kept + open upon an exec() */ + +#define SOS_FS_OPEN_READ (1 << 16) +#define SOS_FS_OPEN_WRITE (1 << 17) + + +/** + * FS access rights + */ +#define SOS_FS_S_IRUSR 00400 +#define SOS_FS_S_IWUSR 00200 +#define SOS_FS_S_IXUSR 00100 + +#define SOS_FS_S_IRWXALL 07777 /* For symlinks */ + +sos_ret_t sos_fs_open(const struct sos_process *owner, + const char *_path, + sos_size_t _pathlen, + sos_ui32_t open_flags, + sos_ui32_t creat_access_rights, + struct sos_fs_opened_file ** of); + +sos_ret_t sos_fs_close(struct sos_fs_opened_file * of); + +sos_ret_t sos_fs_read(struct sos_fs_opened_file * of, + sos_uaddr_t dest_buf, + sos_size_t * /* in/ou */len); + +sos_ret_t sos_fs_readdir(struct sos_fs_opened_file * of, + struct sos_fs_dirent * result); + +sos_ret_t sos_fs_write(struct sos_fs_opened_file * of, + sos_uaddr_t src_buf, + sos_size_t * /* in/out */len); + +sos_ret_t sos_fs_seek(struct sos_fs_opened_file *of, + sos_lsoffset_t offset, + sos_seek_whence_t whence, + sos_lsoffset_t * result_position); + +sos_ret_t sos_fs_ftruncate(struct sos_fs_opened_file *of, + sos_lsoffset_t length); + +sos_ret_t sos_fs_mmap(struct sos_fs_opened_file *of, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + sos_luoffset_t offset); + +sos_ret_t sos_fs_fsync(struct sos_fs_opened_file * of); + +sos_ret_t sos_fs_fcntl(struct sos_fs_opened_file *of, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); + +sos_ret_t sos_fs_ioctl(struct sos_fs_opened_file *of, + int req_id, + sos_ui32_t req_arg /* Usually: sos_uaddr_t */); + +sos_ret_t sos_fs_creat(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights); + +sos_ret_t sos_fs_link(const struct sos_process * creator, + const char * _old_path, + sos_size_t _old_pathlen, + const char * _dest_path, + sos_size_t _dest_pathlen); + +sos_ret_t sos_fs_rename(const struct sos_process * creator, + const char * _old_path, + sos_size_t _old_pathlen, + const char * _dest_path, + sos_size_t _dest_pathlen); + +sos_ret_t sos_fs_unlink(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen); + +sos_ret_t sos_fs_symlink(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_uaddr_t symlink_target, + sos_size_t symlink_target_len); + +sos_ret_t sos_fs_mknod(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_fs_node_type_t type /* only block/char allowed */, + sos_ui32_t access_rights, + const struct sos_fs_dev_id_t * devid); + +sos_ret_t sos_fs_mkdir(const struct sos_process * creator, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights); + +sos_ret_t sos_fs_rmdir(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen); + +sos_ret_t sos_fs_chmod(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + sos_ui32_t access_rights); + +sos_ret_t sos_fs_stat(const struct sos_process * actor, + const char * _path, + sos_size_t _pathlen, + int nofollow, + struct sos_fs_stat * result); + + +/* *************************************************************** + * Restricted functions reserved to FS code and block/char devices + */ + +/** + * Function to be called when proposing a new File system type + */ +sos_ret_t sos_fs_register_fs_type(struct sos_fs_manager_type * fstype); +sos_ret_t sos_fs_unregister_fs_type(struct sos_fs_manager_type * fstype); + +/** + * Marthe given file as dirty, for FS supporting deferred write access + * mode + */ +sos_ret_t sos_fs_mark_dirty(struct sos_fs_opened_file * of); + +/** + * Helper function to be called from the mount() method of the FS + * instance code. Responsible for creating and updating the "root" + * field of the FS instance structure and for connecting this FS in + * the nscache + * @param root_fsnode The root of the FS being mounted + */ +sos_ret_t sos_fs_register_fs_instance(struct sos_fs_manager_instance * fs, + struct sos_fs_node * root_fsnode); + +/** + * Helper function to be called from the umount() method of the FS + * instance code. Responsible for unregistering the instance from the + * FS type's instances list and for disconnecting this mountpoint in + * the nscache. + */ +sos_ret_t sos_fs_unregister_fs_instance(struct sos_fs_manager_instance * fs); + + +/* *************************************************************** + * Restricted functions reserved to syscall.c + */ +sos_ret_t sos_fs_ref_opened_file(struct sos_fs_opened_file * of); +sos_ret_t _sos_fs_unref_opened_file(struct sos_fs_opened_file ** of); +#define sos_fs_unref_opened_file(f) _sos_fs_unref_opened_file(&(f)) + + +/* *************************************************************** + * Restricted functions to be used only by fs_nscache.c + */ + +sos_ret_t sos_fs_ref_fsnode(struct sos_fs_node * fsnode); + +sos_ret_t _sos_fs_unref_fsnode(struct sos_fs_node * fsnode); +#define sos_fs_unref_fsnode(n) \ + ({ sos_ret_t __retval = _sos_fs_unref_fsnode(n); (n)=NULL; __retval; }) + + +/* *************************************************************** + * Restricted functions reserved to process.c and main.c:start_init() + */ +sos_ret_t sos_fs_new_opened_file(const struct sos_process * proc, + struct sos_fs_nscache_node * nsnode, + sos_ui32_t open_flags, + struct sos_fs_opened_file ** result_of); + + +sos_ret_t sos_fs_duplicate_opened_file(struct sos_fs_opened_file * src_of, + const struct sos_process * dst_proc, + struct sos_fs_opened_file ** result_of); + +/** + * Generic fcntl function that can be called from inside the FS's + * fcntl method. It will handle the following fcntl commands: + * - F_DUPFD + * - F_GETFD + * - F_SETFD + * - F_GETFL + * - F_SETFL + * Any other command will lead to a -SOS_ENOSUP return value. + * It will take care not to change anything besides the fs.h structures. + */ +sos_ret_t sos_fs_basic_fcntl_helper(struct sos_fs_opened_file * src_of, + int req_id, sos_ui32_t req_arg); + +#endif /* _SOS_FS_H_ */ diff --git a/sos/fs_nscache.c b/sos/fs_nscache.c new file mode 100644 index 0000000..d6ba80c --- /dev/null +++ b/sos/fs_nscache.c @@ -0,0 +1,527 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2000-2005 The KOS Team (Thomas Petazzoni, David + Decotigny, Julien Munier) + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "fs_nscache.h" + + +/** + * A so-called "dentry" / "nsnode" structure. Used to make the + * "in-memory" representation of the file system files/dir/devices + * that have been used up to now + */ +struct sos_fs_nscache_node +{ + /** The reference to the associated sos_fs_node */ + struct sos_fs_node *fs_node; + + struct sos_fs_pathname name; + + /** Number of references to that node, reference from parent (if + any) is EXCLUDED */ + sos_count_t ref_cnt; + + /* + * Ued to chain the mounted filesystem + */ + + /** If this node is a mountpoint (a file system is mounted on it): + reference to the filesystem mounted on it */ + struct sos_fs_nscache_node *mounted_root; + /** If this node is the root of a mounted filesystem: reference to + the mountpoint where it is mounted on */ + struct sos_fs_nscache_node *mountpoint; + + /** ".." */ + struct sos_fs_nscache_node *parent; + + /** List of the already known children */ + struct sos_fs_nscache_node *children; + + /** Other children for the same parent */ + struct sos_fs_nscache_node *siblings_prev, *siblings_next; +}; + + +/** The cache of nscache_node objects */ +static struct sos_kslab_cache * cache_of_nscache_nodes; + + +sos_ret_t sos_fs_nscache_subsystem_setup() +{ + cache_of_nscache_nodes + = sos_kmem_cache_create("fs_nscache", + sizeof(struct sos_fs_nscache_node), + 3, 0, + SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); + if (! cache_of_nscache_nodes) + return -SOS_ENOMEM; + + return SOS_OK; +}; + + +sos_bool_t +sos_fs_pathname_eat_slashes(const struct sos_fs_pathname * path, + struct sos_fs_pathname * result) +{ + sos_bool_t retval = FALSE; + + result->contents = path->contents; + result->length = path->length; + while (result->length > 0) + { + if (*result->contents != '/') + break; + + result->contents ++; + result->length --; + retval = TRUE; + } + + if(result->length <= 0) + result->contents = NULL; + + return retval; +} + + +static sos_bool_t +sos_fs_pathname_eat_non_slashes(const struct sos_fs_pathname * path, + struct sos_fs_pathname * result) +{ + sos_bool_t retval = FALSE; + + result->contents = path->contents; + result->length = path->length; + while (result->length > 0) + { + if (*result->contents == '/') + break; + + result->contents ++; + result->length --; + retval = TRUE; + } + + if(result->length <= 0) + result->contents = NULL; + + return retval; +} + + +sos_bool_t +sos_fs_pathname_split_path(const struct sos_fs_pathname * path, + struct sos_fs_pathname * result_first_component, + struct sos_fs_pathname * result_remaining_path) +{ + result_first_component->contents = path->contents; + result_first_component->length = path->length; + + /* Skip any leading slash */ + sos_fs_pathname_eat_slashes(result_first_component, + result_first_component); + + /* Extract the first component */ + sos_fs_pathname_eat_non_slashes(result_first_component, + result_remaining_path); + SOS_ASSERT_FATAL(result_remaining_path->length >= 0); + result_first_component->length -= result_remaining_path->length; + + /* Return true if there is something left (at least one slash) */ + return (result_remaining_path->length > 0); +} + + +sos_bool_t fs_pathname_iseq(const struct sos_fs_pathname * p1, + const struct sos_fs_pathname * p2) +{ + if (!p1->contents) + SOS_ASSERT_FATAL(p1->length == 0); + if (!p2->contents) + SOS_ASSERT_FATAL(p2->length == 0); + + if (p1->length != p2->length) + return FALSE; + + if (p1->length == 0) + return TRUE; + + return (0 == memcmp(p1->contents, p2->contents, p1->length)); +} + + +#define fs_pathname_isstr(str,path) \ + ({ struct sos_fs_pathname _s; _s.contents = str; _s.length = sizeof(str)-1; \ + fs_pathname_iseq(&_s, (path)); }) + + +struct sos_fs_node * +sos_fs_nscache_get_fs_node(const struct sos_fs_nscache_node * nsnode) +{ + return nsnode->fs_node; +} + + +sos_ret_t +sos_fs_nscache_get_parent(const struct sos_fs_nscache_node * nsnode, + struct sos_fs_nscache_node ** result_parent) +{ + *result_parent = nsnode->parent; + if (*result_parent) + return SOS_OK; + return -SOS_ENOENT; +} + + +sos_ret_t +sos_fs_nscache_get_name(const struct sos_fs_nscache_node * nsnode, + struct sos_fs_pathname * result_pathname) +{ + result_pathname->contents = nsnode->name.contents; + result_pathname->length = nsnode->name.length; + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_get_ref_cnt(const struct sos_fs_nscache_node * nsnode) +{ + return nsnode->ref_cnt; +} + + +sos_ret_t +sos_fs_nscache_lookup(struct sos_fs_nscache_node * cur_nsnode, + const struct sos_fs_pathname * node_name, + const struct sos_fs_nscache_node * root_nsnode, + struct sos_fs_nscache_node ** result_nsnode) +{ + if (fs_pathname_isstr(".", node_name)) + { + *result_nsnode = cur_nsnode; + } + else if (fs_pathname_isstr("..", node_name)) + { + /* Effectively go up only if we did not reach a root node */ + if (cur_nsnode == root_nsnode) /* did reach chroot */ + { + /* Simply stay here */ + *result_nsnode = cur_nsnode; + } + else + { + /* If current node is a mounted FS, rewind the mountpoint + chain */ + for ( ; cur_nsnode->mountpoint ; cur_nsnode = cur_nsnode->mountpoint) + /* nop */ ; + + /* Now go up to parent */ + SOS_ASSERT_FATAL(NULL != cur_nsnode->parent); + *result_nsnode = cur_nsnode->parent; + } + + /* Update the nscache_node result */ + sos_fs_nscache_ref_node(*result_nsnode); + return SOS_OK; + } + else + { + /* Normal lookup: we iterate over the list of children nscache + nodes */ + int nb_children; + struct sos_fs_nscache_node * child; + + /* Lookup the child node with the correct name, if any */ + list_foreach_named(cur_nsnode->children, + child, nb_children, + siblings_prev, siblings_next) + { + struct sos_fs_node * fs_node = cur_nsnode->fs_node; + + if (fs_node->fs->nsnode_same_name) + { + if (fs_node->fs-> + nsnode_same_name(child->name.contents, + child->name.length, + node_name->contents, + node_name->length)) + break; + } + else + if (fs_pathname_iseq(& child->name, + node_name)) + break; + } + + /* Did not find it ! */ + if (! list_foreach_early_break(cur_nsnode->children, + child, nb_children)) + return -SOS_ENOENT; + + /* Yes, found it ! */ + *result_nsnode = child; + } + + /* Found it. Now, Follow the mountpoint chain, if any */ + for ( ; (*result_nsnode)->mounted_root ; + *result_nsnode = (*result_nsnode)->mounted_root) + /* nop */ ; + + /* Update the nscache_node result */ + sos_fs_nscache_ref_node(*result_nsnode); + + return SOS_OK; +} + + +sos_ret_t sos_fs_nscache_ref_node(struct sos_fs_nscache_node * nsnode) +{ + SOS_ASSERT_FATAL(nsnode->ref_cnt > 0); + nsnode->ref_cnt ++; + return SOS_OK; +} + + +/* Eventually collapses a whole list of nsnodes (non recursive) */ +sos_ret_t _sos_fs_nscache_unref_node(struct sos_fs_nscache_node ** nsnode) +{ + struct sos_fs_nscache_node * to_delete = NULL, *node; + + node = *nsnode; + *nsnode = NULL; + + while (node) + { + /* Unreference this node */ + SOS_ASSERT_FATAL(node->ref_cnt > 0); + node->ref_cnt --; + + /* Is it a good candidate for deletion ? */ + if (node->ref_cnt > 0) + break; /* No */ + + if (node->parent) + { + struct sos_fs_nscache_node * parent = node->parent; + + SOS_ASSERT_FATAL(node->parent->ref_cnt >= 1); + + list_delete_named(parent->children, node, + siblings_prev, siblings_next); + /* The parent lost one child: next iteration will decrement + ths parent's ref cnt */ + } + + /* Add to the list of elements to suppress */ + list_add_tail_named(to_delete, node, siblings_prev, siblings_next); + + /* Now look if parent (if any) can be destroyed */ + node = node->parent; + } + + /* Now destroy all the elements gathered */ + while (! list_is_empty_named(to_delete, siblings_prev, siblings_next)) + { + node = list_pop_head_named(to_delete, siblings_prev, siblings_next); + sos_fs_unref_fsnode(node->fs_node); + sos_kfree((sos_vaddr_t)node); + } + + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_add_new_child_node(struct sos_fs_nscache_node * parent, + const struct sos_fs_pathname * node_name, + struct sos_fs_node * fsnode, + struct sos_fs_nscache_node ** result_nsnode) +{ + struct sos_fs_nscache_node * nsnode; + + /* Allocate a new nscache node from slab */ + nsnode = (struct sos_fs_nscache_node*) + sos_kmem_cache_alloc(cache_of_nscache_nodes, + SOS_KSLAB_ALLOC_ATOMIC); + if (! nsnode) + return -SOS_ENOMEM; + + /* Allocate a new memory chunk to hold the node's name */ + if (node_name && (node_name->length > 0)) + { + char * contents = (char*) sos_kmalloc(node_name->length, + SOS_KMALLOC_ATOMIC); + if (! contents) + { + sos_kfree((sos_vaddr_t)nsnode); + return -SOS_ENOMEM; + } + + memcpy(contents, node_name->contents, node_name->length); + nsnode->name.contents = contents; + nsnode->name.length = node_name->length; + } + + /* Now initialize the new node's fields */ + nsnode->ref_cnt = 1; + sos_fs_ref_fsnode(fsnode); + nsnode->fs_node = fsnode; + + /* Register this node as a child of its parent, if any */ + nsnode->parent = parent; + if (parent) + { + sos_fs_nscache_ref_node(parent); + list_add_head_named(parent->children, nsnode, + siblings_prev, siblings_next); + } + + *result_nsnode = nsnode; + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_add_existing_child_node(struct sos_fs_nscache_node * parent, + const struct sos_fs_pathname * node_name, + struct sos_fs_nscache_node * nsnode) +{ + SOS_ASSERT_FATAL(nsnode->parent == NULL); + + /* If the node already had a name, suppress it */ + if (NULL != nsnode->name.contents) + { + sos_kfree((sos_vaddr_t)nsnode->name.contents); + } + memset(& nsnode->name, 0x0, sizeof(struct sos_fs_pathname)); + + /* Allocate a new memory chunk to hold the node's name */ + if (node_name && (node_name->length > 0)) + { + char * contents = (char*) sos_kmalloc(node_name->length, + SOS_KMALLOC_ATOMIC); + if (! contents) + { + sos_kfree((sos_vaddr_t)nsnode); + return -SOS_ENOMEM; + } + + memcpy(contents, node_name->contents, node_name->length); + nsnode->name.contents = contents; + nsnode->name.length = node_name->length; + } + + + /* Register this node as a child of its parent, if any */ + nsnode->parent = parent; + if (parent) + { + sos_fs_nscache_ref_node(parent); + list_add_head_named(parent->children, nsnode, + siblings_prev, siblings_next); + } + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_disconnect_node(struct sos_fs_nscache_node * nsnode) +{ + if (! nsnode->parent) + return SOS_OK; + + list_delete_named(nsnode->parent->children, nsnode, + siblings_prev, siblings_next); + sos_fs_nscache_unref_node(nsnode->parent); + nsnode->parent = NULL; + + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_register_opened_file(struct sos_fs_nscache_node * nsnode, + struct sos_fs_opened_file * of) +{ + of->direntry = nsnode; + sos_fs_nscache_ref_node(nsnode); + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_mount(struct sos_fs_nscache_node * mountpoint, + struct sos_fs_nscache_node * mounted_root) +{ + SOS_ASSERT_FATAL(NULL == mountpoint->mounted_root); + SOS_ASSERT_FATAL(NULL == mounted_root->mountpoint); + mountpoint->mounted_root = mounted_root; + mounted_root->mountpoint = mountpoint; + sos_fs_nscache_ref_node(mountpoint); + sos_fs_nscache_ref_node(mounted_root); + + return SOS_OK; +} + + +sos_ret_t +sos_fs_nscache_umount(struct sos_fs_nscache_node * mounted_root) +{ + struct sos_fs_manager_instance *fs; + + SOS_ASSERT_FATAL(NULL != mounted_root->mountpoint); + SOS_ASSERT_FATAL(mounted_root->mountpoint->mounted_root == mounted_root); + + /* No FS should be mounted on the mounted root to umount */ + SOS_ASSERT_FATAL(NULL == mounted_root->mounted_root); + + /* The mounted root should have its own reference, plus a reference + from the mountpoint and from the fs instance */ + SOS_ASSERT_FATAL(mounted_root->ref_cnt >= 3); + if (mounted_root->ref_cnt != 3) + return -SOS_EBUSY; + + fs = mounted_root->fs_node->fs; + SOS_ASSERT_FATAL(NULL != fs); + SOS_ASSERT_FATAL(fs->root == mounted_root); + + /* Undo the mountpoint <-> mounted_root mutual reference */ + mounted_root->mountpoint->mounted_root = NULL; + sos_fs_nscache_unref_node(mounted_root->mountpoint); + mounted_root->mountpoint = NULL; + sos_fs_nscache_unref_node(mounted_root); + + /* Undo the fs->root -> mounted_root reference */ + sos_fs_nscache_unref_node(fs->root); + + return SOS_OK; +} + +sos_bool_t +sos_fs_nscache_is_mountnode(const struct sos_fs_nscache_node * nsnode) +{ + return ( (NULL != nsnode->mounted_root) || (NULL != nsnode->mountpoint) ); +} diff --git a/sos/fs_nscache.h b/sos/fs_nscache.h new file mode 100644 index 0000000..9431c72 --- /dev/null +++ b/sos/fs_nscache.h @@ -0,0 +1,284 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2000-2005 The KOS Team (Thomas Petazzoni, David + Decotigny, Julien Munier) + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FS_NSCACHE_H_ +#define _SOS_FS_NSCACHE_H_ + + +/** + * @file fs_nscache.h + * + * FS Namespace cache (aka file hierarchy) management. Internal API + * reserved to fs.c and to the FS managers ! See fs.c for details and + * role of this subsystem in the whole VFS. + * + * We keep the usual filesystem semantics of a "file hierarchy": + * + * parent0 + * / \ + * child1 child2 + * / \ \ + * child1a child1b child2a + * + * The system allows that different children actually reference the + * same "on-disk" node (sos_fs_node). For example: child1a and child2a + * might reference the same sos_fs_node: this represents a so-called + * "hard link". + * + * The functions of this module are in charge of updating the nscache + * nodes and their reference count. They don't influence the other + * subsystems (apart from the sos_fs_nscache_unref_node() function + * which can unreference the underlying sos_fs_node). + * + * Note: only the nscache nodes that are actually used or those that + * are their parents (ie in the path from these nodes to the global + * root) will remain in memory. The others will be destroyed as soon + * as they are not needed anymore. For example, il I do a + * stat("/mnt/toto/titi.txt", & st), all the nscache nodes from "/" to + * "titi.txt" will be allocated, the stat performed, and all of them + * will be destroyed. We could imagine a real "cache" here to avoid + * these bursts of allocations/deallocations, by keeping the last + * accessed nodes aside when they are not referenced anymore (in a + * hash table for example, the key being {parent nscache node address, + * child name}). + * + * Note about mountpoints: When a FS is mounted in, say "/mnt", the + * nscache node of the new FS is registered neither as its child nor + * as its parent, but as a kind of "brother" of /mnt. As seen from the + * global root ("/"), "mnt" in a direct child and the mounted root is + * its brother. But, once mounted, as seen from a child node + * "/mnt/toto", the mounted root is seen as the direct parent of + * /mnt/toto and "mnt" is seen as its brother. That is, each time we + * try to resolve (lookup) the children on a mountpoint, we must + * "follow" the mountchain. In the previous example, multiple + * successive FS could be mounted on the same "/mnt". + */ + +#include +#include + +/** + * Opaque structure defined in fs_nscache.c + * + * Essentially contains: + * - a name (allocated in-place) + * - a reference to the associated FS node (struct sos_fs_node) + * - a reference to the parent nscache node (if any) + * - a list of pointers to the children nscache nodes (for directories) + */ +struct sos_fs_nscache_node; + +#include "fs.h" + + +/** + * Support for non-0 terminated strings (Pascal-style). Useful to + * prevent from altering the contents of the string in order to split + * pathnames into components (@see sos_fs_pathname_split_path) + */ +struct sos_fs_pathname +{ + const char * contents; + sos_size_t length; +}; + + +sos_ret_t sos_fs_nscache_subsystem_setup(void); + + +/** + * Lookup the given entry in the given nsnode. The lookup is limited + * to the children entries that are already in memory. When this + * lookup fails, this simply means that the entry is not already in + * memory, and has to be resolved using disk accesses (@see + * fs_lookup_node in fs.c) + * + * @param cur_nsnode The node in which we are looking for the entry + * @param root_node The base node beyond which lookup must not go (to + * support chroot): a kind of "barrier" + * + * @param result_nsnode The nsnode for the given entry (set only when + * the return value is SOS_OK) + * + * @return error if the entry could not be found in the nsnode + * directory. OK otherwise, and *result_nsnode is set. + * + * @note The symlinks are NOT expanded. The mountpoints ARE followed. + * @note result_nsnode is a NEW reference to the node. It should be + * unreferenced when unused + */ +sos_ret_t +sos_fs_nscache_lookup(struct sos_fs_nscache_node * cur_nsnode, + const struct sos_fs_pathname * node_name, + const struct sos_fs_nscache_node * root_nsnode, + struct sos_fs_nscache_node ** result_nsnode); + + +/** + * Add a new child node for the given parent, for the given fs_node + * + * @param parent might be NULL, meaning that the node is the root of a + * mounted filesystem + * + * @note The new node has the value 0 for the opened_file and + * mount_chain counters + * @note result_nsnode is a NEW reference to the node. It should be + * unreferenced when unused + */ +sos_ret_t +sos_fs_nscache_add_new_child_node(struct sos_fs_nscache_node * parent, + const struct sos_fs_pathname * node_name, + struct sos_fs_node * fsnode, + struct sos_fs_nscache_node ** result_nsnode); + + +/** + * Add a new child node for the given parent, for the given already + * existing nsnode (with no parent !) + * + * @param parent can not be NULL + * + * @note nsnode should NOT have any parent + */ +sos_ret_t +sos_fs_nscache_add_existing_child_node(struct sos_fs_nscache_node * parent, + const struct sos_fs_pathname * node_name, + struct sos_fs_nscache_node * nsnode); + + +/** + * Disconnect the given node from its parent, if any + * @note reference count of nsnode is NOT modified + */ +sos_ret_t +sos_fs_nscache_disconnect_node(struct sos_fs_nscache_node * nsnode); + + +/** + * Register the given root of a new file system (mounted_root) in the + * mountpoint chain located at mountpoint, ie build the mountchain. + */ +sos_ret_t +sos_fs_nscache_mount(struct sos_fs_nscache_node * mountpoint, + struct sos_fs_nscache_node * mounted_root); + + +/** + * Break the mountchain at the given mounted root, making sure that + * this nscache node is not reference by any opened file or child node + * anymore. + */ +sos_ret_t +sos_fs_nscache_umount(struct sos_fs_nscache_node * mounted_root); + + +/** Return true if the node is involved in any mountchain */ +sos_bool_t +sos_fs_nscache_is_mountnode(const struct sos_fs_nscache_node * nsnode); + + +/* + * Accessor functions + */ + + +/** + * Return the FS node of the given nscache node. + * + * @note The FS node returned is NOT newly referenced + */ +struct sos_fs_node * +sos_fs_nscache_get_fs_node(const struct sos_fs_nscache_node * nsnode); + + +/** + * Return the parent nscache node of the given nscache node. + * + * @note The nscache node returned is NOT newly referenced + */ +sos_ret_t +sos_fs_nscache_get_parent(const struct sos_fs_nscache_node * nsnode, + struct sos_fs_nscache_node ** result_parent); + + +sos_ret_t +sos_fs_nscache_get_name(const struct sos_fs_nscache_node * nsnode, + struct sos_fs_pathname * result_pathname); + + +/** + * Return the value of the reference count for the given nscache node + */ +sos_ret_t +sos_fs_nscache_get_ref_cnt(const struct sos_fs_nscache_node * nsnode); + + +sos_ret_t +sos_fs_nscache_register_opened_file(struct sos_fs_nscache_node * nsnode, + struct sos_fs_opened_file * of); + + +sos_ret_t sos_fs_nscache_ref_node(struct sos_fs_nscache_node * nsnode); + + +sos_ret_t _sos_fs_nscache_unref_node(struct sos_fs_nscache_node ** nsnode); +#define sos_fs_nscache_unref_node(n) _sos_fs_nscache_unref_node(& (n)) + + +/* + * Functions reserved to sos_fs_manager_type::mount() and + * sos_fs_manager_type::umount() + */ +#define sos_fs_nscache_create_mounted_root(fsnode,result_nsnode) \ + sos_fs_nscache_add_new_child_node(NULL, NULL, (fsnode), (result_nsnode)) + + +/* + * Pathname manipulation functions + */ + +sos_bool_t fs_pathname_iseq(const struct sos_fs_pathname * p1, + const struct sos_fs_pathname * p2); + +/** + * Remove any leading slash from the path + * + * @Return TRUE when slashes were found at the begining + */ +sos_bool_t sos_fs_pathname_eat_slashes(const struct sos_fs_pathname * path, + struct sos_fs_pathname * result); + +/** + * Transform "a/b/c" into { first_component="a" remaining_path="/b/c" } + * Transform "/a/b/c" into { first_component="a" remaining_path="/b/c" } + * Transform "////a////b/c" into { first_component="a" remaining_path="////b/c" } + * Transform "a" into { first_component="a" remaining_path="" } + * Transform "/a" into { first_component="a" remaining_path="" } + * Transform "a/" into { first_component="a" remaining_path="" } + * Transform "/a/" into { first_component="a" remaining_path="" } + * + * @Return TRUE when slashes after first component were found. In the + * previous example: true everywhere except for the path "a" and "/a" + */ +sos_bool_t +sos_fs_pathname_split_path(const struct sos_fs_pathname * path, + struct sos_fs_pathname * result_first_component, + struct sos_fs_pathname * result_remaining_path); + +#endif /* _SOS_FS_NSCACHE_H_ */ diff --git a/sos/fs_pagecache.c b/sos/fs_pagecache.c new file mode 100644 index 0000000..2a02b7b --- /dev/null +++ b/sos/fs_pagecache.c @@ -0,0 +1,642 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include +#include +#include /* For SOS_PAGE_MASK */ +#include +#include +#include +#include + +#include "fs_pagecache.h" + + +#define SOS_OFFSET64_PAGE_ALIGN_INF(offs64) \ + ( ((sos_luoffset_t)(offs64)) & (~ ((sos_luoffset_t)(SOS_PAGE_MASK))) ) + + +#define SOS_OFFSET64_IS_PAGE_ALIGNED(offs64) \ + ( ( ((sos_luoffset_t)(offs64)) & (((sos_luoffset_t)(SOS_PAGE_MASK))) ) == 0 ) + + +/** + * Definition of an object holding a reference to a shared mapping of + * a file/device-mapped cache page. + * + * @note This structure is huge. We can shrink it largely by removing the + * "name" field from the lock structure (32 bytes). + */ +struct sos_fs_pagecache_entry +{ + /** offset of the cached page in the file or device */ + sos_luoffset_t file_offset; + + /** Address of the cached page for this offset */ + sos_vaddr_t kernel_vaddr; + + struct sos_kmutex lock; + sos_count_t ref_cnt; + + sos_bool_t initial_fill_aborted; /**< True when the page could not + be correctly filled */ + + /** + * When 0: the page is clean wrt to read/write syscalls, ie the disk + * contents reflect the contents of the page since the last + * read/write operation. However, the disk may NOT be in sync wrt to + * mmap() operations: if mmap() operations occured in the meantime, + * the disk may NOT be up to date, and the pagecache entry may even + * NOT be considered dirty. This is because we do not trace each of + * the read/write MMU operations from every processes (this would + * need to catch all writes even on read/write mapped pages) and we + * don't have a reverse mapping available to set the page read-only + * in every mappings once it has been synched to disk (to + * effectively trace the dirty state relative to mmap operations). + * + * When ">0": at least one process changed the contents of the page + * through read/write syscalls since last sync operation. + * + * @note A boolean is enough for 99% of the code. But we need a real + * counter for the sos_fs_pagecache_sync operation to make sure we + * don't iterate 2 times over the same page. + */ + sos_lcount_t rw_dirty_order; +#define ENTRY_IS_RW_DIRTY(e) ((e)->rw_dirty_order > 0) + + /** Linkage structure to keep the cache entry in the hash map */ + struct sos_hash_linkage hlink; + + /** Links to insert the entry into the rw_sync/rw_dirty lists */ + struct sos_fs_pagecache_entry *prev, *next; +}; + + +struct sos_fs_pagecache +{ + /** The operation used to synchronize the mapped pages with the + backing store */ + sos_fs_pagecache_sync_function_t sync_fct; + void * sync_fct_custom_data; + + /** The dictionary offset -> pagecache_entry */ + struct sos_hash_table * lookup_table; + + /* Lists to look into in order to free a node */ + struct sos_fs_pagecache_entry * rw_sync_list; /**< Pages in sync + with disk wrt + read/write API + (LRU at end) */ + struct sos_fs_pagecache_entry * rw_dirty_list; /**< Dirty pages wrt + read/write API + (LRU last) */ + + /** The "timestamp" high watermark used to iterate over the dirty + pages in the sync function */ + sos_lcount_t top_rw_dirty_order; +}; + + +/** The slab cache for pagecache */ +static struct sos_kslab_cache * cache_of_pagecache; + + +/** The slab cache for pagecache entries */ +static struct sos_kslab_cache * cache_of_pagecache_entry; + + +sos_ret_t sos_fs_pagecache_subsystem_setup() +{ + /* Allocate the slab caches for the page caches and page cache + entries */ + + cache_of_pagecache = + sos_kmem_cache_create("pagecache", sizeof(struct sos_fs_pagecache), + 2, 0, SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); + if (NULL == cache_of_pagecache) + return -SOS_ENOMEM; + + cache_of_pagecache_entry = + sos_kmem_cache_create("pagecache_entry", + sizeof(struct sos_fs_pagecache_entry), + 2, 0, SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); + if (NULL == cache_of_pagecache_entry) + { + sos_kmem_cache_destroy(cache_of_pagecache); + return -SOS_ENOMEM; + } + + return SOS_OK; +} + + +struct sos_fs_pagecache * +sos_fs_pagecache_new_cache(sos_fs_pagecache_sync_function_t sync_fct, + void * sync_fct_custom_data) +{ + struct sos_fs_pagecache * pagecache + = (struct sos_fs_pagecache*) sos_kmem_cache_alloc(cache_of_pagecache, + 0); + if (NULL == pagecache) + return NULL; + + pagecache->lookup_table = sos_hash_create("pagecache", + struct sos_fs_pagecache_entry, + sos_hash_ui64, + sos_hash_key_eq_ui64, + 127, file_offset, hlink); + if (NULL == pagecache->lookup_table) + { + sos_kmem_cache_free((sos_vaddr_t) pagecache); + return NULL; + } + + pagecache->sync_fct = sync_fct; + pagecache->sync_fct_custom_data = sync_fct_custom_data; + pagecache->top_rw_dirty_order = 0x24; + + return pagecache; +} + + +sos_ret_t +sos_fs_pagecache_delete_cache(struct sos_fs_pagecache * pc) +{ + /* The cache is EXPECTED to be empty ! */ + + if (!list_is_empty(pc->rw_dirty_list)) + SOS_FATAL_ERROR("Non empty dirty list"); + if (!list_is_empty(pc->rw_sync_list)) + SOS_FATAL_ERROR("Non empty sync list"); + + sos_hash_dispose(pc->lookup_table); + return sos_kmem_cache_free((sos_vaddr_t)pc); +} + + +/** Helper function to flush a page to disk. Expects the entry to be + locked */ +static sos_ret_t pagecache_sync_page(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry) +{ + sos_ret_t retval; + + if (! ENTRY_IS_RW_DIRTY(entry)) + return SOS_OK; + + /* Now do the real transfer to backing store */ + retval = pc->sync_fct(entry->file_offset, entry->kernel_vaddr, + pc->sync_fct_custom_data); + if (SOS_OK != retval) + return retval; + + /* Transfer page to the sync list */ + list_delete(pc->rw_dirty_list, entry); + entry->rw_dirty_order = 0; + list_add_head(pc->rw_sync_list, entry); + + return SOS_OK; +} + + +/** Helper function to correctly lock an entry */ +static sos_ret_t pagecache_use(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry) +{ + entry->ref_cnt ++; + return sos_kmutex_lock(& entry->lock, NULL); +} + + +/** + * Helper function to transfer a page to the dirty r/w list + */ +static sos_ret_t pagecache_set_rw_dirty(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry) +{ + if (ENTRY_IS_RW_DIRTY(entry)) + return SOS_OK; /* Nothing to do */ + + list_delete(pc->rw_sync_list, entry); + entry->rw_dirty_order = ++ pc->top_rw_dirty_order; + list_add_head(pc->rw_dirty_list, entry); + + return SOS_OK; +} + + +/** Helper function to correctly unlock an entry, flushing it to disk + if needed */ +static sos_ret_t pagecache_release(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry) +{ + if (entry->ref_cnt > 1) + { + entry->ref_cnt --; + sos_kmutex_unlock(& entry->lock); + return SOS_OK; + } + + /* + * The cached page is now referenced ONLY by US, we can try to + * remove it from the cache + */ + + /* Flush any change to disk, at least if we are sure that its + content is legal, ie that the page_in callback did success in + filling it */ + if (! entry->initial_fill_aborted) + pagecache_sync_page(pc, entry); + + /* Ok, now WE are not interested by this entry anymore */ + entry->ref_cnt --; + + /* During blocking time, another thread could have asked for the + entry. In this case, stop here */ + if (entry->ref_cnt > 0) + { + sos_kmutex_unlock(& entry->lock); + return SOS_OK; + } + + /* Remove it from the lists */ + sos_hash_remove(pc->lookup_table, entry); + if (ENTRY_IS_RW_DIRTY(entry)) + list_delete(pc->rw_dirty_list, entry); + else + list_delete(pc->rw_sync_list, entry); + + /* We can safely erase it now ! */ + sos_kmutex_unlock(& entry->lock); + SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_dispose(& entry->lock)); /* No thread are waiting */ + sos_kfree(entry->kernel_vaddr); + sos_kmem_cache_free((sos_vaddr_t)entry); + + return SOS_OK; +} + + +/** + * Helper function to look up an entry from the cache and lock it. If + * the entry does not exist (yet), return NULL. + */ +static struct sos_fs_pagecache_entry * +pagecache_lookup_and_lock(struct sos_fs_pagecache * pc, + sos_luoffset_t offset) +{ + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + struct sos_fs_pagecache_entry * entry = NULL; + + while (TRUE) + { + entry + = (struct sos_fs_pagecache_entry*) sos_hash_lookup(pc->lookup_table, + & pgoffs); + if (! entry) + break; + + /* Lock it now */ + SOS_ASSERT_FATAL(SOS_OK == pagecache_use(pc, entry)); + + /* + * Entry is now locked + */ + + /* Make sure it contains legal contents: if we were blocked + because of the page_in operations reading it from disk, an + error could have been occured. In this case, we must consider + that this entry is not yet inserted in the cache */ + if (entry->initial_fill_aborted) + { + pagecache_release(pc, entry); + continue; + } + + /* Ok, we have the entry and it is correctly initialized ! */ + break; + } + + return entry; +} + + +sos_ret_t +sos_fs_pagecache_read(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_genaddr_t dest_buf, + sos_size_t * /* in/out */len) +{ + sos_ret_t retval; + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + sos_luoffset_t endpos = offset + *len; + struct sos_fs_pagecache_entry * entry; + + entry = pagecache_lookup_and_lock(pc, pgoffs); + if (NULL == entry) + return -SOS_ENOENT; + + /* Great ! Found the entry in the cache ! */ + + /* Read only up to the end of the page */ + if (endpos - pgoffs > SOS_PAGE_SIZE) + endpos = pgoffs + SOS_PAGE_SIZE; + + /* Copy page contents to destination buffer */ + retval = sos_memcpy_generic_to(dest_buf, + entry->kernel_vaddr + (offset - pgoffs), + endpos - offset); + pagecache_release(pc, entry); + + if (retval < 0) + { + *len = 0; + return retval; + } + + *len = retval; + if ((sos_luoffset_t)retval != endpos - offset) + return -SOS_EFAULT; + + return SOS_OK; +} + + +sos_ret_t +sos_fs_pagecache_write(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_genaddr_t src_buf, + sos_size_t * /* in/out */len, + sos_bool_t synchronous_write) +{ + sos_ret_t retval; + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + sos_luoffset_t endpos = offset + *len; + struct sos_fs_pagecache_entry * entry; + + entry = pagecache_lookup_and_lock(pc, pgoffs); + if (NULL == entry) + return -SOS_ENOENT; + + /* Great ! Found the entry in the cache ! */ + + /* Read only up to the end of the page */ + if (endpos - pgoffs > SOS_PAGE_SIZE) + endpos = pgoffs + SOS_PAGE_SIZE; + + /* Copy page contents to destination buffer */ + retval = sos_memcpy_generic_from(entry->kernel_vaddr + (offset - pgoffs), + src_buf, + endpos - offset); + /* Transfer the entry in the dirty list if needed */ + if (retval >= 0) + pagecache_set_rw_dirty(pc, entry); + + if (retval < 0) + { + *len = 0; + pagecache_release(pc, entry); + return retval; + } + + *len = retval; + if ((sos_luoffset_t)retval != endpos - offset) + retval = -SOS_EFAULT; + else + retval = SOS_OK; + + /* Flush to disk if needed */ + if (synchronous_write) + { + sos_ret_t ret = pagecache_sync_page(pc, entry); + if (SOS_OK == retval) + retval = ret; + } + + pagecache_release(pc, entry); + return retval; +} + + +sos_ret_t sos_fs_pagecache_set_dirty(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_bool_t sync_backing_store) +{ + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + struct sos_fs_pagecache_entry * entry; + + entry = pagecache_lookup_and_lock(pc, pgoffs); + if (NULL == entry) + return -SOS_ENOENT; + + /* Great ! Found the entry in the cache ! */ + pagecache_set_rw_dirty(pc, entry); + + /* Synchronize to backing store if needed */ + if (sync_backing_store) + pagecache_sync_page(pc, entry); + + pagecache_release(pc, entry); + return SOS_OK; +} + + +struct sos_fs_pagecache_entry * +sos_fs_pagecache_ref_page(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_vaddr_t * /* out */ kernel_vaddr, + sos_bool_t * /* out */ newly_allocated) +{ + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + struct sos_fs_pagecache_entry * entry; + + /* The offset is expected to be page-aligned */ + if (pgoffs != offset) + return NULL; + + entry = pagecache_lookup_and_lock(pc, pgoffs); + if (NULL != entry) + { + /* Found it ! No need to go further */ + *newly_allocated = FALSE; + *kernel_vaddr = entry->kernel_vaddr; + return entry; + } + + + /* + * Need to allocate a new kernel page + */ + + entry = (struct sos_fs_pagecache_entry*) + sos_kmem_cache_alloc(cache_of_pagecache_entry, 0); + if (NULL == entry) + return (sos_vaddr_t)NULL; + + if (SOS_OK != sos_kmutex_init(& entry->lock, "pagecache_entry", + SOS_KWQ_ORDER_FIFO)) + { + sos_kmem_cache_free((sos_vaddr_t)entry); + return NULL; + } + + /* Initial state of the page correspond to an erroneous + initialization */ + entry->file_offset = pgoffs; + entry->initial_fill_aborted = TRUE; + entry->ref_cnt = 1; + + /* Allocate the page */ + entry->kernel_vaddr = sos_kmalloc(SOS_PAGE_SIZE, 0); + if (((sos_vaddr_t)NULL) == entry->kernel_vaddr) + { + sos_kmutex_dispose(& entry->lock); + sos_kmem_cache_free((sos_vaddr_t)entry); + return NULL; + } + + /* Own the mutex */ + SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_lock(& entry->lock, NULL)); + + /* Try to insert it into the hash table. Might fail if the page was + already inserted, which could be possible because the allocation + routines might have blocked */ + if (SOS_OK != sos_hash_insert(pc->lookup_table, entry)) + { + /* entry was inserted during allocations, undo the new entry */ + sos_kmutex_unlock(& entry->lock); + sos_kmutex_dispose(& entry->lock); + sos_kfree(entry->kernel_vaddr); + sos_kmem_cache_free((sos_vaddr_t)entry); + + /* Get the real entry */ + entry = pagecache_lookup_and_lock(pc, offset); + SOS_ASSERT_FATAL(NULL != entry); + *kernel_vaddr = entry->kernel_vaddr; + *newly_allocated = FALSE; + return entry; + } + + /* Now register the entry in the sync list */ + entry->rw_dirty_order = 0; + list_add_head(pc->rw_sync_list, entry); + + *newly_allocated = TRUE; + *kernel_vaddr = entry->kernel_vaddr; + return entry; +} + + +sos_ret_t +sos_fs_pagecache_unlock_page(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry, + sos_bool_t initial_fill_aborted) +{ + + entry->initial_fill_aborted = initial_fill_aborted; + + if (initial_fill_aborted) + return pagecache_release(pc, entry); + + return sos_kmutex_unlock(& entry->lock); +} + + +sos_ret_t +sos_fs_pagecache_unref_page(struct sos_fs_pagecache * pc, + sos_luoffset_t offset) +{ + sos_luoffset_t pgoffs = SOS_OFFSET64_PAGE_ALIGN_INF(offset); + struct sos_fs_pagecache_entry * entry; + + /* The offset is expected to be page-aligned */ + if (pgoffs != offset) + return -SOS_EINVAL; + + entry + = (struct sos_fs_pagecache_entry*) sos_hash_lookup(pc->lookup_table, + & pgoffs); + SOS_ASSERT_FATAL(NULL != entry); + SOS_ASSERT_FATAL(SOS_OK == sos_kmutex_lock(& entry->lock, NULL)); + return pagecache_release(pc, entry); +} + + +sos_ret_t +sos_fs_pagecache_sync(struct sos_fs_pagecache * pc) +{ + sos_ret_t retval = SOS_OK; + int dummy = 0; + sos_lcount_t rw_dirty_order = 0; + + /** High watermark telling "you won't take the pages added + afterwards into account" */ + sos_lcount_t top_rw_dirty_order = pc->top_rw_dirty_order; + + if (list_is_empty(pc->rw_dirty_list)) + return SOS_OK; + + /* This scan will be exhaustive and resilient to addition/removal of + devices as long as new devices are added with list_add_tail + (because the scan is "forward", ie in order head -> tail) */ + while (TRUE) + { + struct sos_fs_pagecache_entry * entry = NULL; + int ndirty; + + /* As long as we don't block, we can safely access the + prev/next fields of the page descriptor */ + list_foreach_backward(pc->rw_dirty_list, entry, ndirty) + { + sos_ret_t ret = SOS_OK; + struct sos_fs_pagecache_entry * prev_entry = NULL; + + /* Reached the initial high watermark ? Don't take the + additional pages into account */ + if (entry->rw_dirty_order > top_rw_dirty_order) + break; + + if (entry->rw_dirty_order <= rw_dirty_order) + continue; + + rw_dirty_order = entry->rw_dirty_order; + prev_entry = entry->prev; + + SOS_ASSERT_FATAL(SOS_OK == pagecache_use(pc, entry)); + if (! entry->initial_fill_aborted) + ret = pagecache_sync_page(pc, entry); + if (SOS_OK != ret) + retval = ret; + pagecache_release(pc, entry); + + /* We must NOT continue the loops because the prev/next page + cache entry might have been removed or added (sync pages, + by definition) ! */ + if (prev_entry != entry->prev) + goto lookup_next_ent; + } + + /* Reached the end of the list */ + break; + + lookup_next_ent: + /* Loop over */ + dummy ++; + } + + return retval; +} diff --git a/sos/fs_pagecache.h b/sos/fs_pagecache.h new file mode 100644 index 0000000..9413b33 --- /dev/null +++ b/sos/fs_pagecache.h @@ -0,0 +1,214 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FSPAGECACHE_H_ +#define _SOS_FSPAGECACHE_H_ + + +/** + * @file fs_pagecache.h + * + * Simple page cache interface. Used to automate the synchronization + * between the rad/write operations and mmap. A "FS page cache" is + * simply a set of pages mapping a file in memory. A file may not be + * entirely mapped into memory: its pages are mapped only if any user + * thread invoked an mmap and page-faulted inside the mapped region to + * map these pages into memory. Contrary to some other caches in SOS + * (eg the block cache), this one is not limited in size. As many + * pages as needed will be allocated for it, as permitted by the + * available RAM. With a pageout mechanism, this cache will be + * shrinked when needed: some of its pages will be transferred back to + * disk and unmapped. + * + * A page cache is used both to cache memory mapped files of an FS, + * and memory mapped block devices. Hence: + * - there is one SINGLE page cache for each block device (proper to + * each disk, to each partition) + * - there is one SINGLE page cache for each file of a file system + * + * For block devices, the page cache automatically synchronizes the + * pages with the block cache as long as these mapped pages are + * accessed through the read/write API. However, NO automatic + * and accurate synchronization between the in-memory modified + * pages (accessed through the MMU) and the block cache is provided + * because we have no way to collect the accurate list of pages + * modified through MMU write accesses (this would require either to + * catch all the MMU write operations [too inefficient !], or to have + * a reverse-mapping system in order to look at the dirty bit of all + * the mappings). Hence, to enforce blkcache/pagecache + * synchronization, the msync/munmap API must be used manually. Thus, + * the page cache is accurately synchronized with the block cache: + * - automatically: with the read/write/sync operations + * - manually: with the msync and munmap (and of course: exit) operations + * + * Nevertheless, from the viewpoint of "blkdev.c", the + * blockdev_read/write operations are always in sync with the MMU + * because the pagecache is accessed prior to the blkcache: any + * divergence between the pagecache and the blkcache is hence + * hidden. But keep in mind that if you want the disk to + * accurately reflect the contents of the mapped pages, you have to + * eventually call msync, munmap, or to destroy the address space (ie + * exit the process). + * + * A side effect: if you map /dev/hda and /dev/hda1, both mappings + * will be inconsistent and may also be inconsistent with read/write + * accesses. This is because the partitions have their own page cache + * while they share the block cache with the disk device. A solution + * would be to share the page cache between the disk device and all + * its partitions. But, due to the fact that partitions are not + * necessarily page-aligned in the disk, this would impose some pages + * to not correspond to a page-aligned offset inside a partition, + * requiring either to have an odd semantic of the mmap syscall (the + * allowed device "offset" would depend on the disk partitioning) if + * we want to share the mapped pages between the cache and userspace, + * or to allocate other pages for the required userspace mappings and + * keep them in sync with the page cache pages. Both solutions seem + * ugly to me, and not worth implementing since the page cache is + * aimed at being generic enough to be used for file mappings: files + * don't have sub-files (as do disk devices that have partitions). So + * solving the problem is non pertinent for files. And who will ever + * need /dev/hda mappings to be consistent with those of /dev/hda1 ?... + */ +#include +#include + + +/** Opaque structure holding a page cache */ +struct sos_fs_pagecache; + +/** Opaque structure holding a page of the cache */ +struct sos_fs_pagecache_entry; + + +sos_ret_t sos_fs_pagecache_subsystem_setup(void); + + +/** + * Function called to flush the dirty pages to backing store + */ +typedef sos_ret_t +(*sos_fs_pagecache_sync_function_t)(sos_luoffset_t offset, + sos_vaddr_t dirty_page, + void * custom_data); + + +/** + * Create a new pagecache. + * + * @param sync_fct, the function used to flush the dirty pages to + * backing store. may be NULL + */ +struct sos_fs_pagecache * +sos_fs_pagecache_new_cache(sos_fs_pagecache_sync_function_t sync_fct, + void * sync_fct_custom_data); + + +/** + * Delete the page cache. + * + * The page cache is expected to be already flushed to backing store + */ +sos_ret_t +sos_fs_pagecache_delete_cache(struct sos_fs_pagecache * pc); + + +/** + * Read from the given offset from the cache, if present. + * @return ENOENT when no page for the given offset is mapped, return + * EFAULT when the contents could not be completely copied to + * destination buffer + */ +sos_ret_t +sos_fs_pagecache_read(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_genaddr_t dest_buf, + sos_size_t * /* in/out */len); + + +/** + * Write at the given offset from the cache, if present + * @return ENOENT when no page for the given offset is mapped, return + * EFAULT when the contents could not be completely copied from + * source buffer + */ +sos_ret_t +sos_fs_pagecache_write(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_genaddr_t src_buf, + sos_size_t * /* in/out */len, + sos_bool_t synchronous_write); + + +/** + * Function reserved to blkdev.c and FS code: used by the msync + * callback to mark a pagecache page dirty + * + * @param sync_backing_store When TRUE, then the page must be flushed + * to backing store. + */ +sos_ret_t sos_fs_pagecache_set_dirty(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_bool_t sync_backing_store); + + +/** + * Prepare a page to be mapped: get a NEW reference to the page + * (kernel address) of the page to be mapped, which is also locked in + * order to be used. If the page is not yet present in the cache, + * allocate it and prepare it to be filled + * + * @param offset MUST be page-aligned + * @param newly_allocated TRUE when the page was not already mapped by + * someone: the contents of the page is then IRRELEVANT + * + * @return NULL on error + * + * @note The page is also LOCKED, use unlock to unlock it before + * unreferencing it + */ +struct sos_fs_pagecache_entry * +sos_fs_pagecache_ref_page(struct sos_fs_pagecache * pc, + sos_luoffset_t offset, + sos_vaddr_t * /* out */ kernel_vaddr, + sos_bool_t * /* out */ newly_allocated); + + +/** Called by the blkdev.c and FS page_in callback to unlock the entry + after it has been initialized. */ +sos_ret_t +sos_fs_pagecache_unlock_page(struct sos_fs_pagecache * pc, + struct sos_fs_pagecache_entry * entry, + sos_bool_t initial_fill_aborted); + + +/** + * Called when the page is unmapped from a user process space + * @param offset MUST be page-aligned + * + * @note the page is expected to be present in the cache + * @note the entry is expected NOT to be locked ! + */ +sos_ret_t +sos_fs_pagecache_unref_page(struct sos_fs_pagecache * pc, + sos_luoffset_t offset); + + +/** Call the sync function on each dirty page */ +sos_ret_t +sos_fs_pagecache_sync(struct sos_fs_pagecache * pc); + +#endif diff --git a/sos/hash.c b/sos/hash.c new file mode 100644 index 0000000..aa5d3cc --- /dev/null +++ b/sos/hash.c @@ -0,0 +1,271 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "hash.h" + +#define SOS_HASH_NAME_MAXLEN 32 + + +/** + * @file hash.c + * + * A hash table is simply a table of lists: each list hash a "bucket + * index". Each list contains the element for which the hash of the + * key is equal to the bucket index (modulo the number of buckets). + */ + + +/** + * Structure of one list of elements + */ +struct bucket +{ + sos_count_t nb_elems; + struct sos_hash_linkage * list; +}; + + +/** + * The table of buckets, ie the hash itself + */ +struct sos_hash_table +{ + char name[SOS_HASH_NAME_MAXLEN]; + + /** Hash function */ + sos_hash_func_t * key_hasher; + + /** Key comparison function */ + sos_hash_key_eq_func_t * key_iseq; + + /** Memory-offset of the key in the element structure */ + sos_uoffset_t offset_h_key; + + /** Memory-offset of the hash linkage in the element structure */ + sos_uoffset_t offset_h_linkage; + + /** Number of buckets in this hash table */ + sos_count_t nbuckets; + + /** Number of elements currently in the hash */ + sos_count_t nb_elems; + + struct bucket bucket[0]; +}; + + +/** From the address of the given element, access to its hash_linkage + structrure */ +#define h_linkage_of_elt(h,elt) \ + ( (struct sos_hash_linkage*) \ + ( ((unsigned long)(elt)) + (h)->offset_h_linkage) ) + + +/** From the address of the given element, access to its pointer to + the key */ +#define h_keyptr_of_elt(h,elt) \ + ( (void*) \ + ( ((unsigned long)(elt)) + (h)->offset_h_key) ) + + +/** From the given hash linkage structure address, retrieve the + address of the surronding element */ +#define elt_for_h_linkage(h,linkage) \ + ( (void*) \ + ( ((unsigned long)(linkage)) - (h)->offset_h_linkage) ) + + +struct sos_hash_table * +_sos_hash_create_FULL(const char *name, + sos_hash_func_t *key_hasher, + sos_hash_key_eq_func_t *key_iseq, + sos_count_t nbuckets, + sos_uoffset_t offset_h_key, + sos_uoffset_t offset_h_linkage) +{ + struct sos_hash_table * h; + h = (struct sos_hash_table*) + sos_kmalloc(sizeof(struct sos_hash_table) + + nbuckets*sizeof(struct bucket), 0); + + memset(h, 0x0, + sizeof(struct sos_hash_table) + nbuckets*sizeof(struct bucket)); + h->key_hasher = key_hasher; + h->key_iseq = key_iseq; + h->offset_h_linkage = offset_h_linkage; + h->offset_h_key = offset_h_key; + h->nbuckets = nbuckets; + strzcpy(h->name, name, SOS_HASH_NAME_MAXLEN); + + return h; +} + + +sos_count_t sos_hash_get_nb_elts(const struct sos_hash_table * h) +{ + return h->nb_elems; +} + + +sos_ret_t sos_hash_dispose(struct sos_hash_table *h) +{ + unsigned int i; + for (i = 0 ; i < h->nbuckets ; i++) + { + struct sos_hash_linkage * elt; + + list_collapse_named(h->bucket[i].list, elt, h_prev, h_next) + { + elt->h_prev = elt->h_next = NULL; + } + } + + return sos_kfree((sos_vaddr_t)h); +} + + +sos_ret_t sos_hash_insert(struct sos_hash_table *h, + void *elt_with_key) +{ + struct sos_hash_linkage * h_elt; + sos_uoffset_t bucket; + + h_elt = h_linkage_of_elt(h, elt_with_key); + if (h_elt->h_prev || h_elt->h_next) + return -SOS_EBUSY; + + if (h->key_hasher) + bucket = h->key_hasher(h_keyptr_of_elt(h, elt_with_key)) % h->nbuckets; + else + { + /* The key is assumed to be an integer */ + unsigned long * keyval = h_keyptr_of_elt(h, elt_with_key); + bucket = *keyval % h->nbuckets; + } + + list_add_head_named(h->bucket[bucket].list, h_elt, h_prev, h_next); + h->bucket[bucket].nb_elems ++; + h->nb_elems ++; + + return SOS_OK; +} + + +void * sos_hash_lookup(struct sos_hash_table *h, + const void * ptr_key) +{ + struct sos_hash_linkage * h_elt; + sos_uoffset_t bucket; + int nb; + + if (h->key_hasher) + bucket = h->key_hasher(ptr_key) % h->nbuckets; + else + { + /* The key is assumed to be an integer */ + const unsigned long * keyval = ptr_key; + bucket = *keyval % h->nbuckets; + } + + list_foreach_forward_named(h->bucket[bucket].list, h_elt, nb, h_prev, h_next) + { + void * elt = elt_for_h_linkage(h, h_elt); + void * elt_ptr_key = h_keyptr_of_elt(h, elt); + + if (ptr_key == elt_ptr_key) + return elt; + + if (! h->key_iseq) + continue; + + if (h->key_iseq(ptr_key, elt_ptr_key)) + return elt; + } + + return NULL; +} + + +sos_ret_t sos_hash_remove(struct sos_hash_table *h, + void * elt) +{ + struct sos_hash_linkage * h_elt; + sos_uoffset_t bucket; + + h_elt = h_linkage_of_elt(h, elt); + SOS_ASSERT_FATAL(h_elt->h_prev && h_elt->h_next); + + if (h->key_hasher) + bucket = h->key_hasher(h_keyptr_of_elt(h, elt)) % h->nbuckets; + else + { + unsigned long * keyval = h_keyptr_of_elt(h, elt); + bucket = *keyval % h->nbuckets; + } + + list_delete_named(h->bucket[bucket].list, h_elt, h_prev, h_next); + h->bucket[bucket].nb_elems --; + h->nb_elems --; + + return SOS_OK; +} + + +sos_bool_t sos_hash_walk(const struct sos_hash_table *h, + sos_hash_map_func_t * iter_func, + void * custom_data) +{ + unsigned int i; + for (i = 0 ; i < h->nbuckets ; i++) + { + sos_count_t nelts; + struct sos_hash_linkage * elt; + + list_foreach_forward_named(h->bucket[i].list, elt, + nelts, h_prev, h_next) + { + if (! iter_func(custom_data, + elt_for_h_linkage(h, elt))) + return FALSE; + } + } + + return TRUE; +} + + +unsigned long sos_hash_ui64(const void * ptr_key) +{ + const sos_ui64_t * keyval = ptr_key; + return ((*keyval) * 302954987) & 0xffffffff; +} + + +sos_bool_t sos_hash_key_eq_ui64(const void * ptr_key1, + const void * ptr_key2) +{ + const sos_ui64_t * keyval1 = ptr_key1; + const sos_ui64_t * keyval2 = ptr_key2; + + return (*keyval1 == *keyval2); +} diff --git a/sos/hash.h b/sos/hash.h new file mode 100644 index 0000000..ada0ef4 --- /dev/null +++ b/sos/hash.h @@ -0,0 +1,153 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_HASH_H_ +#define _SOS_HASH_H_ + +#include +#include +#include + +/** + * hash.h + * + * Hash table implementation. The key and the element structure is + * user-definable. Each element must embed: + * - the key + * - a sos_hash_linkage structure + * + * The DANGER is that the key value must NOT be changed while the + * element is inserted in the hash + */ + +/** Prototype of a hash function */ +typedef unsigned long (sos_hash_func_t)(const void * ptr_key); + +/** Prototype of a key comparison function */ +typedef sos_bool_t (sos_hash_key_eq_func_t)(const void * ptr_key1, + const void * ptr_key2); + +/** Prototype of a key/value iteration function: when FALSE = stop now ! */ +typedef sos_bool_t (sos_hash_map_func_t)(void * custom_data, + void * elt_with_key); + + +/** Opaque structure */ +struct sos_hash_table; + +/** This structure must be embedded in the elements to be insterted in + the hah */ +struct sos_hash_linkage +{ + struct sos_hash_linkage * h_prev, * h_next; +}; + + +/** + * Creation of a hash table + * + * @param name The name of the hash table (debug) + * @param elt_type The type of the elements + * @param hfunc The hash function (@see sos_hash_func_t). When NULL: + * identity (native unsigned long keys assumed) + * @param eqfunc The element comparaison function (@see + * sos_hash_key_eq_func_t). When NULL: native + * unsigned long comparison + * @param nbuckets The number of bucks in the hash + * @param name_key_field The name of the field in the element type + * that holds the key + * @param name_key_field The name of the field in the element type + * that hold the prev/next hash linkage data + */ +#define sos_hash_create(name,elt_type,hfunc,eqfunc,nbuckets, \ + name_key_field,name_h_linkage) \ + _sos_hash_create_FULL(name, hfunc, eqfunc, nbuckets, \ + offsetof(elt_type, name_key_field), \ + offsetof(elt_type, name_h_linkage)) + + +/** + * @note Real hash creation function called by the sos_hash_create + * macro + * + * @param key_hasher When NULL: the value of the hash is directly the + * pointer address + * @param key_compare When NULL: compare directly the pointer addresses + */ +struct sos_hash_table * +_sos_hash_create_FULL(const char *name, + sos_hash_func_t *key_hasher, + sos_hash_key_eq_func_t *key_iseq, + sos_count_t nbuckets, + sos_uoffset_t offset_h_key, + sos_uoffset_t offset_h_linkage); + + +/** Return the number of elements in the hash table */ +sos_count_t sos_hash_get_nb_elts(const struct sos_hash_table * h); + + +/** Does not free the elements themselves ! */ +sos_ret_t sos_hash_dispose(struct sos_hash_table *h); + + +/** + * Insert the element in the hash, associating it with the key that it + * embeds + * + * Makes sure the element is not already in the hash + */ +sos_ret_t sos_hash_insert(struct sos_hash_table *h, + void *elt_with_key); + + +/** Look for the element stored in the hash that has the key given by + ptr_key */ +void * sos_hash_lookup(struct sos_hash_table *h, + const void * ptr_key); + + +/** Remove an element from the hash, previously returned by + sos_hash_lookup */ +sos_ret_t sos_hash_remove(struct sos_hash_table *h, + void *elt); + + +/** + * Call iter_func on each of the elements in the hash table + * Stop when iter_func returns 0 (and returns FALSE). Otherwise return + * TRUE. + * + * @note iter_func must NOT block ! + * @note No particular locking + */ +sos_bool_t sos_hash_walk(const struct sos_hash_table *h, + sos_hash_map_func_t * iter_func, + void * custom_data); + + +/* + * Common hash functions + */ + +/* key = 64bits integer */ +unsigned long sos_hash_ui64(const void * ptr_key); +sos_bool_t sos_hash_key_eq_ui64(const void * ptr_key1, + const void * ptr_key2); + + +#endif /* _SOS_HASH_H_ */ diff --git a/sos/klibc.c b/sos/klibc.c new file mode 100644 index 0000000..d0f2171 --- /dev/null +++ b/sos/klibc.c @@ -0,0 +1,398 @@ +/* Copyright (C) 2004 David Decotigny (with INSA Rennes for vsnprintf) + Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "klibc.h" + +/* For an optimized version, see BSD sources ;) */ +void *memcpy(void *dst0, const void *src0, register unsigned int size) +{ + char *dst; + const char *src; + for (dst = (char*)dst0, src = (const char*)src0 ; + size > 0 ; + dst++, src++, size--) + *dst = *src; + return dst0; +} + +/* ditto */ +void *memset(void *dst0, register int c, register unsigned int length) +{ + char *dst; + for (dst = (char*) dst0 ; + length > 0 ; + dst++, length --) + *dst = (char)c; + return dst0; +} + +int memcmp(const void *s1, const void *s2, sos_size_t len) +{ + const unsigned char *c1, *c2; + unsigned int i; + + for (i = 0, c1 = s1, c2 = s2; i < len; i++, c1++, c2++) + { + if(*c1 != *c2) + return *c1 - *c2; + } + + return 0; +} + + +unsigned int strlen(register const char *str) +{ + unsigned int retval = 0; + + while (*str++) + retval++; + + return retval; +} + + +unsigned int strnlen(const char * s, sos_size_t count) +{ + const char *sc; + + for (sc = s; count-- && *sc != '\0'; ++sc) + /* nothing */continue; + + return sc - s; +} + + +char *strzcpy(register char *dst, register const char *src, register int len) +{ + int i; + + if (len <= 0) + return dst; + + for (i = 0; i < len; i++) + { + dst[i] = src[i]; + if(src[i] == '\0') + return dst; + } + + dst[len-1] = '\0'; + return dst; +} + + +char *strzcat (char *dest, const char *src, sos_size_t n) +{ + char *res = dest; + + for ( ; *dest ; dest++); + + for ( ; *src ; src++, dest++) { + *dest = *src; + n--; + if (n <= 0) + break; + } + + *dest = '\0'; + return res; +} + +int strcmp(register const char *s1, register const char *s2) +{ + while (*s1 == *s2++) + if (*s1++ == 0) + return (0); + + return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1)); +} + + +int strncmp(register const char *s1, register const char *s2, register int len) +{ + char c1 = '\0', c2 = '\0'; + + while (len > 0) + { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0' || c1 != c2) + return c1 - c2; + len--; + } + + return c1 - c2; +} + + +static unsigned long int _random_seed = 93186752; + +/** + * The following code is borrowed from Glenn Rhoads. + * http://remus.rutgers.edu/~rhoads/Code/code.html + * License to be defined... + */ +unsigned long int random (void) +{ +/* The following parameters are recommended settings based on research + uncomment the one you want. */ + +/* For RAND_MAX == 4294967291 */ + static unsigned int a = 1588635695, q = 2, r = 1117695901; +/* static unsigned int a = 1223106847, m = 4294967291U, q = 3, r = 625646750;*/ +/* static unsigned int a = 279470273, m = 4294967291U, q = 15, r = 102913196;*/ + +/* For RAND_MAX == 2147483647 */ +/* static unsigned int a = 1583458089, m = 2147483647, q = 1, r = 564025558; */ +/* static unsigned int a = 784588716, m = 2147483647, q = 2, r = 578306215; */ +/* static unsigned int a = 16807, m = 2147483647, q = 127773, r = 2836; */ +/* static unsigned int a = 950706376, m = 2147483647, q = 2, r = 246070895; */ + + _random_seed = a*(_random_seed % q) - r*(_random_seed / q); + return _random_seed; +} + + +void srandom (unsigned long int seed) +{ + _random_seed = seed; +} + + +/* I (d2) borrowed and rewrote this for Nachos/INSA Rennes. Thanks to + them for having kindly allowed me to do so. */ +int vsnprintf(char *buff, sos_size_t len, const char * format, va_list ap) +{ + sos_size_t i, result; + sos_bool_t fmt_modifiers = FALSE; + sos_bool_t prefix_long = FALSE; + sos_bool_t prefix_long_long = FALSE; + + if (!buff || !format || (len <= 0)) + return -1; + +#define PUTCHAR(thechar) \ + do { \ + if (result < len-1) \ + *buff++ = (thechar); \ + result++; \ + } while (0) + + result = 0; + for(i=0 ; format[i] != '\0' ; i++) + { + if (!fmt_modifiers && (format[i] != '%')) + { + PUTCHAR(format[i]); + continue; + } + + switch (format[i]) + { + case '%': + if (fmt_modifiers) + { + PUTCHAR('%'); + fmt_modifiers = FALSE; + break; + } + + fmt_modifiers = TRUE; + prefix_long = FALSE; + prefix_long_long = FALSE; + break; + + case 'l': + if (prefix_long) + prefix_long_long = TRUE; + else + prefix_long = TRUE; + break; + + case 'u': + { + if (! prefix_long_long) + { + unsigned int integer = va_arg(ap,unsigned int); + int cpt2 = 0; + char buff_int[16]; + + do { + int m10 = integer%10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + else + { + unsigned long long int integer + = va_arg(ap,unsigned long long int); + int cpt2 = 0; + char buff_int[32]; + + do { + int m10 = integer%10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + } + fmt_modifiers = FALSE; + break; + + case 'i': + case 'd': + { + if (! prefix_long_long) + { + int integer = va_arg(ap,int); + int cpt2 = 0; + char buff_int[16]; + + if (integer<0) + PUTCHAR('-'); + /* Ne fait pas integer = -integer ici parce que INT_MIN + n'a pas d'equivalent positif (int = [-2^31, 2^31-1]) */ + + do { + int m10 = integer%10; + m10 = (m10 < 0)? -m10:m10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + else + { + long long int integer = va_arg(ap,long long int); + int cpt2 = 0; + char buff_int[32]; + + if (integer<0) + PUTCHAR('-'); + /* Ne fait pas integer = -integer ici parce que INT_MIN + n'a pas d'equivalent positif (int = [-2^63, 2^63-1]) */ + + do { + int m10 = integer%10; + m10 = (m10 < 0)? -m10:m10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + } + fmt_modifiers = FALSE; + break; + + case 'c': + { + int value = va_arg(ap,int); + PUTCHAR((char)value); + fmt_modifiers = FALSE; + break; + } + + case 's': + { + char const*string = va_arg(ap,char const*); + if (! string) + string = "(null)"; + for( ; *string != '\0' ; string++) + PUTCHAR(*string); + fmt_modifiers = FALSE; + break; + } + + case 'p': + PUTCHAR('0'); + PUTCHAR('x'); + case 'x': + { + unsigned long long int hexa; + unsigned long long int nb; + int x, had_nonzero = 0; + + if (prefix_long_long) + hexa = va_arg(ap,unsigned long long int); + else + hexa = va_arg(ap,unsigned int); + + for(x=0 ; x < 16 ; x++) + { + nb = (unsigned long long int)(hexa << (x*4)); + nb = (nb >> 60) & 0xf; + // Skip the leading zeros + if (nb == 0) + { + if (had_nonzero) + PUTCHAR('0'); + } + else + { + had_nonzero = 1; + if (nb < 10) + PUTCHAR('0'+nb); + else + PUTCHAR('a'+(nb-10)); + } + } + if (! had_nonzero) + PUTCHAR('0'); + } + fmt_modifiers = FALSE; + break; + + default: + PUTCHAR('%'); + if (prefix_long) + PUTCHAR('l'); + if (prefix_long_long) + PUTCHAR('l'); + PUTCHAR(format[i]); + fmt_modifiers = FALSE; + } + } + + *buff = '\0'; + return result; +} + + +int snprintf(char * buff, sos_size_t len, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + len = vsnprintf(buff, len, format, ap); + va_end(ap); + + return len; +} diff --git a/sos/klibc.h b/sos/klibc.h new file mode 100644 index 0000000..061a6f0 --- /dev/null +++ b/sos/klibc.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KLIBC_H_ +#define _SOS_KLIBC_H_ + +/** + * @file klibc.h + * + * Basic libc-style support for common useful functions (string.h, + * stdarg.h), some with slight non-standard behavior (see comments). + * + * Most of the prototypes of these functions are borrowed from + * FreeBSD, but their implementation (in klibc.c) come either from Kos + * (GPL v2) or from David Decotigny (SOS). + */ + +#include + +/* string.h functions */ + +void *memcpy(void *dst, const void *src, register unsigned int size ) ; +void *memset(void *dst, register int c, register unsigned int length ) ; +int memcmp(const void *s1, const void *s2, sos_size_t n); + +#define islower(c) (('a' <= (c)) && ((c) <= 'z')) +#define isupper(c) (('A' <= (c)) && ((c) <= 'Z')) +#define isdigit(c) (('0' <= (c)) && ((c) <= '9')) +#define isspace(c) (((c) == ' ') || ((c) == '\t') || \ + ((c) == '\f') || ((c) == '\n') || \ + ((c) == '\r') || ((c) == '\v')) +#define isprint(c) ((' ' <= (c)) && ((c) <= '~')) + +unsigned int strlen( register const char *str) ; +unsigned int strnlen(const char * s, sos_size_t maxlen); + +/** + * @note Same as strncpy(), with a slightly different semantic. + * Actually, strncpy(3C) says " The result will not be null-terminated + * if the length of 'from' is n or more.". Here, 'dst' is ALWAYS + * null-terminated. And its total len will ALWAYS be <= len, with + * null-terminating-char included. + */ +char *strzcpy( register char *dst, register const char *src, + register int len ) ; + +/** + * @note Same as strncat(), with the same semantic : 'dst' is ALWAYS + * null-terminated. And its total len will ALWAYS be <= len, with + * null-terminating-char included. + */ +char *strzcat (char *dest, const char *src, + const sos_size_t len); + +int strcmp(register const char *s1, register const char *s2 ); +int strncmp(register const char *s1, register const char *s2, + register int len ); + +/* Basic stdarg.h macros. Taken from gcc support files */ +#define __GNUC_VA_LIST +typedef void *__gnuc_va_list; +typedef __gnuc_va_list va_list; +#define __va_rounded_size(TYPE) \ + (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int)) +#define va_start(AP, LASTARG) \ + (AP = ((__gnuc_va_list) __builtin_next_arg (LASTARG))) +#define va_end(AP) \ + ((void)0) +#define va_arg(AP, TYPE) \ + (AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)), \ + *((TYPE *) (void *) ((char *) (AP) - __va_rounded_size (TYPE)))) +#define __va_copy(dest, src) \ + (dest) = (src) + +/* stdarg.h functions. There might be a non-standard behavior: there + will always be a trailing '\0' in the resulting string */ +int vsnprintf(char *, sos_size_t, const char *, va_list); +int snprintf(char *, sos_size_t, const char *, /*args*/ ...) + __attribute__ ((format (printf, 3, 4))); + + +/* + * Pseudo-random generation functions. Useful to do some coverage + * tests. + */ + +/* Amplitude of the random number generation */ +#define RAND_MAX 4294967291U + +/* Pseudo-random number generation (MT unsafe) */ +unsigned long int random (void); + +/* Set random seed (MT unsafe) */ +void srandom (unsigned long int seed); + +#endif /* _SOS_KLIBC_H_ */ diff --git a/sos/kmalloc.c b/sos/kmalloc.c new file mode 100644 index 0000000..1b3d80d --- /dev/null +++ b/sos/kmalloc.c @@ -0,0 +1,114 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include + +#include "physmem.h" +#include "kmem_vmm.h" +#include "kmem_slab.h" + +#include "kmalloc.h" + +/* The cache structures for these caches, the object size, their + names, and some number of pages that contain them. They might not + necessarily be powers of 2s. */ +static struct { + const char *name; + sos_size_t object_size; + sos_count_t pages_per_slab; + struct sos_kslab_cache *cache; +} kmalloc_cache[] = + { + { "kmalloc 8B objects", 8, 1 }, + { "kmalloc 16B objects", 16, 1 }, + { "kmalloc 32B objects", 32, 1 }, + { "kmalloc 64B objects", 64, 1 }, + { "kmalloc 128B objects", 128, 1 }, + { "kmalloc 256B objects", 256, 2 }, + { "kmalloc 1024B objects", 1024, 2 }, + { "kmalloc 2048B objects", 2048, 3 }, + { "kmalloc 4096B objects", 4096, 4 }, + { "kmalloc 8192B objects", 8192, 8 }, + { "kmalloc 16384B objects", 16384, 12 }, + { NULL, 0, 0, NULL } + }; + + +sos_ret_t sos_kmalloc_subsystem_setup() +{ + int i; + for (i = 0 ; kmalloc_cache[i].object_size != 0 ; i ++) + { + struct sos_kslab_cache *new_cache; + new_cache = sos_kmem_cache_create(kmalloc_cache[i].name, + kmalloc_cache[i].object_size, + kmalloc_cache[i].pages_per_slab, + 0, + SOS_KSLAB_CREATE_MAP + ); + SOS_ASSERT_FATAL(new_cache != NULL); + kmalloc_cache[i].cache = new_cache; + } + return SOS_OK; +} + + +sos_vaddr_t sos_kmalloc(sos_size_t size, sos_ui32_t flags) +{ + /* Look for a suitable pre-allocated kmalloc cache */ + int i; + SOS_ASSERT_FATAL(size > 0); + for (i = 0 ; kmalloc_cache[i].object_size != 0 ; i ++) + { + if (kmalloc_cache[i].object_size >= size) + return sos_kmem_cache_alloc(kmalloc_cache[i].cache, + (flags + & SOS_KMALLOC_ATOMIC)? + SOS_KSLAB_ALLOC_ATOMIC:0); + } + + /* none found yet => we directly use the kmem_vmm subsystem to + allocate whole pages */ + return sos_kmem_vmm_alloc(SOS_PAGE_ALIGN_SUP(size) / SOS_PAGE_SIZE, + ( (flags + & SOS_KMALLOC_ATOMIC)? + SOS_KMEM_VMM_ATOMIC:0) + | SOS_KMEM_VMM_MAP + ); +} + + +sos_ret_t sos_kfree(sos_vaddr_t vaddr) +{ + /* The trouble here is that we aren't sure whether this object is a + slab object in a pre-allocated kmalloc cache, or an object + directly allocated as a kmem_vmm region. */ + + /* We first pretend this object is allocated in a pre-allocated + kmalloc cache */ + if (! sos_kmem_cache_free(vaddr)) + return SOS_OK; /* Great ! We guessed right ! */ + + /* Here we're wrong: it appears not to be an object in a + pre-allocated kmalloc cache. So we try to pretend this is a + kmem_vmm area */ + return sos_kmem_vmm_free(vaddr); +} + + diff --git a/sos/kmalloc.h b/sos/kmalloc.h new file mode 100644 index 0000000..3f35b9d --- /dev/null +++ b/sos/kmalloc.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KMALLOC_H_ +#define _SOS_KMALLOC_H_ + +/** + * @file kmalloc.h + * + * Simple malloc-style wrapper to kmem_vmm.h and kmem_slab.h for + * "anonymous" objects (ie not associated to any precise slab cache). + */ + +#include +#include + + +/** + * Iniatilize the kmalloc subsystem, ie pre-allocate a series of caches. + */ +sos_ret_t sos_kmalloc_subsystem_setup(void); + +/* + * sos_kmalloc flags + */ +/** sos_kmalloc() should succeed without blocking, or return NULL */ +#define SOS_KMALLOC_ATOMIC 1 + +/** + * Allocate a kernel object of the given size in the most suited slab + * cache if size can be handled by one of the pre-allocated caches, or + * using directly the range allocator otherwise. The object will + * allways be mapped in physical memory (ie implies + * SOS_KSLAB_CREATE_MAP and SOS_KMEM_VMM_MAP). + * + * @param size The size of the object + * @param flags The allocation flags (SOS_KMALLOC_* flags) + */ +sos_vaddr_t sos_kmalloc(sos_size_t size, sos_ui32_t flags); + +/** + * @note you are perfectly allowed to give the address of the + * kernel image, or the address of the bios area here, it will work: + * the kernel/bios WILL be "deallocated". But if you really want to do + * this, well..., do expect some "surprises" ;) + */ +sos_ret_t sos_kfree(sos_vaddr_t vaddr); + +#endif /* _SOS_KMALLOC_H_ */ diff --git a/sos/kmem_slab.c b/sos/kmem_slab.c new file mode 100644 index 0000000..be837ae --- /dev/null +++ b/sos/kmem_slab.c @@ -0,0 +1,814 @@ +/* Copyright (C) 2000 Thomas Petazzoni + Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include +#include +#include +#include +#include +#include + +#include "kmem_slab.h" + +/* Dimensioning constants */ +#define NB_PAGES_IN_SLAB_OF_CACHES 1 +#define NB_PAGES_IN_SLAB_OF_RANGES 1 + +/** The structure of a slab cache */ +struct sos_kslab_cache +{ + char const* name; + + /* non mutable characteristics of this slab */ + sos_size_t original_obj_size; /* asked object size */ + sos_size_t alloc_obj_size; /* actual object size, taking the + alignment constraints into account */ + sos_count_t nb_objects_per_slab; + sos_count_t nb_pages_per_slab; + sos_count_t min_free_objects; + +/* slab cache flags */ +// #define SOS_KSLAB_CREATE_MAP (1<<0) /* See kmem_slab.h */ +// #define SOS_KSLAB_CREATE_ZERO (1<<1) /* " " " " " " " " */ +#define ON_SLAB (1<<31) /* struct sos_kslab is included inside the slab */ + sos_ui32_t flags; + + /* Supervision data (updated at run-time) */ + sos_count_t nb_free_objects; + + /* The lists of slabs owned by this cache */ + struct sos_kslab *slab_list; /* head = non full, tail = full */ + + /* The caches are linked together on the kslab_cache_list */ + struct sos_kslab_cache *prev, *next; +}; + + +/** The structure of a slab */ +struct sos_kslab +{ + /** Number of free objects on this slab */ + sos_count_t nb_free; + + /** The list of these free objects */ + struct sos_kslab_free_object *free; + + /** The address of the associated range structure */ + struct sos_kmem_range *range; + + /** Virtual start address of this range */ + sos_vaddr_t first_object; + + /** Slab cache owning this slab */ + struct sos_kslab_cache *cache; + + /** Links to the other slabs managed by the same cache */ + struct sos_kslab *prev, *next; +}; + + +/** The structure of the free objects in the slab */ +struct sos_kslab_free_object +{ + struct sos_kslab_free_object *prev, *next; +}; + +/** The cache of slab caches */ +static struct sos_kslab_cache *cache_of_struct_kslab_cache; + +/** The cache of slab structures for non-ON_SLAB caches */ +static struct sos_kslab_cache *cache_of_struct_kslab; + +/** The list of slab caches */ +static struct sos_kslab_cache *kslab_cache_list; + +/* Helper function to initialize a cache structure */ +static sos_ret_t +cache_initialize(/*out*/struct sos_kslab_cache *the_cache, + const char* name, + sos_size_t obj_size, + sos_count_t pages_per_slab, + sos_count_t min_free_objs, + sos_ui32_t cache_flags) +{ + unsigned int space_left; + sos_size_t alloc_obj_size; + + if (obj_size <= 0) + return -SOS_EINVAL; + + /* Default allocation size is the requested one */ + alloc_obj_size = obj_size; + + /* Make sure the requested size is large enough to store a + free_object structure */ + if (alloc_obj_size < sizeof(struct sos_kslab_free_object)) + alloc_obj_size = sizeof(struct sos_kslab_free_object); + + /* Align obj_size on 4 bytes */ + alloc_obj_size = SOS_ALIGN_SUP(alloc_obj_size, sizeof(int)); + + /* Make sure supplied number of pages per slab is consistent with + actual allocated object size */ + if (alloc_obj_size > pages_per_slab*SOS_PAGE_SIZE) + return -SOS_EINVAL; + + /* Refuse too large slabs */ + if (pages_per_slab > MAX_PAGES_PER_SLAB) + return -SOS_ENOMEM; + + /* Fills in the cache structure */ + memset(the_cache, 0x0, sizeof(struct sos_kslab_cache)); + the_cache->name = name; + the_cache->flags = cache_flags; + the_cache->original_obj_size = obj_size; + the_cache->alloc_obj_size = alloc_obj_size; + the_cache->min_free_objects = min_free_objs; + the_cache->nb_pages_per_slab = pages_per_slab; + + /* Small size objets => the slab structure is allocated directly in + the slab */ + if(alloc_obj_size <= sizeof(struct sos_kslab)) + the_cache->flags |= ON_SLAB; + + /* + * Compute the space left once the maximum number of objects + * have been allocated in the slab + */ + space_left = the_cache->nb_pages_per_slab*SOS_PAGE_SIZE; + if(the_cache->flags & ON_SLAB) + space_left -= sizeof(struct sos_kslab); + the_cache->nb_objects_per_slab = space_left / alloc_obj_size; + space_left -= the_cache->nb_objects_per_slab*alloc_obj_size; + + /* Make sure a single slab is large enough to contain the minimum + number of objects requested */ + if (the_cache->nb_objects_per_slab < min_free_objs) + return -SOS_EINVAL; + + /* If there is now enough place for both the objects and the slab + structure, then make the slab structure ON_SLAB */ + if (space_left >= sizeof(struct sos_kslab)) + the_cache->flags |= ON_SLAB; + + return SOS_OK; +} + + +/** Helper function to add a new slab for the given cache. */ +static sos_ret_t +cache_add_slab(struct sos_kslab_cache *kslab_cache, + sos_vaddr_t vaddr_slab, + struct sos_kslab *slab) +{ + unsigned int i; + + /* Setup the slab structure */ + memset(slab, 0x0, sizeof(struct sos_kslab)); + slab->cache = kslab_cache; + + /* Establish the address of the first free object */ + slab->first_object = vaddr_slab; + + /* Account for this new slab in the cache */ + slab->nb_free = kslab_cache->nb_objects_per_slab; + kslab_cache->nb_free_objects += slab->nb_free; + + /* Build the list of free objects */ + for (i = 0 ; i < kslab_cache->nb_objects_per_slab ; i++) + { + sos_vaddr_t obj_vaddr; + + /* Set object's address */ + obj_vaddr = slab->first_object + i*kslab_cache->alloc_obj_size; + + /* Add it to the list of free objects */ + list_add_tail(slab->free, + (struct sos_kslab_free_object *)obj_vaddr); + } + + /* Add the slab to the cache's slab list: add the head of the list + since this slab is non full */ + list_add_head(kslab_cache->slab_list, slab); + + return SOS_OK; +} + + +/** Helper function to allocate a new slab for the given kslab_cache */ +static sos_ret_t +cache_grow(struct sos_kslab_cache *kslab_cache, + sos_ui32_t alloc_flags) +{ + sos_ui32_t range_alloc_flags; + + struct sos_kmem_range *new_range; + sos_vaddr_t new_range_start; + + struct sos_kslab *new_slab; + + /* + * Setup the flags for the range allocation + */ + range_alloc_flags = 0; + + /* Atomic ? */ + if (alloc_flags & SOS_KSLAB_ALLOC_ATOMIC) + range_alloc_flags |= SOS_KMEM_VMM_ATOMIC; + + /* Need physical mapping NOW ? */ + if (kslab_cache->flags & (SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO)) + range_alloc_flags |= SOS_KMEM_VMM_MAP; + + /* Allocate the range */ + new_range = sos_kmem_vmm_new_range(kslab_cache->nb_pages_per_slab, + range_alloc_flags, + & new_range_start); + if (! new_range) + return -SOS_ENOMEM; + + /* Allocate the slab structure */ + if (kslab_cache->flags & ON_SLAB) + { + /* Slab structure is ON the slab: simply set its address to the + end of the range */ + sos_vaddr_t slab_vaddr + = new_range_start + kslab_cache->nb_pages_per_slab*SOS_PAGE_SIZE + - sizeof(struct sos_kslab); + new_slab = (struct sos_kslab*)slab_vaddr; + } + else + { + /* Slab structure is OFF the slab: allocate it from the cache of + slab structures */ + sos_vaddr_t slab_vaddr + = sos_kmem_cache_alloc(cache_of_struct_kslab, + alloc_flags); + if (! slab_vaddr) + { + sos_kmem_vmm_del_range(new_range); + return -SOS_ENOMEM; + } + new_slab = (struct sos_kslab*)slab_vaddr; + } + + cache_add_slab(kslab_cache, new_range_start, new_slab); + new_slab->range = new_range; + + /* Set the backlink from range to this slab */ + sos_kmem_vmm_set_slab(new_range, new_slab); + + return SOS_OK; +} + + +/** + * Helper function to release a slab + * + * The corresponding range is always deleted, except when the @param + * must_del_range_now is not set. This happens only when the function + * gets called from sos_kmem_cache_release_struct_range(), to avoid + * large recursions. + */ +static sos_ret_t +cache_release_slab(struct sos_kslab *slab, + sos_bool_t must_del_range_now) +{ + struct sos_kslab_cache *kslab_cache = slab->cache; + struct sos_kmem_range *range = slab->range; + + SOS_ASSERT_FATAL(kslab_cache != NULL); + SOS_ASSERT_FATAL(range != NULL); + SOS_ASSERT_FATAL(slab->nb_free == slab->cache->nb_objects_per_slab); + + /* First, remove the slab from the slabs' list of the cache */ + list_delete(kslab_cache->slab_list, slab); + slab->cache->nb_free_objects -= slab->nb_free; + + /* Release the slab structure if it is OFF slab */ + if (! (slab->cache->flags & ON_SLAB)) + sos_kmem_cache_free((sos_vaddr_t)slab); + + /* Ok, the range is not bound to any slab anymore */ + sos_kmem_vmm_set_slab(range, NULL); + + /* Always delete the range now, unless we are told not to do so (see + sos_kmem_cache_release_struct_range() below) */ + if (must_del_range_now) + return sos_kmem_vmm_del_range(range); + + return SOS_OK; +} + + +/** + * Helper function to create the initial cache of caches, with a very + * first slab in it, so that new cache structures can be simply allocated. + * @return the cache structure for the cache of caches + */ +static struct sos_kslab_cache * +create_cache_of_caches(sos_vaddr_t vaddr_first_slab_of_caches, + int nb_pages) +{ + /* The preliminary cache structure we need in order to allocate the + first slab in the cache of caches (allocated on the stack !) */ + struct sos_kslab_cache fake_cache_of_caches; + + /* The real cache structure for the cache of caches */ + struct sos_kslab_cache *real_cache_of_caches; + + /* The kslab structure for this very first slab */ + struct sos_kslab *slab_of_caches; + + /* Init the cache structure for the cache of caches */ + if (cache_initialize(& fake_cache_of_caches, + "Caches", sizeof(struct sos_kslab_cache), + nb_pages, 0, SOS_KSLAB_CREATE_MAP | ON_SLAB)) + /* Something wrong with the parameters */ + return NULL; + + memset((void*)vaddr_first_slab_of_caches, 0x0, nb_pages*SOS_PAGE_SIZE); + + /* Add the pages for the 1st slab of caches */ + slab_of_caches = (struct sos_kslab*)(vaddr_first_slab_of_caches + + nb_pages*SOS_PAGE_SIZE + - sizeof(struct sos_kslab)); + + /* Add the abovementioned 1st slab to the cache of caches */ + cache_add_slab(& fake_cache_of_caches, + vaddr_first_slab_of_caches, + slab_of_caches); + + /* Now we allocate a cache structure, which will be the real cache + of caches, ie a cache structure allocated INSIDE the cache of + caches, not inside the stack */ + real_cache_of_caches + = (struct sos_kslab_cache*) sos_kmem_cache_alloc(& fake_cache_of_caches, + 0); + /* We initialize it */ + memcpy(real_cache_of_caches, & fake_cache_of_caches, + sizeof(struct sos_kslab_cache)); + /* We need to update the slab's 'cache' field */ + slab_of_caches->cache = real_cache_of_caches; + + /* Add the cache to the list of slab caches */ + list_add_tail(kslab_cache_list, real_cache_of_caches); + + return real_cache_of_caches; +} + + +/** + * Helper function to create the initial cache of ranges, with a very + * first slab in it, so that new kmem_range structures can be simply + * allocated. + * @return the cache of kmem_range + */ +static struct sos_kslab_cache * +create_cache_of_ranges(sos_vaddr_t vaddr_first_slab_of_ranges, + sos_size_t sizeof_struct_range, + int nb_pages) +{ + /* The cache structure for the cache of kmem_range */ + struct sos_kslab_cache *cache_of_ranges; + + /* The kslab structure for the very first slab of ranges */ + struct sos_kslab *slab_of_ranges; + + cache_of_ranges = (struct sos_kslab_cache*) + sos_kmem_cache_alloc(cache_of_struct_kslab_cache, + 0); + if (! cache_of_ranges) + return NULL; + + /* Init the cache structure for the cache of ranges with min objects + per slab = 2 !!! */ + if (cache_initialize(cache_of_ranges, + "struct kmem_range", + sizeof_struct_range, + nb_pages, 2, SOS_KSLAB_CREATE_MAP | ON_SLAB)) + /* Something wrong with the parameters */ + return NULL; + + /* Add the cache to the list of slab caches */ + list_add_tail(kslab_cache_list, cache_of_ranges); + + /* + * Add the first slab for this cache + */ + memset((void*)vaddr_first_slab_of_ranges, 0x0, nb_pages*SOS_PAGE_SIZE); + + /* Add the pages for the 1st slab of ranges */ + slab_of_ranges = (struct sos_kslab*)(vaddr_first_slab_of_ranges + + nb_pages*SOS_PAGE_SIZE + - sizeof(struct sos_kslab)); + + cache_add_slab(cache_of_ranges, + vaddr_first_slab_of_ranges, + slab_of_ranges); + + return cache_of_ranges; +} + + +struct sos_kslab_cache * +sos_kmem_cache_subsystem_setup_prepare(sos_vaddr_t kernel_core_base, + sos_vaddr_t kernel_core_top, + sos_size_t sizeof_struct_range, + /* results */ + struct sos_kslab **first_struct_slab_of_caches, + sos_vaddr_t *first_slab_of_caches_base, + sos_count_t *first_slab_of_caches_nb_pages, + struct sos_kslab **first_struct_slab_of_ranges, + sos_vaddr_t *first_slab_of_ranges_base, + sos_count_t *first_slab_of_ranges_nb_pages) +{ + int i; + sos_ret_t retval; + sos_vaddr_t vaddr; + + /* The cache of ranges we are about to allocate */ + struct sos_kslab_cache *cache_of_ranges; + + /* In the begining, there isn't any cache */ + kslab_cache_list = NULL; + cache_of_struct_kslab = NULL; + cache_of_struct_kslab_cache = NULL; + + /* + * Create the cache of caches, initialised with 1 allocated slab + */ + + /* Allocate the pages needed for the 1st slab of caches, and map them + in kernel space, right after the kernel */ + *first_slab_of_caches_base = SOS_PAGE_ALIGN_SUP(kernel_core_top); + for (i = 0, vaddr = *first_slab_of_caches_base ; + i < NB_PAGES_IN_SLAB_OF_CACHES ; + i++, vaddr += SOS_PAGE_SIZE) + { + sos_paddr_t ppage_paddr; + + ppage_paddr + = sos_physmem_ref_physpage_new(FALSE); + SOS_ASSERT_FATAL(ppage_paddr != (sos_paddr_t)NULL); + + retval = sos_paging_map(ppage_paddr, vaddr, + FALSE, + SOS_VM_MAP_ATOMIC + | SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE); + SOS_ASSERT_FATAL(retval == SOS_OK); + + retval = sos_physmem_unref_physpage(ppage_paddr); + SOS_ASSERT_FATAL(retval == FALSE); + } + + /* Create the cache of caches */ + *first_slab_of_caches_nb_pages = NB_PAGES_IN_SLAB_OF_CACHES; + cache_of_struct_kslab_cache + = create_cache_of_caches(*first_slab_of_caches_base, + NB_PAGES_IN_SLAB_OF_CACHES); + SOS_ASSERT_FATAL(cache_of_struct_kslab_cache != NULL); + + /* Retrieve the slab that should have been allocated */ + *first_struct_slab_of_caches + = list_get_head(cache_of_struct_kslab_cache->slab_list); + + + /* + * Create the cache of ranges, initialised with 1 allocated slab + */ + *first_slab_of_ranges_base = vaddr; + /* Allocate the 1st slab */ + for (i = 0, vaddr = *first_slab_of_ranges_base ; + i < NB_PAGES_IN_SLAB_OF_RANGES ; + i++, vaddr += SOS_PAGE_SIZE) + { + sos_paddr_t ppage_paddr; + + ppage_paddr + = sos_physmem_ref_physpage_new(FALSE); + SOS_ASSERT_FATAL(ppage_paddr != (sos_paddr_t)NULL); + + retval = sos_paging_map(ppage_paddr, vaddr, + FALSE, + SOS_VM_MAP_ATOMIC + | SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE); + SOS_ASSERT_FATAL(retval == SOS_OK); + + retval = sos_physmem_unref_physpage(ppage_paddr); + SOS_ASSERT_FATAL(retval == FALSE); + } + + /* Create the cache of ranges */ + *first_slab_of_ranges_nb_pages = NB_PAGES_IN_SLAB_OF_RANGES; + cache_of_ranges = create_cache_of_ranges(*first_slab_of_ranges_base, + sizeof_struct_range, + NB_PAGES_IN_SLAB_OF_RANGES); + SOS_ASSERT_FATAL(cache_of_ranges != NULL); + + /* Retrieve the slab that should have been allocated */ + *first_struct_slab_of_ranges + = list_get_head(cache_of_ranges->slab_list); + + /* + * Create the cache of slabs, without any allocated slab yet + */ + cache_of_struct_kslab + = sos_kmem_cache_create("off-slab slab structures", + sizeof(struct sos_kslab), + 1, + 0, + SOS_KSLAB_CREATE_MAP); + SOS_ASSERT_FATAL(cache_of_struct_kslab != NULL); + + return cache_of_ranges; +} + + +sos_ret_t +sos_kmem_cache_subsystem_setup_commit(struct sos_kslab *first_struct_slab_of_caches, + struct sos_kmem_range *first_range_of_caches, + struct sos_kslab *first_struct_slab_of_ranges, + struct sos_kmem_range *first_range_of_ranges) +{ + first_struct_slab_of_caches->range = first_range_of_caches; + first_struct_slab_of_ranges->range = first_range_of_ranges; + return SOS_OK; +} + + +struct sos_kslab_cache * +sos_kmem_cache_create(const char* name, + sos_size_t obj_size, + sos_count_t pages_per_slab, + sos_count_t min_free_objs, + sos_ui32_t cache_flags) +{ + struct sos_kslab_cache *new_cache; + + SOS_ASSERT_FATAL(obj_size > 0); + + /* Allocate the new cache */ + new_cache = (struct sos_kslab_cache*) + sos_kmem_cache_alloc(cache_of_struct_kslab_cache, + 0/* NOT ATOMIC */); + if (! new_cache) + return NULL; + + if (cache_initialize(new_cache, name, obj_size, + pages_per_slab, min_free_objs, + cache_flags)) + { + /* Something was wrong */ + sos_kmem_cache_free((sos_vaddr_t)new_cache); + return NULL; + } + + /* Add the cache to the list of slab caches */ + list_add_tail(kslab_cache_list, new_cache); + + /* if the min_free_objs is set, pre-allocate a slab */ + if (min_free_objs) + { + if (cache_grow(new_cache, 0 /* Not atomic */) != SOS_OK) + { + sos_kmem_cache_destroy(new_cache); + return NULL; /* Not enough memory */ + } + } + + return new_cache; +} + + +sos_ret_t sos_kmem_cache_destroy(struct sos_kslab_cache *kslab_cache) +{ + int nb_slabs; + struct sos_kslab *slab; + + if (! kslab_cache) + return -SOS_EINVAL; + + /* Refuse to destroy the cache if there are any objects still + allocated */ + list_foreach(kslab_cache->slab_list, slab, nb_slabs) + { + if (slab->nb_free != kslab_cache->nb_objects_per_slab) + return -SOS_EBUSY; + } + + /* Remove all the slabs */ + while ((slab = list_get_head(kslab_cache->slab_list)) != NULL) + { + cache_release_slab(slab, TRUE); + } + + /* Remove the cache */ + return sos_kmem_cache_free((sos_vaddr_t)kslab_cache); +} + + +sos_vaddr_t sos_kmem_cache_alloc(struct sos_kslab_cache *kslab_cache, + sos_ui32_t alloc_flags) +{ + sos_vaddr_t obj_vaddr; + struct sos_kslab * slab_head; +#define ALLOC_RET return + + /* If the slab at the head of the slabs' list has no free object, + then the other slabs don't either => need to allocate a new + slab */ + if ((! kslab_cache->slab_list) + || (! list_get_head(kslab_cache->slab_list)->free)) + { + if (cache_grow(kslab_cache, alloc_flags) != SOS_OK) + /* Not enough memory or blocking alloc */ + ALLOC_RET( (sos_vaddr_t)NULL); + } + + /* Here: we are sure that list_get_head(kslab_cache->slab_list) + exists *AND* that list_get_head(kslab_cache->slab_list)->free is + NOT NULL */ + slab_head = list_get_head(kslab_cache->slab_list); + SOS_ASSERT_FATAL(slab_head != NULL); + + /* Allocate the object at the head of the slab at the head of the + slabs' list */ + obj_vaddr = (sos_vaddr_t)list_pop_head(slab_head->free); + slab_head->nb_free --; + kslab_cache->nb_free_objects --; + + /* If needed, reset object's contents */ + if (kslab_cache->flags & SOS_KSLAB_CREATE_ZERO) + memset((void*)obj_vaddr, 0x0, kslab_cache->alloc_obj_size); + + /* Slab is now full ? */ + if (slab_head->free == NULL) + { + /* Transfer it at the tail of the slabs' list */ + struct sos_kslab *slab; + slab = list_pop_head(kslab_cache->slab_list); + list_add_tail(kslab_cache->slab_list, slab); + } + + /* + * For caches that require a minimum amount of free objects left, + * allocate a slab if needed. + * + * Notice the "== min_objects - 1": we did not write " < + * min_objects" because for the cache of kmem structure, this would + * lead to an chicken-and-egg problem, since cache_grow below would + * call cache_alloc again for the kmem_vmm cache, so we return here + * with the same cache. If the test were " < min_objects", then we + * would call cache_grow again for the kmem_vmm cache again and + * again... until we reach the bottom of our stack (infinite + * recursion). By telling precisely "==", then the cache_grow would + * only be called the first time. + */ + if ((kslab_cache->min_free_objects > 0) + && (kslab_cache->nb_free_objects == (kslab_cache->min_free_objects - 1))) + { + /* No: allocate a new slab now */ + if (cache_grow(kslab_cache, alloc_flags) != SOS_OK) + { + /* Not enough free memory or blocking alloc => undo the + allocation */ + sos_kmem_cache_free(obj_vaddr); + ALLOC_RET( (sos_vaddr_t)NULL); + } + } + + ALLOC_RET(obj_vaddr); +} + + +/** + * Helper function to free the object located at the given address. + * + * @param empty_slab is the address of the slab to release, if removing + * the object causes the slab to become empty. + */ +inline static +sos_ret_t +free_object(sos_vaddr_t vaddr, + struct sos_kslab ** empty_slab) +{ + struct sos_kslab_cache *kslab_cache; + + /* Lookup the slab containing the object in the slabs' list */ + struct sos_kslab *slab = sos_kmem_vmm_resolve_slab(vaddr); + + /* By default, consider that the slab will not become empty */ + *empty_slab = NULL; + + /* Did not find the slab */ + if (! slab) + return -SOS_EINVAL; + + SOS_ASSERT_FATAL(slab->cache); + kslab_cache = slab->cache; + + /* + * Check whether the address really could mark the start of an actual + * allocated object + */ + /* Address multiple of an object's size ? */ + if (( (vaddr - slab->first_object) + % kslab_cache->alloc_obj_size) != 0) + return -SOS_EINVAL; + /* Address not too large ? */ + if (( (vaddr - slab->first_object) + / kslab_cache->alloc_obj_size) >= kslab_cache->nb_objects_per_slab) + return -SOS_EINVAL; + + /* + * Ok: we now release the object + */ + + /* Did find a full slab => will not be full any more => move it + to the head of the slabs' list */ + if (! slab->free) + { + list_delete(kslab_cache->slab_list, slab); + list_add_head(kslab_cache->slab_list, slab); + } + + /* Release the object */ + list_add_head(slab->free, (struct sos_kslab_free_object*)vaddr); + slab->nb_free++; + kslab_cache->nb_free_objects++; + SOS_ASSERT_FATAL(slab->nb_free <= slab->cache->nb_objects_per_slab); + + /* Cause the slab to be released if it becomes empty, and if we are + allowed to do it */ + if ((slab->nb_free >= kslab_cache->nb_objects_per_slab) + && (kslab_cache->nb_free_objects - slab->nb_free + >= kslab_cache->min_free_objects)) + { + *empty_slab = slab; + } + + return SOS_OK; +} + + +sos_ret_t sos_kmem_cache_free(sos_vaddr_t vaddr) +{ + sos_ret_t retval; + struct sos_kslab *empty_slab; + + /* Remove the object from the slab */ + retval = free_object(vaddr, & empty_slab); + if (retval != SOS_OK) + return retval; + + /* Remove the slab and the underlying range if needed */ + if (empty_slab != NULL) + return cache_release_slab(empty_slab, TRUE); + + return SOS_OK; +} + + +struct sos_kmem_range * +sos_kmem_cache_release_struct_range(struct sos_kmem_range *the_range) +{ + sos_ret_t retval; + struct sos_kslab *empty_slab; + + /* Remove the object from the slab */ + retval = free_object((sos_vaddr_t)the_range, & empty_slab); + if (retval != SOS_OK) + return NULL; + + /* Remove the slab BUT NOT the underlying range if needed */ + if (empty_slab != NULL) + { + struct sos_kmem_range *empty_range = empty_slab->range; + SOS_ASSERT_FATAL(cache_release_slab(empty_slab, FALSE) == SOS_OK); + SOS_ASSERT_FATAL(empty_range != NULL); + return empty_range; + } + + return NULL; +} + diff --git a/sos/kmem_slab.h b/sos/kmem_slab.h new file mode 100644 index 0000000..50a0d03 --- /dev/null +++ b/sos/kmem_slab.h @@ -0,0 +1,206 @@ +/* Copyright (C) 2000 Thomas Petazzoni + Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KMEM_SLAB_H_ +#define _SOS_KMEM_SLAB_H_ + +/** + * @file kmem_slab.h + * + * Kernel Memory Allocator based on Bonwick's slab llocator (Solaris + * 2.4, Linux 2.4). This allocator achieves good memory utilization + * ratio (memory effectively used / memory requested) ie limited + * fragmentation, while elegantly handling cache-effect considerations + * (TLB locality through the notion of "cache" of slabs, and the + * dcache utilization through the notion of cache colouring to + * decrease the conflicts in the dcache for accesses to different data + * in the same cache). + * + * This allocator relies on the range allocator (kmem_vmm.h) to + * allocate the slabs, which itself relies on the slab allocator to + * allocate its "range" data structures, thus leading to a + * chicken-and-egg problem. We solve this problem by introducing the + * notion of "min_free_objs" for the slab caches, in order for the cache + * of ranges to always have enough ranges in reserve to complete the + * range allocation before being urged to allocate a new slab of + * ranges, which would require the allocation of a new range. + * + * Compared to Bonwick's recommendations, we don't handle ctor/dtor + * routines on the objects, so that we can alter the objects once they + * are set free. Thus, the list of free object is stored in the free + * objects themselves, not alongside the objects (this also implies that + * the SOS_KSLAB_CREATE_MAP flag below is meaningless). We also don't + * implement the cache colouring (trivial to add, but we omit it for + * readability reasons), and the only alignment constraint we respect + * is that allocated objects are aligned on a 4B boundary: for other + * alignment constraints, the user must integrate them in the + * "object_size" parameter to "sos_kmem_cache_create()". + * + * References : + * - J. Bonwick's paper, "The slab allocator: An object-caching kernel + * memory allocator", In USENIX Summer 1994 Technical Conference + * - The bible, aka "Unix internals : the new frontiers" (section + * 12.10), Uresh Vahalia, Prentice Hall 1996, ISBN 0131019082 + * - "The Linux slab allocator", B. Fitzgibbons, + * http://www.cc.gatech.edu/people/home/bradf/cs7001/proj2/ + * - The Kos, http://kos.enix.org/ + */ +#include +#include + +/** Opaque data structure that defines a Cache of slabs */ +struct sos_kslab_cache; + +/** Opaque data structure that defines a slab. Exported only to + kmem_vmm.h */ +struct sos_kslab; + +#include "kmem_vmm.h" + + +/** The maximum allowed pages for each slab */ +#define MAX_PAGES_PER_SLAB 32 /* 128 kB */ + + +/** + * Initialize the slab cache of slab caches, and prepare the cache of + * kmem_range for kmem_vmm. + * + * @param kernel_core_base The virtual address of the first byte used + * by the kernel code/data + * + * @param kernel_core_top The virtual address of the first byte after + * the kernel code/data. + * + * @param sizeof_struct_range the size of the objects (aka "struct + * sos_kmem_vmm_ranges") to be allocated in the cache of ranges + * + * @param first_struct_slab_of_caches (output value) the virtual + * address of the first slab structure that gets allocated for the + * cache of caches. The function actually manually allocate the first + * slab of the cache of caches because of a chicken-and-egg thing. The + * address of the slab is used by the kmem_vmm_setup routine to + * finalize the allocation of the slab, in order for it to behave like + * a real slab afterwards. + * + * @param first_slab_of_caches_base (output value) the virtual address + * of the slab associated to the slab structure. + * + * @param first_slab_of_caches_nb_pages (output value) the number of + * (virtual) pages used by the first slab of the cache of caches. + * + * @param first_struct_slab_of_ranges (output value) the virtual address + * of the first slab that gets allocated for the cache of ranges. Same + * explanation as above. + * + * @param first_slab_of_ranges_base (output value) the virtual address + * of the slab associated to the slab structure. + * + * @param first_slab_of_ranges_nb_pages (output value) the number of + * (virtual) pages used by the first slab of the cache of ranges. + * + * @return the cache of kmem_range immediatly usable + */ +struct sos_kslab_cache * +sos_kmem_cache_subsystem_setup_prepare(sos_vaddr_t kernel_core_base, + sos_vaddr_t kernel_core_top, + sos_size_t sizeof_struct_range, + /* results */ + struct sos_kslab **first_struct_slab_of_caches, + sos_vaddr_t *first_slab_of_caches_base, + sos_count_t *first_slab_of_caches_nb_pages, + struct sos_kslab **first_struct_slab_of_ranges, + sos_vaddr_t *first_slab_of_ranges_base, + sos_count_t *first_slab_of_ranges_nb_pages); + +/** + * Update the configuration of the cache subsystem once the vmm + * subsystem has been fully initialized + */ +sos_ret_t +sos_kmem_cache_subsystem_setup_commit(struct sos_kslab *first_struct_slab_of_caches, + struct sos_kmem_range *first_range_of_caches, + struct sos_kslab *first_struct_slab_of_ranges, + struct sos_kmem_range *first_range_of_ranges); + + +/* + * Flags for sos_kmem_cache_create() + */ +/** The slabs should be initially mapped in physical memory */ +#define SOS_KSLAB_CREATE_MAP (1<<0) +/** The object should always be set to zero at allocation (implies + SOS_KSLAB_CREATE_MAP) */ +#define SOS_KSLAB_CREATE_ZERO (1<<1) + +/** + * @note this function MAY block (involved allocations are not atomic) + * @param name must remain valid during the whole cache's life + * (shallow copy) ! + * @param cache_flags An or-ed combination of the SOS_KSLAB_CREATE_* flags + */ +struct sos_kslab_cache * +sos_kmem_cache_create(const char* name, + sos_size_t object_size, + sos_count_t pages_per_slab, + sos_count_t min_free_objects, + sos_ui32_t cache_flags); + +sos_ret_t sos_kmem_cache_destroy(struct sos_kslab_cache *kslab_cache); + + +/* + * Flags for sos_kmem_cache_alloc() + */ +/** Allocation should either succeed or fail, without blocking */ +#define SOS_KSLAB_ALLOC_ATOMIC (1<<0) + +/** + * Allocate an object from the given cache. + * + * @param alloc_flags An or-ed combination of the SOS_KSLAB_ALLOC_* flags + */ +sos_vaddr_t sos_kmem_cache_alloc(struct sos_kslab_cache *kslab_cache, + sos_ui32_t alloc_flags); + + +/** + * Free an object (assumed to be already allocated and not already + * free) at the given virtual address. + */ +sos_ret_t sos_kmem_cache_free(sos_vaddr_t vaddr); + + +/* + * Function reserved to kmem_vmm.c. Does almost everything + * sos_kmem_cache_free() does, except it does not call + * sos_kmem_vmm_del_range() if it needs to. This is aimed at avoiding + * large recursion when a range is freed with + * sos_kmem_vmm_del_range(). + * + * @param the_range The range structure to free + * + * @return NULL when the range containing 'the_range' still contains + * other ranges, or the address of the range which owned 'the_range' + * if it becomes empty. + */ +struct sos_kmem_range * +sos_kmem_cache_release_struct_range(struct sos_kmem_range *the_range); + + +#endif /* _SOS_KMEM_SLAB_H_ */ diff --git a/sos/kmem_vmm.c b/sos/kmem_vmm.c new file mode 100644 index 0000000..7fd7988 --- /dev/null +++ b/sos/kmem_vmm.c @@ -0,0 +1,607 @@ +/* Copyright (C) 2000 Thomas Petazzoni + Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "kmem_vmm.h" + +/** The structure of a range of kernel-space virtual addresses */ +struct sos_kmem_range +{ + sos_vaddr_t base_vaddr; + sos_count_t nb_pages; + + /* The slab owning this range, or NULL */ + struct sos_kslab *slab; + + struct sos_kmem_range *prev, *next; +}; +const int sizeof_struct_sos_kmem_range = sizeof(struct sos_kmem_range); + +/** The ranges are SORTED in (strictly) ascending base addresses */ +static struct sos_kmem_range *kmem_free_range_list, *kmem_used_range_list; + +/** The slab cache for the kmem ranges */ +static struct sos_kslab_cache *kmem_range_cache; + + + +/** Helper function to get the closest preceding or containing + range for the given virtual address */ +static struct sos_kmem_range * +get_closest_preceding_kmem_range(struct sos_kmem_range *the_list, + sos_vaddr_t vaddr) +{ + int nb_elements; + struct sos_kmem_range *a_range, *ret_range; + + /* kmem_range list is kept SORTED, so we exit as soon as vaddr >= a + range base address */ + ret_range = NULL; + list_foreach(the_list, a_range, nb_elements) + { + if (vaddr < a_range->base_vaddr) + return ret_range; + ret_range = a_range; + } + + /* This will always be the LAST range in the kmem area */ + return ret_range; +} + + +/** + * Helper function to lookup a free range large enough to hold nb_pages + * pages (first fit) + */ +static struct sos_kmem_range *find_suitable_free_range(sos_count_t nb_pages) +{ + int nb_elements; + struct sos_kmem_range *r; + + list_foreach(kmem_free_range_list, r, nb_elements) + { + if (r->nb_pages >= nb_pages) + return r; + } + + return NULL; +} + + +/** + * Helper function to add a_range in the_list, in strictly ascending order. + * + * @return The (possibly) new head of the_list + */ +static struct sos_kmem_range *insert_range(struct sos_kmem_range *the_list, + struct sos_kmem_range *a_range) +{ + struct sos_kmem_range *prec_used; + + /** Look for any preceding range */ + prec_used = get_closest_preceding_kmem_range(the_list, + a_range->base_vaddr); + /** insert a_range /after/ this prec_used */ + if (prec_used != NULL) + list_insert_after(the_list, prec_used, a_range); + else /* Insert at the beginning of the list */ + list_add_head(the_list, a_range); + + return the_list; +} + + +/** + * Helper function to retrieve the range owning the given vaddr, by + * scanning the physical memory first if vaddr is mapped in RAM + */ +static struct sos_kmem_range *lookup_range(sos_vaddr_t vaddr) +{ + struct sos_kmem_range *range; + + /* First: try to retrieve the physical page mapped at this address */ + sos_paddr_t ppage_paddr = SOS_PAGE_ALIGN_INF(sos_paging_get_paddr(vaddr)); + + if (ppage_paddr) + { + range = sos_physmem_get_kmem_range(ppage_paddr); + + /* If a page is mapped at this address, it is EXPECTED that it + is really associated with a range */ + SOS_ASSERT_FATAL(range != NULL); + } + + /* Otherwise scan the list of used ranges, looking for the range + owning the address */ + else + { + range = get_closest_preceding_kmem_range(kmem_used_range_list, + vaddr); + /* Not found */ + if (! range) + return NULL; + + /* vaddr not covered by this range */ + if ( (vaddr < range->base_vaddr) + || (vaddr >= (range->base_vaddr + range->nb_pages*SOS_PAGE_SIZE)) ) + return NULL; + } + + return range; +} + + +/** + * Helper function for sos_kmem_vmm_setup() to initialize a new range + * that maps a given area as free or as already used. + * This function either succeeds or halts the whole system. + */ +static struct sos_kmem_range * +create_range(sos_bool_t is_free, + sos_vaddr_t base_vaddr, + sos_vaddr_t top_vaddr, + struct sos_kslab *associated_slab) +{ + struct sos_kmem_range *range; + + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(base_vaddr)); + SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(top_vaddr)); + + if ((top_vaddr - base_vaddr) < SOS_PAGE_SIZE) + return NULL; + + range = (struct sos_kmem_range*)sos_kmem_cache_alloc(kmem_range_cache, + SOS_KSLAB_ALLOC_ATOMIC); + SOS_ASSERT_FATAL(range != NULL); + + range->base_vaddr = base_vaddr; + range->nb_pages = (top_vaddr - base_vaddr) / SOS_PAGE_SIZE; + + if (is_free) + { + list_add_tail(kmem_free_range_list, + range); + } + else + { + sos_vaddr_t vaddr; + range->slab = associated_slab; + list_add_tail(kmem_used_range_list, + range); + + /* Ok, set the range owner for the pages in this page */ + for (vaddr = base_vaddr ; + vaddr < top_vaddr ; + vaddr += SOS_PAGE_SIZE) + { + sos_paddr_t ppage_paddr = sos_paging_get_paddr(vaddr); + SOS_ASSERT_FATAL((void*)ppage_paddr != NULL); + sos_physmem_set_kmem_range(ppage_paddr, range); + } + } + + return range; +} + + +sos_ret_t +sos_kmem_vmm_subsystem_setup(sos_vaddr_t kernel_core_base, + sos_vaddr_t kernel_core_top, + sos_vaddr_t bootstrap_stack_bottom_vaddr, + sos_vaddr_t bootstrap_stack_top_vaddr) +{ + struct sos_kslab *first_struct_slab_of_caches, + *first_struct_slab_of_ranges; + sos_vaddr_t first_slab_of_caches_base, + first_slab_of_caches_nb_pages, + first_slab_of_ranges_base, + first_slab_of_ranges_nb_pages; + struct sos_kmem_range *first_range_of_caches, + *first_range_of_ranges; + + list_init(kmem_free_range_list); + list_init(kmem_used_range_list); + + kmem_range_cache + = sos_kmem_cache_subsystem_setup_prepare(kernel_core_base, + kernel_core_top, + sizeof(struct sos_kmem_range), + & first_struct_slab_of_caches, + & first_slab_of_caches_base, + & first_slab_of_caches_nb_pages, + & first_struct_slab_of_ranges, + & first_slab_of_ranges_base, + & first_slab_of_ranges_nb_pages); + SOS_ASSERT_FATAL(kmem_range_cache != NULL); + + /* Mark virtual addresses 16kB - Video as FREE */ + create_range(TRUE, + SOS_KMEM_VMM_BASE, + SOS_PAGE_ALIGN_INF(BIOS_N_VIDEO_START), + NULL); + + /* Mark virtual addresses in Video hardware mapping as NOT FREE */ + create_range(FALSE, + SOS_PAGE_ALIGN_INF(BIOS_N_VIDEO_START), + SOS_PAGE_ALIGN_SUP(BIOS_N_VIDEO_END), + NULL); + + /* Mark virtual addresses Video - Kernel as FREE */ + create_range(TRUE, + SOS_PAGE_ALIGN_SUP(BIOS_N_VIDEO_END), + SOS_PAGE_ALIGN_INF(kernel_core_base), + NULL); + + /* Mark virtual addresses in Kernel code/data up to the bootstrap stack + as NOT FREE */ + create_range(FALSE, + SOS_PAGE_ALIGN_INF(kernel_core_base), + bootstrap_stack_bottom_vaddr, + NULL); + + /* Mark virtual addresses in the bootstrap stack as NOT FREE too, + but in another vmm region in order to be un-allocated later */ + create_range(FALSE, + bootstrap_stack_bottom_vaddr, + bootstrap_stack_top_vaddr, + NULL); + + /* Mark the remaining virtual addresses in Kernel code/data after + the bootstrap stack as NOT FREE */ + create_range(FALSE, + bootstrap_stack_top_vaddr, + SOS_PAGE_ALIGN_SUP(kernel_core_top), + NULL); + + /* Mark virtual addresses in the first slab of the cache of caches + as NOT FREE */ + SOS_ASSERT_FATAL(SOS_PAGE_ALIGN_SUP(kernel_core_top) + == first_slab_of_caches_base); + SOS_ASSERT_FATAL(first_struct_slab_of_caches != NULL); + first_range_of_caches + = create_range(FALSE, + first_slab_of_caches_base, + first_slab_of_caches_base + + first_slab_of_caches_nb_pages*SOS_PAGE_SIZE, + first_struct_slab_of_caches); + + /* Mark virtual addresses in the first slab of the cache of ranges + as NOT FREE */ + SOS_ASSERT_FATAL((first_slab_of_caches_base + + first_slab_of_caches_nb_pages*SOS_PAGE_SIZE) + == first_slab_of_ranges_base); + SOS_ASSERT_FATAL(first_struct_slab_of_ranges != NULL); + first_range_of_ranges + = create_range(FALSE, + first_slab_of_ranges_base, + first_slab_of_ranges_base + + first_slab_of_ranges_nb_pages*SOS_PAGE_SIZE, + first_struct_slab_of_ranges); + + /* Mark virtual addresses after these slabs as FREE */ + create_range(TRUE, + first_slab_of_ranges_base + + first_slab_of_ranges_nb_pages*SOS_PAGE_SIZE, + SOS_KMEM_VMM_TOP, + NULL); + + /* Update the cache subsystem so that the artificially-created + caches of caches and ranges really behave like *normal* caches (ie + those allocated by the normal slab API) */ + sos_kmem_cache_subsystem_setup_commit(first_struct_slab_of_caches, + first_range_of_caches, + first_struct_slab_of_ranges, + first_range_of_ranges); + + return SOS_OK; +} + + +/** + * Allocate a new kernel area spanning one or multiple pages. + * + * @eturn a new range structure + */ +struct sos_kmem_range *sos_kmem_vmm_new_range(sos_count_t nb_pages, + sos_ui32_t flags, + sos_vaddr_t * range_start) +{ + struct sos_kmem_range *free_range, *new_range; + + if (nb_pages <= 0) + return NULL; + + /* Find a suitable free range to hold the size-sized object */ + free_range = find_suitable_free_range(nb_pages); + if (free_range == NULL) + return NULL; + + /* If range has exactly the requested size, just move it to the + "used" list */ + if(free_range->nb_pages == nb_pages) + { + list_delete(kmem_free_range_list, free_range); + kmem_used_range_list = insert_range(kmem_used_range_list, + free_range); + /* The new_range is exactly the free_range */ + new_range = free_range; + } + + /* Otherwise the range is bigger than the requested size, split it. + This involves reducing its size, and allocate a new range, which + is going to be added to the "used" list */ + else + { + /* free_range split in { new_range | free_range } */ + new_range = (struct sos_kmem_range*) + sos_kmem_cache_alloc(kmem_range_cache, + (flags & SOS_KMEM_VMM_ATOMIC)? + SOS_KSLAB_ALLOC_ATOMIC:0); + if (! new_range) + return NULL; + + new_range->base_vaddr = free_range->base_vaddr; + new_range->nb_pages = nb_pages; + free_range->base_vaddr += nb_pages*SOS_PAGE_SIZE; + free_range->nb_pages -= nb_pages; + + /* free_range is still at the same place in the list */ + /* insert new_range in the used list */ + kmem_used_range_list = insert_range(kmem_used_range_list, + new_range); + } + + /* By default, the range is not associated with any slab */ + new_range->slab = NULL; + + /* If mapping of physical pages is needed, map them now */ + if (flags & SOS_KMEM_VMM_MAP) + { + unsigned int i; + for (i = 0 ; i < nb_pages ; i ++) + { + /* Get a new physical page */ + sos_paddr_t ppage_paddr + = sos_physmem_ref_physpage_new(! (flags & SOS_KMEM_VMM_ATOMIC)); + + /* Map the page in kernel space */ + if (ppage_paddr) + { + if (sos_paging_map(ppage_paddr, + new_range->base_vaddr + + i * SOS_PAGE_SIZE, + FALSE /* Not a user page */, + ((flags & SOS_KMEM_VMM_ATOMIC)? + SOS_VM_MAP_ATOMIC:0) + | SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE)) + { + /* Failed => force unallocation, see below */ + sos_physmem_unref_physpage(ppage_paddr); + ppage_paddr = (sos_paddr_t)NULL; + } + else + { + /* Success : page can be unreferenced since it is + now mapped */ + sos_physmem_unref_physpage(ppage_paddr); + } + } + + /* Undo the allocation if failed to allocate or map a new page */ + if (! ppage_paddr) + { + sos_kmem_vmm_del_range(new_range); + return NULL; + } + + /* Ok, set the range owner for this page */ + sos_physmem_set_kmem_range(ppage_paddr, new_range); + } + } + /* ... Otherwise: Demand Paging will do the job */ + + if (range_start) + *range_start = new_range->base_vaddr; + + return new_range; +} + + +sos_ret_t sos_kmem_vmm_del_range(struct sos_kmem_range *range) +{ + struct sos_kmem_range *ranges_to_free; + list_init(ranges_to_free); + + SOS_ASSERT_FATAL(range != NULL); + SOS_ASSERT_FATAL(range->slab == NULL); + + /* Remove the range from the 'USED' list now */ + list_delete(kmem_used_range_list, range); + + /* + * The following do..while() loop is here to avoid an indirect + * recursion: if we call directly kmem_cache_free() from inside the + * current function, we take the risk to re-enter the current function + * (sos_kmem_vmm_del_range()) again, which may cause problem if it + * in turn calls kmem_slab again and sos_kmem_vmm_del_range again, + * and again and again. This may happen while freeing ranges of + * struct sos_kslab... + * + * To avoid this,we choose to call a special function of kmem_slab + * doing almost the same as sos_kmem_cache_free(), but which does + * NOT call us (ie sos_kmem_vmm_del_range()): instead WE add the + * range that is to be freed to a list, and the do..while() loop is + * here to process this list ! The recursion is replaced by + * classical iterations. + */ + do + { + unsigned int i; + + /* Ok, we got the range. Now, insert this range in the free list */ + kmem_free_range_list = insert_range(kmem_free_range_list, range); + + /* Unmap the physical pages */ + for (i = 0 ; i < range->nb_pages ; i ++) + { + /* This will work even if no page is mapped at this address */ + sos_paging_unmap(range->base_vaddr + i*SOS_PAGE_SIZE); + } + + /* Eventually coalesce it with prev/next free ranges (there is + always a valid prev/next link since the list is circular). Note: + the tests below will lead to correct behaviour even if the list + is limited to the 'range' singleton, at least as long as the + range is not zero-sized */ + /* Merge with preceding one ? */ + if (range->prev->base_vaddr + range->prev->nb_pages*SOS_PAGE_SIZE + == range->base_vaddr) + { + struct sos_kmem_range *empty_range_of_ranges = NULL; + struct sos_kmem_range *prec_free = range->prev; + + /* Merge them */ + prec_free->nb_pages += range->nb_pages; + list_delete(kmem_free_range_list, range); + + /* Mark the range as free. This may cause the slab owning + the range to become empty */ + empty_range_of_ranges = + sos_kmem_cache_release_struct_range(range); + + /* If this causes the slab owning the range to become empty, + add the range corresponding to the slab at the end of the + list of the ranges to be freed: it will be actually freed + in one of the next iterations of the do{} loop. */ + if (empty_range_of_ranges != NULL) + { + list_delete(kmem_used_range_list, empty_range_of_ranges); + list_add_tail(ranges_to_free, empty_range_of_ranges); + } + + /* Set range to the beginning of this coelescion */ + range = prec_free; + } + + /* Merge with next one ? [NO 'else' since range may be the result of + the merge above] */ + if (range->base_vaddr + range->nb_pages*SOS_PAGE_SIZE + == range->next->base_vaddr) + { + struct sos_kmem_range *empty_range_of_ranges = NULL; + struct sos_kmem_range *next_range = range->next; + + /* Merge them */ + range->nb_pages += next_range->nb_pages; + list_delete(kmem_free_range_list, next_range); + + /* Mark the next_range as free. This may cause the slab + owning the next_range to become empty */ + empty_range_of_ranges = + sos_kmem_cache_release_struct_range(next_range); + + /* If this causes the slab owning the next_range to become + empty, add the range corresponding to the slab at the end + of the list of the ranges to be freed: it will be + actually freed in one of the next iterations of the + do{} loop. */ + if (empty_range_of_ranges != NULL) + { + list_delete(kmem_used_range_list, empty_range_of_ranges); + list_add_tail(ranges_to_free, empty_range_of_ranges); + } + } + + + /* If deleting the range(s) caused one or more range(s) to be + freed, get the next one to free */ + if (list_is_empty(ranges_to_free)) + range = NULL; /* No range left to free */ + else + range = list_pop_head(ranges_to_free); + + } + /* Stop when there is no range left to be freed for now */ + while (range != NULL); + + return SOS_OK; +} + + +sos_vaddr_t sos_kmem_vmm_alloc(sos_count_t nb_pages, + sos_ui32_t flags) +{ + struct sos_kmem_range *range + = sos_kmem_vmm_new_range(nb_pages, + flags, + NULL); + if (! range) + return (sos_vaddr_t)NULL; + + return range->base_vaddr; +} + + +sos_ret_t sos_kmem_vmm_free(sos_vaddr_t vaddr) +{ + struct sos_kmem_range *range = lookup_range(vaddr); + + /* We expect that the given address is the base address of the + range */ + if (!range || (range->base_vaddr != vaddr)) + return -SOS_EINVAL; + + /* We expect that this range is not held by any cache */ + if (range->slab != NULL) + return -SOS_EBUSY; + + return sos_kmem_vmm_del_range(range); +} + + +sos_ret_t sos_kmem_vmm_set_slab(struct sos_kmem_range *range, + struct sos_kslab *slab) +{ + if (! range) + return -SOS_EINVAL; + + range->slab = slab; + return SOS_OK; +} + +struct sos_kslab * sos_kmem_vmm_resolve_slab(sos_vaddr_t vaddr) +{ + struct sos_kmem_range *range = lookup_range(vaddr); + if (! range) + return NULL; + + return range->slab; +} + + +sos_bool_t sos_kmem_vmm_is_valid_vaddr(sos_vaddr_t vaddr) +{ + struct sos_kmem_range *range = lookup_range(vaddr); + return (range != NULL); +} diff --git a/sos/kmem_vmm.h b/sos/kmem_vmm.h new file mode 100644 index 0000000..ab391e9 --- /dev/null +++ b/sos/kmem_vmm.h @@ -0,0 +1,113 @@ +/* Copyright (C) 2000 Thomas Petazzoni + Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KMEM_VMM_H_ +#define _SOS_KMEM_VMM_H_ + +/** + * @file kmem_vmm.h + * + * Kernel Memory Allocator for multiple-page-sized objects residing in + * the kernel (virtual memory) space. Relies on the slab cache + * allocator to allocate its (internal) "range" data structure. + */ + +#include + +/* The base and top virtual addresses covered by the kernel allocator */ +#define SOS_KMEM_VMM_BASE SOS_PAGING_BASE_KERNEL_ADDRESS /* 16kB */ +#define SOS_KMEM_VMM_TOP (SOS_PAGING_UPPER_KERNEL_ADDRESS+1) /* 1GB - 4MB */ + +/** Opaque structure used internally and declared here for physmem.h */ +struct sos_kmem_range; + +#include + +/** + * Mark the areas belonging to SOS_KMEM_VMM_BASE and SOS_KMEM_VMM_TOP + * are either used or free. Those that are already mapped are marked + * as "used", and the 0..SOS_KMEM_VMM_BASE virtual addresses as marked + * as "used" too (to detect incorrect pointer dereferences). + */ +sos_ret_t +sos_kmem_vmm_subsystem_setup(sos_vaddr_t kernel_core_base_vaddr, + sos_vaddr_t kernel_core_top_vaddr, + sos_vaddr_t bootstrap_stack_bottom_vaddr, + sos_vaddr_t bootstrap_stack_top_vaddr); + + +/* + * Flags for kmem_vmm_new_range and kmem_vmm_alloc + */ +/** Physical pages should be immediately mapped */ +#define SOS_KMEM_VMM_MAP (1<<0) +/** Allocation should either success or fail, without blocking */ +#define SOS_KMEM_VMM_ATOMIC (1<<1) + +/** + * Allocate a new kernel area spanning one or multiple pages. + * + * @param range_base_vaddr If not NULL, the start address of the range + * is stored in this location + * @eturn a new range structure + */ +struct sos_kmem_range *sos_kmem_vmm_new_range(sos_size_t nb_pages, + sos_ui32_t flags, + sos_vaddr_t *range_base_vaddr); +sos_ret_t sos_kmem_vmm_del_range(struct sos_kmem_range *range); + + +/** + * Straighforward variant of sos_kmem_vmm_new_range() returning the + * range's start address instead of the range structure + */ +sos_vaddr_t sos_kmem_vmm_alloc(sos_size_t nb_pages, + sos_ui32_t flags); + +/** + * @note you are perfectly allowed to give the address of the + * kernel image, or the address of the bios area here, it will work: + * the kernel/bios WILL be "deallocated". But if you really want to do + * this, well..., do expect some "surprises" ;) + */ +sos_ret_t sos_kmem_vmm_free(sos_vaddr_t vaddr); + + +/** + * @return TRUE when vaddr is covered by any (used) kernel range + */ +sos_bool_t sos_kmem_vmm_is_valid_vaddr(sos_vaddr_t vaddr); + + +/* ***************************** + * Reserved to kmem_slab.c ONLY. + */ +/** + * Associate the range with the given slab. + */ +sos_ret_t sos_kmem_vmm_set_slab(struct sos_kmem_range *range, + struct sos_kslab *slab); + +/** + * Retrieve the (used) slab associated with the range covering vaddr. + * + * @return NULL if the range is not associated with a KMEM range + */ +struct sos_kslab *sos_kmem_vmm_resolve_slab(sos_vaddr_t vaddr); + +#endif /* _SOS_KMEM_VMM_H_ */ diff --git a/sos/ksynch.c b/sos/ksynch.c new file mode 100644 index 0000000..63f029d --- /dev/null +++ b/sos/ksynch.c @@ -0,0 +1,256 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + + +#include +#include + + +#include "ksynch.h" + + +sos_ret_t sos_ksema_init(struct sos_ksema *sema, const char *name, + int initial_value, + sos_kwaitq_ordering_t ordering) +{ + sema->value = initial_value; + return sos_kwaitq_init(& sema->kwaitq, name, ordering); +} + + +sos_ret_t sos_ksema_dispose(struct sos_ksema *sema) +{ + return sos_kwaitq_dispose(& sema->kwaitq); +} + + +sos_ret_t sos_ksema_down(struct sos_ksema *sema, + struct sos_time *timeout) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = SOS_OK; + + sema->value --; + if (sema->value < 0) + { + /* Wait for somebody to wake us */ + retval = sos_kwaitq_wait(& sema->kwaitq, timeout); + + /* Something wrong happened (timeout, external wakeup, ...) ? */ + if (SOS_OK != retval) + { + /* Yes: pretend we did not ask for the semaphore */ + sema->value ++; + } + } + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_ksema_trydown(struct sos_ksema *sema) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + /* Can we take the semaphore without blocking ? */ + if (sema->value >= 1) + { + /* Yes: we take it now */ + sema->value --; + retval = SOS_OK; + } + else + { + /* No: we signal it */ + retval = -SOS_EBUSY; + } + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_ksema_up(struct sos_ksema *sema) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + sema->value ++; + retval = sos_kwaitq_wakeup(& sema->kwaitq, 1, SOS_OK); + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_kmutex_init(struct sos_kmutex *mutex, const char *name, + sos_kwaitq_ordering_t ordering) +{ + mutex->owner = NULL; + return sos_kwaitq_init(& mutex->kwaitq, name, ordering); +} + + +sos_ret_t sos_kmutex_dispose(struct sos_kmutex *mutex) +{ + return sos_kwaitq_dispose(& mutex->kwaitq); +} + + +/* + * Implementation based on ownership transfer (ie no while() + * loop). The only assumption is that the thread awoken by + * kmutex_unlock is not suppressed before effectively waking up: in + * that case the mutex will be forever locked AND unlockable (by + * nobody other than the owner, but this is not natural since this + * owner already issued an unlock()...). The same problem happens with + * the semaphores, but in a less obvious manner. + */ +sos_ret_t sos_kmutex_lock(struct sos_kmutex *mutex, + struct sos_time *timeout) +{ + __label__ exit_kmutex_lock; + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = SOS_OK; + + /* Mutex already owned ? */ + if (NULL != mutex->owner) + { + /* Owned by us or by someone else ? */ + if (sos_thread_get_current() == mutex->owner) + { + /* Owned by us: do nothing */ + retval = -SOS_EBUSY; + goto exit_kmutex_lock; + } + + /* Wait for somebody to wake us */ + retval = sos_kwaitq_wait(& mutex->kwaitq, timeout); + + /* Something wrong happened ? */ + if (SOS_OK != retval) + { + goto exit_kmutex_lock; + } + } + + /* Ok, the mutex is available to us: take it */ + mutex->owner = sos_thread_get_current(); + + exit_kmutex_lock: + sos_restore_IRQs(flags); + return retval; +} + + +sos_bool_t sos_kmutex_owned_by_me(struct sos_kmutex const* mutex) +{ + sos_ui32_t flags; + sos_bool_t retval; + + sos_disable_IRQs(flags); + retval = (sos_thread_get_current() == mutex->owner); + sos_restore_IRQs(flags); + + return retval; +} + + +sos_ret_t sos_kmutex_trylock(struct sos_kmutex *mutex) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + /* Mutex available to us ? */ + if (NULL == mutex->owner) + { + /* Great ! Take it now */ + mutex->owner = sos_thread_get_current(); + + retval = SOS_OK; + } + else + { + /* No: signal it */ + retval = -SOS_EBUSY; + } + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_kmutex_unlock(struct sos_kmutex *mutex) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + if (sos_thread_get_current() != mutex->owner) + retval = -SOS_EPERM; + + else if (sos_kwaitq_is_empty(& mutex->kwaitq)) + { + /* + * There is NOT ANY thread waiting => we really mark the mutex + * as FREE + */ + mutex->owner = NULL; + retval = SOS_OK; + } + else + { + /* + * There is at least 1 thread waiting => we DO NOT mark the + * mutex as free ! + * Actually, we should have written: + * mutex->owner = thread_that_is_woken_up; + * But the real Id of the next thread owning the mutex is not + * that important. What is important here is that mutex->owner + * IS NOT NULL and does not correspond to any existing thread + * (address 0x43 is a good candidate because, in SOS, addresses + * below 4kB are never mapped in order to catch invalid + * pointers). Otherwise there will be a possibility for the + * thread woken up here to have the mutex stolen by a thread + * locking the mutex in the meantime. + */ +#define MUTEX_STILL_LOCKED ((struct sos_thread*) 0x43) + mutex->owner = MUTEX_STILL_LOCKED; + + /* We wake up ONE thread ONLY */ + retval = sos_kwaitq_wakeup(& mutex->kwaitq, 1, SOS_OK); + } + + sos_restore_IRQs(flags); + return retval; +} diff --git a/sos/ksynch.h b/sos/ksynch.h new file mode 100644 index 0000000..15339e3 --- /dev/null +++ b/sos/ksynch.h @@ -0,0 +1,178 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KSYNCH_H_ +#define _SOS_KSYNCH_H_ + + +/** + * @file synch.h + * + * Common kernel synchronisation primitives. + */ + + +#include +#include + + +/* ==================================================================== + * Kernel semaphores, NON-recursive + */ + + +/** + * The structure of a (NON-RECURSIVE) kernel Semaphore + */ +struct sos_ksema +{ + int value; + struct sos_kwaitq kwaitq; +}; + + +/* + * Initialize a kernel semaphore structure with the given name + * + * @param name Name of the semaphore (for debugging purpose only; safe + * [deep copied]) + * + * @param initial_value The value of the semaphore before any up/down + */ +sos_ret_t sos_ksema_init(struct sos_ksema *sema, const char *name, + int initial_value, + sos_kwaitq_ordering_t ordering); + + +/* + * De-initialize a kernel semaphore + * + * @return -SOS_EBUSY when semaphore could not be de-initialized + * because at least a thread is in the waitq. + */ +sos_ret_t sos_ksema_dispose(struct sos_ksema *sema); + + +/* + * Enters the semaphore + * + * @param timeout Maximum time to wait for the semaphore. Or NULL for + * "no limit". Updated on return to reflect the time remaining (0 when + * timeout has been triggered) + * + * @return -SOS_EINTR when timeout was triggered or when another waitq + * woke us up. + * + * @note This is a BLOCKING FUNCTION + */ +sos_ret_t sos_ksema_down(struct sos_ksema *sema, + struct sos_time *timeout); + + +/* + * Try to enter the semaphore without blocking. + * + * @return -SOS_EBUSY when locking the semaphore would block + */ +sos_ret_t sos_ksema_trydown(struct sos_ksema *sema); + + +/** + * Increments the semaphore's value, eventually waking up a thread + */ +sos_ret_t sos_ksema_up(struct sos_ksema *sema); + + + +/* ==================================================================== + * Kernel mutex (ie binary semaphore with strong ownership), + * NON-recursive ! + */ + + +/** + * The structure of a (NON-RECURSIVE) kernel Mutex + */ +struct sos_kmutex +{ + struct sos_thread *owner; + struct sos_kwaitq kwaitq; +}; + + +/* + * Initialize a kernel mutex structure with the given name + * + * @param name Name of the mutex (for debugging purpose only; safe + * [deep copied]) + * + * @param initial_value The value of the mutex before any up/down + */ +sos_ret_t sos_kmutex_init(struct sos_kmutex *mutex, const char *name, + sos_kwaitq_ordering_t ordering); + + +/* + * De-initialize a kernel mutex + * + * @return -SOS_EBUSY when mutex could not be de-initialized + * because at least a thread is in the waitq. + */ +sos_ret_t sos_kmutex_dispose(struct sos_kmutex *mutex); + + +/* + * Lock the mutex. If the same thread multiply locks the same mutex, + * it won't hurt (no deadlock, return value = -SOS_EBUSY). + * + * @param timeout Maximum time to wait for the mutex. Or NULL for "no + * limit". Updated on return to reflect the time remaining (0 when + * timeout has been triggered) + * + * @return -SOS_EINTR when timeout was triggered or when another waitq + * woke us up, -SOS_EBUSY when the thread already owns the mutex. + * + * @note This is a BLOCKING FUNCTION + */ +sos_ret_t sos_kmutex_lock(struct sos_kmutex *mutex, + struct sos_time *timeout); + + +/* + * Try to lock the mutex without blocking. + * + * @return -SOS_EBUSY when locking the mutex would block + */ +sos_ret_t sos_kmutex_trylock(struct sos_kmutex *mutex); + + +/** + * Predicate returns TRUE when the current thread owns the mutex + */ +sos_bool_t sos_kmutex_owned_by_me(struct sos_kmutex const* mutex); + + +/** + * Unlock the mutex, eventually waking up a thread + * + * @return -SOS_EPERM when the calling thread is NOT the owner of the + * mutex + */ +sos_ret_t sos_kmutex_unlock(struct sos_kmutex *mutex); + + +#endif /* _SOS_KSYNCH_H_ */ diff --git a/sos/kwaitq.c b/sos/kwaitq.c new file mode 100644 index 0000000..2b49a04 --- /dev/null +++ b/sos/kwaitq.c @@ -0,0 +1,321 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "kwaitq.h" + + +sos_ret_t sos_kwaitq_init(struct sos_kwaitq *kwq, + const char *name, + sos_kwaitq_ordering_t ordering) +{ + memset(kwq, 0x0, sizeof(struct sos_kwaitq)); + +#ifdef SOS_KWQ_DEBUG + if (! name) + name = ""; + strzcpy(kwq->name, name, SOS_KWQ_DEBUG_MAX_NAMELEN); +#endif + kwq->ordering = ordering; + list_init_named(kwq->waiting_list, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + + return SOS_OK; +} + + +sos_ret_t sos_kwaitq_dispose(struct sos_kwaitq *kwq) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + if (list_is_empty_named(kwq->waiting_list, + prev_entry_in_kwaitq, next_entry_in_kwaitq)) + retval = SOS_OK; + else + retval = -SOS_EBUSY; + + sos_restore_IRQs(flags); + return retval; +} + + +sos_bool_t sos_kwaitq_is_empty(const struct sos_kwaitq *kwq) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = list_is_empty_named(kwq->waiting_list, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_kwaitq_init_entry(struct sos_kwaitq_entry *kwq_entry) +{ + memset(kwq_entry, 0x0, sizeof(struct sos_kwaitq_entry)); + kwq_entry->thread = sos_thread_get_current(); + return SOS_OK; +} + + +/** Internal helper function equivalent to sos_kwaitq_add_entry(), but + without interrupt protection scheme */ +inline static sos_ret_t +_kwaitq_add_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry, + sos_sched_priority_t prio) +{ + struct sos_kwaitq_entry *next_entry = NULL, *entry; + int nb_entries; + + /* This entry is already added in the kwaitq ! */ + SOS_ASSERT_FATAL(NULL == kwq_entry->kwaitq); + + /* sos_kwaitq_init_entry() has not been called ?! */ + SOS_ASSERT_FATAL(NULL != kwq_entry->thread); + + /* (Re-)Initialize wakeup status of the entry */ + kwq_entry->wakeup_triggered = FALSE; + kwq_entry->wakeup_status = SOS_OK; + + /* Insert this entry in the kwaitq waiting list */ + switch (kwq->ordering) + { + case SOS_KWQ_ORDER_FIFO: + /* Insertion in the list in FIFO order */ + { + /* Add the thread in the list */ + list_add_tail_named(kwq->waiting_list, kwq_entry, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + } + break; + + case SOS_KWQ_ORDER_PRIO: + /* Priority-driven insertion in the list */ + { + /* Look for the place where to insert the thread in the queue (we + want to order them in order of increasing priorities) */ + list_foreach_forward_named(kwq->waiting_list, entry, nb_entries, + prev_entry_in_kwaitq, next_entry_in_kwaitq) + { + /* Does the thread we want to insert have higher priority than + the given thread in the queue ? */ + if (SOS_SCHED_PRIO_CMP(prio, + sos_thread_get_priority(entry->thread)) + > 0) + { + /* Yes: we insert before this given thread */ + next_entry = entry; + break; + } + } + + /* Actually insert the entry in the list */ + if (next_entry != NULL) + { + list_insert_before_named(kwq->waiting_list, kwq_entry, next_entry, + prev_entry_in_kwaitq, + next_entry_in_kwaitq); + } + else + { + /* The thread we want to insert has less priority than any + other in the list */ + list_add_tail_named(kwq->waiting_list, kwq_entry, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + } + } + break; + + default: + SOS_FATAL_ERROR("Invalid kwq ordering %d !\n", kwq->ordering); + break; + } + + /* Update the list of waitqueues for the thread */ + list_add_tail_named(kwq_entry->thread->kwaitq_list, kwq_entry, + prev_entry_for_thread, next_entry_for_thread); + + kwq_entry->kwaitq = kwq; + + return SOS_OK; +} + + +sos_ret_t sos_kwaitq_add_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = _kwaitq_add_entry(kwq, kwq_entry, + sos_thread_get_priority(kwq_entry->thread)); + sos_restore_IRQs(flags); + + return retval; +} + + +/** Internal helper function equivalent to sos_kwaitq_remove_entry(), + but without interrupt protection scheme */ +inline static sos_ret_t +_kwaitq_remove_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry) +{ + SOS_ASSERT_FATAL(kwq_entry->kwaitq == kwq); + + list_delete_named(kwq->waiting_list, kwq_entry, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + + list_delete_named(kwq_entry->thread->kwaitq_list, kwq_entry, + prev_entry_for_thread, next_entry_for_thread); + + kwq_entry->kwaitq = NULL; + return SOS_OK; +} + + +sos_ret_t sos_kwaitq_remove_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = _kwaitq_remove_entry(kwq, kwq_entry); + sos_restore_IRQs(flags); + + return retval; +} + + +sos_ret_t sos_kwaitq_wait(struct sos_kwaitq *kwq, + struct sos_time *timeout) +{ + sos_ui32_t flags; + sos_ret_t retval; + struct sos_kwaitq_entry kwq_entry; + + sos_kwaitq_init_entry(& kwq_entry); + + sos_disable_IRQs(flags); + + retval = _kwaitq_add_entry(kwq, & kwq_entry, + sos_thread_get_priority(kwq_entry.thread)); + + /* Wait for wakeup or timeout */ + sos_thread_sleep(timeout); + /* Woken up ! */ + + /* Sleep delay elapsed ? */ + if (! kwq_entry.wakeup_triggered) + { + /* Yes (timeout occured, or wakeup on another waitqueue): remove + the waitq entry by ourselves */ + _kwaitq_remove_entry(kwq, & kwq_entry); + retval = -SOS_EINTR; + } + else + { + retval = kwq_entry.wakeup_status; + } + + sos_restore_IRQs(flags); + + /* We were correctly awoken: position return status */ + return retval; +} + + +sos_ret_t sos_kwaitq_wakeup(struct sos_kwaitq *kwq, + unsigned int nb_threads, + sos_ret_t wakeup_status) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + + /* Wake up as much threads waiting in waitqueue as possible (up to + nb_threads), scanning the list in FIFO/decreasing priority order + (depends on the kwaitq ordering) */ + while (! list_is_empty_named(kwq->waiting_list, + prev_entry_in_kwaitq, next_entry_in_kwaitq)) + { + struct sos_kwaitq_entry *kwq_entry + = list_get_head_named(kwq->waiting_list, + prev_entry_in_kwaitq, next_entry_in_kwaitq); + + /* Enough threads woken up ? */ + if (nb_threads <= 0) + break; + + /* + * Ok: wake up the thread for this entry + */ + + /* Thread already woken up ? */ + if (SOS_THR_RUNNING == sos_thread_get_state(kwq_entry->thread)) + { + /* Yes => Do nothing because WE are that woken-up thread. In + particular: don't call set_ready() here because this + would result in an inconsistent configuration (currently + running thread marked as "waiting for CPU"...). */ + continue; + } + else + { + /* No => wake it up now. */ + sos_sched_set_ready(kwq_entry->thread); + } + + /* Remove this waitq entry */ + _kwaitq_remove_entry(kwq, kwq_entry); + kwq_entry->wakeup_triggered = TRUE; + kwq_entry->wakeup_status = wakeup_status; + + /* Next iteration... */ + nb_threads --; + } + + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +/* Internal function (callback for thread subsystem) */ +sos_ret_t sos_kwaitq_change_priority(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry, + sos_sched_priority_t priority) +{ + /* Reorder the waiting list */ + _kwaitq_remove_entry(kwq, kwq_entry); + _kwaitq_add_entry(kwq, kwq_entry, priority); + + return SOS_OK; +} diff --git a/sos/kwaitq.h b/sos/kwaitq.h new file mode 100644 index 0000000..9bc6c39 --- /dev/null +++ b/sos/kwaitq.h @@ -0,0 +1,216 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_KWAITQ_H_ +#define _SOS_KWAITQ_H_ + +#include +#include +#include + + +/** + * @kwaitq.h + * + * Low-level functions to manage queues of threads waiting for a + * resource. These functions are public, except + * sos_kwaitq_change_priority() that is a callback for the thread + * subsystem. However, for higher-level synchronization primitives + * such as mutex, semaphores, conditions, ... prefer to look at the + * corresponding libraries. + */ + + +/** + * Define this if you want to know the names of the kwaitq + */ +// #define SOS_KWQ_DEBUG + + +/* Forward declaration */ +struct sos_kwaitq_entry; + + +/** + * The threads in the kwaitqs can be ordered in FIFO or in decreasing + * priority order. + */ +typedef enum { SOS_KWQ_ORDER_FIFO, SOS_KWQ_ORDER_PRIO } sos_kwaitq_ordering_t; + + +#include + + +/** + * Definition of a waitqueue. In a kwaitq, the threads can be ordererd + * either in FIFO order (SOS_KWQ_ORDER_FIFO) or in decreasing priority + * order (SOS_KWQ_ORDER_PRIO ; with FIFO ordering for same-prio + * threads). + * + * A more efficient method to store the threads ordered by their + * priority would have been to use 1 list for each priority level. But + * we have to be careful to the size of a kwaitq structure here: + * potentially there are thousands of kwaitq in a running system + * (basically: 1 per opened file !). The algorithm we use to order the + * threads in the kwaitq in this case is highly under-optimal (naive + * linear insertion): as an exercise, one can implement a more + * efficient algorithm (think of a heap). + */ +struct sos_kwaitq +{ +#ifdef SOS_KWQ_DEBUG +# define SOS_KWQ_DEBUG_MAX_NAMELEN 32 + char name[SOS_KWQ_DEBUG_MAX_NAMELEN]; +#endif + sos_kwaitq_ordering_t ordering; + struct sos_kwaitq_entry *waiting_list; +}; + + +/** + * Definition of an entry for a thread waiting in the waitqueue + */ +struct sos_kwaitq_entry +{ + /** The thread associted with this entry */ + struct sos_thread *thread; + + /** The kwaitqueue this entry belongs to */ + struct sos_kwaitq *kwaitq; + + /** TRUE when somebody woke up this entry */ + sos_bool_t wakeup_triggered; + + /** The status of wakeup for this entry. @see wakeup_status argument + of sos_kwaitq_wakeup() */ + sos_ret_t wakeup_status; + + /** Other entries in this kwaitqueue */ + struct sos_kwaitq_entry *prev_entry_in_kwaitq, *next_entry_in_kwaitq; + + /** Other entries for the thread */ + struct sos_kwaitq_entry *prev_entry_for_thread, *next_entry_for_thread; +}; + + +/** + * Initialize an empty waitqueue. + * + * @param name Used only if SOS_KWQ_DEBUG is defined (safe [deep + * copied]) + */ +sos_ret_t sos_kwaitq_init(struct sos_kwaitq *kwq, + const char *name, + sos_kwaitq_ordering_t ordering); + + +/** + * Release a waitqueue, making sure that no thread is in it. + * + * @return -SOS_EBUSY in case a thread is still in the waitqueue. + */ +sos_ret_t sos_kwaitq_dispose(struct sos_kwaitq *kwq); + + +/** + * Return whether there are no threads in the waitq + */ +sos_bool_t sos_kwaitq_is_empty(const struct sos_kwaitq *kwq); + + +/** + * Initialize a waitqueue entry. Mainly consists in updating the + * "thread" field of the entry (set to current running thread), and + * initializing the remaining of the entry as to indicate it does not + * belong to any waitq. + */ +sos_ret_t sos_kwaitq_init_entry(struct sos_kwaitq_entry *kwq_entry); + + +/** + * Add an entry (previously initialized with sos_kwaitq_init_entry()) + * in the given waitqueue. + * + * @note: No state change/context switch can occur here ! Among other + * things: the current executing thread is not preempted. + */ +sos_ret_t sos_kwaitq_add_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry); + + +/** + * Remove the given kwaitq_entry from the kwaitq. + * + * @note: No state change/context switch can occur here ! Among other + * things: the thread associated with the entry is not necessarilly + * the same as the one currently running, and does not preempt the + * current running thread if they are different. + */ +sos_ret_t sos_kwaitq_remove_entry(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry); + + +/** + * Helper function to make the current running thread block in the + * given kwaitq, waiting to be woken up by somedy else or by the given + * timeout. It calls the sos_kwaitq_add_entry() and + * sos_kwaitq_remove_entry(). + * + * @param timeout The desired timeout (can be NULL => wait for + * ever). It is updated by the function to reflect the remaining + * timeout in case the thread has been woken-up prior to its + * expiration. + * + * @return -SOS_EINTR when the thread is resumed while it has not be + * explicitely woken up by someone calling sos_kwaitq_wakeup() upon + * the same waitqueue... This can only happen 1/ if the timeout + * expired, or 2/ if the current thread is also in another kwaitq + * different to "kwq". Otherwise return the value set by + * sos_kwaitq_wakeup(). The timeout remaining is updated in timeout. + * + * @note This is a BLOCKING FUNCTION + */ +sos_ret_t sos_kwaitq_wait(struct sos_kwaitq *kwq, + struct sos_time *timeout); + + +/** + * Wake up as much as nb_thread threads (SOS_KWQ_WAKEUP_ALL to wake + * up all threads) in the kwaitq kwq, in FIFO or decreasing priority + * order (depends on the ordering scheme selected at kwaitq + * initialization time). + * + * @param wakeup_status The value returned by sos_kwaitq_wait() when + * the thread will effectively woken up due to this wakeup. + */ +sos_ret_t sos_kwaitq_wakeup(struct sos_kwaitq *kwq, + unsigned int nb_threads, + sos_ret_t wakeup_status); +#define SOS_KWQ_WAKEUP_ALL (~((unsigned int)0)) + + +/** + * @note INTERNAL function (in particular: interrupts not disabled) ! + * + * @note: The use of this function is RESERVED (to thread.c). Do not + * call it directly: use sos_thread_set_priority() for that ! + */ +sos_ret_t sos_kwaitq_change_priority(struct sos_kwaitq *kwq, + struct sos_kwaitq_entry *kwq_entry, + sos_sched_priority_t priority); + +#endif /* _SOS_KWAITQ_H_ */ diff --git a/sos/list.h b/sos/list.h new file mode 100644 index 0000000..1f4b4d1 --- /dev/null +++ b/sos/list.h @@ -0,0 +1,194 @@ +/* Copyright (C) 2001 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_LIST_H_ +#define _SOS_LIST_H_ + +/** + * @file list.h + * + * Circular doubly-linked lists implementation entirely based on C + * macros + */ + + +/* *_named are used when next and prev links are not exactly next + and prev. For instance when we have next_in_team, prev_in_team, + prev_global and next_global */ + +#define list_init_named(list,prev,next) \ + ((list) = NULL) + +#define list_singleton_named(list,item,prev,next) ({ \ + (item)->next = (item)->prev = (item); \ + (list) = (item); \ +}) + +#define list_is_empty_named(list,prev,next) \ + ((list) == NULL) + +#define list_is_singleton_named(list,prev,next) \ + ( ((list) != NULL) && ((list)->prev == (list)->next) && ((list) == (list)->next) ) + +#define list_get_head_named(list,prev,next) \ + (list) + +#define list_get_tail_named(list,prev,next) \ + ((list)?((list)->prev):NULL) + +/* Internal macro : insert before the head == insert at tail */ +#define __list_insert_atleft_named(before_this,item,prev,next) ({ \ + (before_this)->prev->next = (item); \ + (item)->prev = (before_this)->prev; \ + (before_this)->prev = (item); \ + (item)->next = (before_this); \ +}) + +/* @note Before_this and item are expected to be valid ! */ +#define list_insert_before_named(list,before_this,item,prev,next) ({ \ + __list_insert_atleft_named(before_this,item,prev,next); \ + if ((list) == (before_this)) (list) = (item); \ +}) + +/** @note After_this and item are expected to be valid ! */ +#define list_insert_after_named(list,after_this,item,prev,next) ({ \ + (after_this)->next->prev = (item); \ + (item)->next = (after_this)->next; \ + (after_this)->next = (item); \ + (item)->prev = (after_this); \ +}) + +#define list_add_head_named(list,item,prev,next) ({ \ + if (list) \ + list_insert_before_named(list,list,item,prev,next); \ + else \ + list_singleton_named(list,item,prev,next); \ + (list) = (item); \ +}) + +#define list_add_tail_named(list,item,prev,next) ({ \ + if (list) \ + __list_insert_atleft_named(list,item,prev,next); \ + else \ + list_singleton_named(list,item,prev,next); \ +}) + +/** @note NO check whether item really is in list ! */ +#define list_delete_named(list,item,prev,next) ({ \ + if ( ((item)->next == (item)) && ((item)->prev == (item)) ) \ + (item)->next = (item)->prev = (list) = NULL; \ + else { \ + (item)->prev->next = (item)->next; \ + (item)->next->prev = (item)->prev; \ + if ((item) == (list)) (list) = (item)->next; \ + (item)->prev = (item)->next = NULL; \ + } \ +}) + +#define list_pop_head_named(list,prev,next) ({ \ + typeof(list) __ret_elt = (list); \ + list_delete_named(list,__ret_elt,prev,next); \ + __ret_elt; }) + +/** Loop statement that iterates through all of its elements, from + head to tail */ +#define list_foreach_forward_named(list,iterator,nb_elements,prev,next) \ + for (nb_elements=0, (iterator) = (list) ; \ + (iterator) && (!nb_elements || ((iterator) != (list))) ; \ + nb_elements++, (iterator) = (iterator)->next ) + +/** Loop statement that iterates through all of its elements, from + tail back to head */ +#define list_foreach_backward_named(list,iterator,nb_elements,prev,next) \ + for (nb_elements=0, (iterator) = list_get_tail_named(list,prev,next) ; \ + (iterator) && (!nb_elements || \ + ((iterator) != list_get_tail_named(list,prev,next))) ; \ + nb_elements++, (iterator) = (iterator)->prev ) + +#define list_foreach_named list_foreach_forward_named + +/** True when we exitted early from the foreach loop (ie break) */ +#define list_foreach_early_break(list,iterator,nb_elements) \ + ((list) && ( \ + ((list) != (iterator)) || \ + ( ((list) == (iterator)) && (nb_elements == 0)) )) + +/** Loop statement that also removes the item at each iteration. The + body of the loop is allowed to delete the iterator element from + memory. */ +#define list_collapse_named(list,iterator,prev,next) \ + for ( ; ({ ((iterator) = (list)) ; \ + if (list) list_delete_named(list,iterator,prev,next) ; \ + (iterator); }) ; ) + + +/* + * the same macros : assume that the prev and next fields are really + * named "prev" and "next" + */ + +#define list_init(list) \ + list_init_named(list,prev,next) + +#define list_singleton(list,item) \ + list_singleton_named(list,item,prev,next) + +#define list_is_empty(list) \ + list_is_empty_named(list,prev,next) + +#define list_is_singleton(list) \ + list_is_singleton_named(list,prev,next) + +#define list_get_head(list) \ + list_get_head_named(list,prev,next) \ + +#define list_get_tail(list) \ + list_get_tail_named(list,prev,next) \ + +/* @note Before_this and item are expected to be valid ! */ +#define list_insert_after(list,after_this,item) \ + list_insert_after_named(list,after_this,item,prev,next) + +/* @note After_this and item are expected to be valid ! */ +#define list_insert_before(list,before_this,item) \ + list_insert_before_named(list,before_this,item,prev,next) + +#define list_add_head(list,item) \ + list_add_head_named(list,item,prev,next) + +#define list_add_tail(list,item) \ + list_add_tail_named(list,item,prev,next) + +/* @note NO check whether item really is in list ! */ +#define list_delete(list,item) \ + list_delete_named(list,item,prev,next) + +#define list_pop_head(list) \ + list_pop_head_named(list,prev,next) + +#define list_foreach_forward(list,iterator,nb_elements) \ + list_foreach_forward_named(list,iterator,nb_elements,prev,next) + +#define list_foreach_backward(list,iterator,nb_elements) \ + list_foreach_backward_named(list,iterator,nb_elements,prev,next) + +#define list_foreach list_foreach_forward + +#define list_collapse(list,iterator) \ + list_collapse_named(list,iterator,prev,next) + +#endif /* _SOS_LIST_H_ */ diff --git a/sos/macros.h b/sos/macros.h new file mode 100644 index 0000000..29a8848 --- /dev/null +++ b/sos/macros.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2004 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_MACROS_H_ +#define _SOS_MACROS_H_ + +/** Align on a boundary (MUST be a power of 2), so that return value <= val */ +#define SOS_ALIGN_INF(val,boundary) \ + (((unsigned)(val)) & (~((boundary)-1))) + +/** Align on a boundary (MUST be a power of 2), so that return value >= val */ +#define SOS_ALIGN_SUP(val,boundary) \ + ({ unsigned int __bnd=(boundary); \ + (((((unsigned)(val))-1) & (~(__bnd - 1))) + __bnd); }) + +/** Check whether val is aligned on a boundary (MUST be a power of 2) */ +#define SOS_IS_ALIGNED(val,boundary) \ + ( 0 == (((unsigned)(val)) & ((boundary)-1)) ) + +/** + * @return TRUE if val is a power of 2. + * @note val is evaluated multiple times + */ +#define SOS_IS_POWER_OF_2(val) \ + ((((val) - 1) & (val)) == 0) + +/** + * Standard macro to get the offset of the given field in a structure + */ +#define offsetof(type,field) \ + ((unsigned) &(((type *)0)->field)) + +#endif /* _SOS_MACROS_H_ */ diff --git a/sos/main.c b/sos/main.c new file mode 100644 index 0000000..eb97bcf --- /dev/null +++ b/sos/main.c @@ -0,0 +1,640 @@ +/* Copyright (C) 2004 The SOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/* Include definitions of the multiboot standard */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Helper function to display each bits of a 32bits integer on the + screen as dark or light carrets */ +void display_bits(unsigned char row, unsigned char col, + unsigned char attribute, + sos_ui32_t integer) +{ + int i; + /* Scan each bit of the integer, MSb first */ + for (i = 31 ; i >= 0 ; i--) + { + /* Test if bit i of 'integer' is set */ + int bit_i = (integer & (1 << i)); + /* Ascii 219 => dark carret, Ascii 177 => light carret */ + unsigned char ascii_code = bit_i?219:177; + sos_x86_videomem_putchar(row, col++, + attribute, + ascii_code); + } +} + +/* Clock IRQ handler */ +static void clk_it(int intid) +{ + static sos_ui32_t clock_count = 0; + + display_bits(0, 48, + SOS_X86_VIDEO_FG_LTGREEN | SOS_X86_VIDEO_BG_BLUE, + clock_count); + clock_count++; + + /* Execute the expired timeout actions (if any) */ + sos_time_do_tick(); + + /* Update scheduler statistics and status */ + sos_sched_do_timer_tick(); +} + + +/* ====================================================================== + * Page fault exception handling + */ + + +/* Page fault exception handler with demand paging for the kernel */ +static void pgflt_ex(int intid, struct sos_cpu_state *ctxt) +{ + static sos_ui32_t demand_paging_count = 0; + struct sos_thread * cur_thr = sos_thread_get_current(); + sos_vaddr_t faulting_vaddr = sos_cpu_context_get_EX_faulting_vaddr(ctxt); + sos_paddr_t ppage_paddr; + + if (sos_cpu_context_is_in_user_mode(ctxt) + || (cur_thr->fixup_uaccess.return_vaddr)) + { + __label__ unforce_address_space; + sos_bool_t need_to_prepare_user_space_access; + sos_ui32_t errcode = sos_cpu_context_get_EX_info(ctxt); + + /* Do we already try to access an address space ? */ + need_to_prepare_user_space_access + = (sos_thread_get_current()->squatted_address_space == NULL); + + if (need_to_prepare_user_space_access) + /* No: Need to configure the interrupted thread user space */ + sos_thread_prepare_user_space_access(NULL, 0); + + if (SOS_OK == + sos_umem_vmm_try_resolve_page_fault(faulting_vaddr, + errcode & (1 << 1), + TRUE)) + goto unforce_address_space; + + /* If the page fault occured in kernel mode, return to kernel to + the fixup address */ + if (! sos_cpu_context_is_in_user_mode(ctxt)) + { + cur_thr->fixup_uaccess.faulted_uaddr = faulting_vaddr; + sos_cpu_context_set_EX_return_address(ctxt, + cur_thr->fixup_uaccess.return_vaddr); + goto unforce_address_space; + } + + if (need_to_prepare_user_space_access) + sos_thread_end_user_space_access(); + + sos_bochs_printf("\e[35mTHR %p: Unresolved USER page Fault at instruction 0x%x on access to address 0x%x (info=%x)!\e[m\n", + (void*)sos_thread_get_current(), + sos_cpu_context_get_PC(ctxt), + (unsigned)faulting_vaddr, + (unsigned)sos_cpu_context_get_EX_info(ctxt)); + sos_bochs_printf("Terminating User thread\n"); + sos_thread_exit(); + + unforce_address_space: + if (need_to_prepare_user_space_access) + sos_thread_end_user_space_access(); + return; + } + + /* Check if address is covered by any VMM range */ + if (! sos_kmem_vmm_is_valid_vaddr(faulting_vaddr)) + { + /* No: The page fault is out of any kernel virtual region. For + the moment, we don't handle this. */ + sos_display_fatal_error("Unresolved page Fault at instruction 0x%x on access to address 0x%x (info=%x)!", + sos_cpu_context_get_PC(ctxt), + (unsigned)faulting_vaddr, + (unsigned)sos_cpu_context_get_EX_info(ctxt)); + SOS_ASSERT_FATAL(! "Got page fault (note: demand paging is disabled)"); + } + + + /* + * Demand paging in kernel space + */ + + /* Update the number of demand paging requests handled */ + demand_paging_count ++; + display_bits(0, 0, + SOS_X86_VIDEO_FG_LTRED | SOS_X86_VIDEO_BG_BLUE, + demand_paging_count); + + /* Allocate a new page for the virtual address */ + ppage_paddr = sos_physmem_ref_physpage_new(FALSE); + if (! ppage_paddr) + SOS_ASSERT_FATAL(! "TODO: implement swap. (Out of mem in demand paging because no swap for kernel yet !)"); + SOS_ASSERT_FATAL(SOS_OK == sos_paging_map(ppage_paddr, + SOS_PAGE_ALIGN_INF(faulting_vaddr), + FALSE, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_ATOMIC)); + sos_physmem_unref_physpage(ppage_paddr); + + /* Ok, we can now return to interrupted context */ +} + + + +/* ====================================================================== + * An operating system MUST always have a ready thread ! Otherwise: + * what would the CPU have to execute ?! + */ +static void idle_thread(void* unused) __attribute__((noreturn)); +static void idle_thread(void* unused) +{ + sos_ui32_t idle_twiddle = 0; + + while (1) + { + /* Remove this instruction if you get an "Invalid opcode" CPU + exception (old 80386 CPU) */ + asm("hlt\n"); + + idle_twiddle ++; + display_bits(0, 0, SOS_X86_VIDEO_FG_GREEN | SOS_X86_VIDEO_BG_BLUE, + idle_twiddle); + + /* Lend the CPU to some other thread */ + sos_thread_yield(); + } +} + + +/* ====================================================================== + * Kernel thread showing some CPU usage statistics on the console every 1s + */ +#define LOAD_DISPLAY_BASELINE 4 +#define LOAD_DISPLAY_STARTROW 34 +static void stat_thread(void * unused) __attribute__((noreturn)); +static void stat_thread(void * unused) +{ + while (1) + { + sos_ui32_t flags; + sos_ui32_t load1, load5, load15; + char str1[11], str5[11], str15[11]; + struct sos_time t; + t.sec = 1; + t.nanosec = 0; + + sos_thread_sleep(& t); + + sos_disable_IRQs(flags); + + /* The IDLE task is EXcluded in the following computation */ + sos_load_get_sload(&load1, &load5, &load15); + sos_load_to_string(str1, load1); + sos_load_to_string(str5, load5); + sos_load_to_string(str15, load15); + sos_x86_videomem_printf(LOAD_DISPLAY_BASELINE+0, LOAD_DISPLAY_STARTROW, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Kernel (- Idle): %s %s %s ", + str1, str5, str15); + + sos_load_get_uload(&load1, &load5, &load15); + sos_load_to_string(str1, load1); + sos_load_to_string(str5, load5); + sos_load_to_string(str15, load15); + sos_x86_videomem_printf(LOAD_DISPLAY_BASELINE+1, LOAD_DISPLAY_STARTROW, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "User: %s %s %s ", + str1, str5, str15); + + sos_load_get_uratio(&load1, &load5, &load15); + sos_load_to_string(str1, load1); + sos_load_to_string(str5, load5); + sos_load_to_string(str15, load15); + sos_x86_videomem_printf(LOAD_DISPLAY_BASELINE+2, LOAD_DISPLAY_STARTROW, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "User CPU %%: %s %s %s ", + str1, str5, str15); + + /* The IDLE task is INcluded in the following computation */ + sos_load_get_sratio(&load1, &load5, &load15); + sos_load_to_string(str1, load1); + sos_load_to_string(str5, load5); + sos_load_to_string(str15, load15); + sos_x86_videomem_printf(LOAD_DISPLAY_BASELINE+3, LOAD_DISPLAY_STARTROW, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Kernel CPU %% (+ Idle): %s %s %s ", + str1, str5, str15); + sos_restore_IRQs(flags); + } +} + + +/* ====================================================================== + * Start the "init" (userland) process + */ +static sos_ret_t +start_init(struct sos_fs_manager_instance * rootfs) +{ + sos_ret_t retval; + struct sos_umem_vmm_as *as_init; + struct sos_process *proc_init; + struct sos_thread *new_thr; + sos_uaddr_t ustack, start_uaddr; + int fake_args[32]; /* Must be large enough to contain argv/envp for init */ + sos_ret_t ret; + struct sos_fs_opened_file * init_root, * init_cwd, * unused_of; + + /* Create the new process */ + proc_init = sos_process_create("init", FALSE); + if (! proc_init) + return -SOS_ENOMEM; + as_init = sos_process_get_address_space(proc_init); + + + /* + * Setup the root and CWD directories of the process. The root of + * this process will correspond to the "global" root of the whole + * system since all the future processes will duplicate it ! + */ + retval = sos_fs_new_opened_file(proc_init, rootfs->root, + SOS_FS_OPEN_READ | SOS_FS_OPEN_WRITE, + & init_root); + if (SOS_OK != retval) + { + sos_process_unref(proc_init); + return -SOS_ENOENT; + } + + /* Duplicate the root file to set the current working directory of + the init process */ + retval = sos_fs_duplicate_opened_file(init_root, proc_init, + & init_cwd); + if (SOS_OK != retval) + { + sos_fs_close(init_root); + sos_process_unref(proc_init); + return -SOS_ENOENT; + } + + /* Now update the process ! */ + if ( ( SOS_OK != sos_process_chroot(proc_init, init_root, & unused_of) ) + || ( SOS_OK != sos_process_chdir(proc_init, init_cwd, & unused_of) ) ) + { + sos_fs_close(init_root); + sos_fs_close(init_cwd); + sos_process_chroot(proc_init, NULL, & unused_of); + sos_process_chdir(proc_init, NULL, & unused_of); + sos_process_unref(proc_init); + return -SOS_ENOENT; + } + + + /* Map the 'init' program in user space */ + start_uaddr = sos_binfmt_elf32_map(as_init, "init"); + if (0 == start_uaddr) + { + sos_process_unref(proc_init); + return -SOS_ENOENT; + } + + /* Allocate the user stack */ + ustack = (SOS_PAGING_UPPER_USER_ADDRESS - SOS_DEFAULT_USER_STACK_SIZE) + 1; + retval = sos_dev_zero_map(as_init, &ustack, SOS_DEFAULT_USER_STACK_SIZE, + SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE, + /* PRIVATE */ 0); + if (SOS_OK != retval) + { + sos_process_unref(proc_init); + return -SOS_ENOMEM; + } + + /* Compute the address of the stack that will be used to initialize + the user thread */ + ustack = SOS_ALIGN_INF((ustack + SOS_DEFAULT_USER_STACK_SIZE + - sizeof(fake_args)), 4); + + /* Build fake argv/envp arguments for the init process. See + userland/crt.c for the format. */ + fake_args[0] = 0x1; /* argc */ + fake_args[1] = 4 * sizeof(fake_args[0]); /* offset of argv[0] */ + fake_args[2] = 0x0; /* delimiter between argv and envp */ + fake_args[3] = 0x0; /* end of envp */ + strzcpy ((char *) & fake_args[4], /* argv[0] contents */ + "init", 5); + + /* Copy the arguments to the user thread stack */ + ret = sos_memcpy_to_specified_userspace (as_init, + ustack, + (sos_vaddr_t) fake_args, + sizeof(fake_args)); + if (sizeof(fake_args) != ret) + { + sos_bochs_printf ("sos_memcpy_to_specified_userspace() failed, returned %d\n", ret); + sos_process_unref(proc_init); + return ret; + } + + /* Now create the user thread */ + new_thr = sos_create_user_thread(NULL, + proc_init, + start_uaddr, + 0, 0, + ustack, + SOS_SCHED_PRIO_TS_LOWEST); + if (! new_thr) + { + sos_process_unref(proc_init); + return -SOS_ENOMEM; + } + + sos_process_unref(proc_init); + return SOS_OK; +} + + + +/* ====================================================================== + * The C entry point of our operating system + */ +void sos_main(unsigned long magic, unsigned long arg) + __attribute__((noreturn)); +void sos_main(unsigned long magic, unsigned long arg) +{ + sos_paddr_t sos_kernel_core_base_paddr, sos_kernel_core_top_paddr; + struct sos_time tick_resolution; + struct sos_fs_manager_instance * rootfs; + + /* Size of RAM above 1MB. Might be undefined ! */ + unsigned long int upper_mem = 0; + + /* Setup bochs and console, and clear the console */ + sos_bochs_subsystem_setup(); + + sos_x86_videomem_setup(); + sos_x86_videomem_cls(SOS_X86_VIDEO_BG_BLUE); + + /* Greetings from SOS */ + if (magic == MULTIBOOT_BOOTLOADER_MAGIC) + { + /* Grub sends us a structure, called multiboot_info_t with a lot of + precious informations about the system, see the multiboot + documentation for more information. */ + multiboot_info_t *mbi = (multiboot_info_t *) arg; + + /* Multiboot says: "The value returned for upper memory is + maximally the address of the first upper memory hole minus 1 + megabyte.". It also adds: "It is not guaranteed to be this + value." aka "YMMV" ;) */ + upper_mem = mbi->mem_upper; + sos_x86_videomem_printf(1, 0, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Welcome From GRUB to %s%c RAM is %dMB (upper mem = 0x%x kB)", + "SOS article 10", ',', + (unsigned)(upper_mem >> 10) + 1, + (unsigned)upper_mem); + } + else if (magic == 0x42244224) + { + /* Loaded with SOS bootsect */ + upper_mem = arg; + sos_x86_videomem_printf(1, 0, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Welcome to %s%c RAM is %dMB (upper mem = 0x%x kB)", + "SOS article 10", ',', + (unsigned)(upper_mem >> 10) + 1, + (unsigned)upper_mem); + } + else + /* Not loaded with grub, not from an enhanced bootsect */ + sos_x86_videomem_printf(1, 0, + SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Welcome to SOS article 10"); + + sos_bochs_putstring("Message in a bochs: This is SOS article 10.\n"); + + /* Setup CPU segmentation and IRQ subsystem */ + sos_gdt_subsystem_setup(); + sos_idt_subsystem_setup(); + + /* Setup SOS IRQs and exceptions subsystem */ + sos_exception_subsystem_setup(); + sos_irq_subsystem_setup(); + + /* Configure the timer so as to raise the IRQ0 at a 100Hz rate */ + sos_i8254_set_frequency(100); + + /* Setup the kernel time subsystem to get prepared to take the timer + ticks into account */ + tick_resolution = (struct sos_time) { .sec=0, .nanosec=10000000UL }; + sos_time_subsysem_setup(& tick_resolution); + + /* We need to know the RAM size */ + if (upper_mem == 0) + { + sos_x86_videomem_putstring(20, 0, + SOS_X86_VIDEO_FG_LTRED + | SOS_X86_VIDEO_BG_BLUE + | SOS_X86_VIDEO_FG_BLINKING, + "I don't know RAM size ! Load me with Grub..."); + /* STOP ! */ + for (;;) + continue; + } + + /* + * Some interrupt handlers + */ + + /* Binding some HW interrupts and exceptions to software routines */ + sos_irq_set_routine(SOS_IRQ_TIMER, + clk_it); + + /* + * Setup physical memory management + */ + + SOS_ASSERT_FATAL(SOS_OK + == sos_physmem_subsystem_setup((upper_mem<<10) + (1<<20), + &sos_kernel_core_base_paddr, + &sos_kernel_core_top_paddr)); + + /* + * Switch to paged-memory mode + */ + + /* Disabling interrupts should seem more correct, but it's not really + necessary at this stage */ + SOS_ASSERT_FATAL(SOS_OK == + sos_paging_subsystem_setup(sos_kernel_core_base_paddr, + sos_kernel_core_top_paddr)); + + /* Bind the page fault exception */ + sos_exception_set_routine(SOS_EXCEPT_PAGE_FAULT, + pgflt_ex); + + /* + * Setup kernel virtual memory allocator + */ + + if (sos_kmem_vmm_subsystem_setup(sos_kernel_core_base_paddr, + sos_kernel_core_top_paddr, + bootstrap_stack_bottom, + bootstrap_stack_bottom + + bootstrap_stack_size)) + sos_bochs_printf("Could not setup the Kernel virtual space allocator\n"); + + if (sos_kmalloc_subsystem_setup()) + sos_bochs_printf("Could not setup the Kmalloc subsystem\n"); + + /* + * Initialize the MMU context subsystem + */ + sos_mm_context_subsystem_setup(); + + /* + * Initialize the CPU context subsystem + */ + sos_cpu_context_subsystem_setup(); + + /* + * Bind the syscall handler to its software interrupt handler + */ + sos_swintr_subsystem_setup(); + + + /* + * Initialize the Kernel thread and scheduler subsystems + */ + + /* Initialize kernel thread subsystem */ + sos_thread_subsystem_setup(bootstrap_stack_bottom, + bootstrap_stack_size); + + /* Initialize the scheduler */ + sos_sched_subsystem_setup(); + + /* Declare the IDLE thread */ + SOS_ASSERT_FATAL(sos_create_kernel_thread("idle", idle_thread, NULL, + SOS_SCHED_PRIO_TS_LOWEST) != NULL); + + /* Prepare the stats subsystem */ + sos_load_subsystem_setup(); + + /* Declare a thread that prints some stats */ + SOS_ASSERT_FATAL(sos_create_kernel_thread("stat_thread", stat_thread, + NULL, + SOS_SCHED_PRIO_TS_LOWEST) != NULL); + + + /* + * Initialise user address space management subsystem + */ + sos_umem_vmm_subsystem_setup(); + sos_dev_zero_subsystem_setup(); + + /* Initialize the page and block cache subsystems */ + sos_fs_pagecache_subsystem_setup(); + sos_blockdev_subsystem_setup(); + sos_dev_mem_chardev_setup(); + + /* + * Initialize process stuff + */ + sos_process_subsystem_setup(); + + + /* Enabling the HW interrupts here, this will make the timer HW + interrupt call the scheduler */ + asm volatile ("sti\n"); + + + SOS_ASSERT_FATAL(SOS_OK == sos_fs_virtfs_subsystem_setup()); + SOS_ASSERT_FATAL(SOS_OK == sos_fs_fat_subsystem_setup()); + SOS_ASSERT_FATAL(SOS_OK == sos_fs_subsystem_setup(NULL, + "virtfs", + NULL, + & rootfs)); + + + tty_subsystem_setup(); + sos_ttyS0_subsystem_setup(); + sos_console_subsystem_setup(); + + + sos_ide_subsystem_setup(); + + + + /* Start the 'init' process, which in turns launches the other + programs */ + start_init(rootfs); + /* + * We can safely exit from this function now, for there is already + * an idle Kernel thread ready to make the CPU busy working... + * + * However, we must EXPLICITELY call sos_thread_exit() because a + * simple "return" will return nowhere ! Actually this first thread + * was initialized by the Grub bootstrap stage, at a time when the + * word "thread" did not exist. This means that the stack was not + * setup in order for a return here to call sos_thread_exit() + * automagically. Hence we must call it manually. This is the ONLY + * kernel thread where we must do this manually. + */ + sos_bochs_printf("Bye from primary thread !\n"); + sos_thread_exit(); + SOS_FATAL_ERROR("No trespassing !"); +} diff --git a/sos/mouse_sim.c b/sos/mouse_sim.c new file mode 100644 index 0000000..736effe --- /dev/null +++ b/sos/mouse_sim.c @@ -0,0 +1,804 @@ +/*************************************************************************** + * Copyright (C) 2004 by cyril dupuit * + * cyrildupuit@hotmail.com * + * http://perso.wanadoo.fr/koalys/ * + * (Adaptation for SOS by d2 -- 2004/12/20) * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +//***************************************************************************** +// Nom du module : MouseSim.c +// Description : Creation et destruction de souris mangeuse de fromages +//***************************************************************************** + +#include +#include +#include +#include +#include +#include + +// Historique : +// 20/12/04 : Suppr DestroyMap et suppr handler kbd dans version LM (d2) +// 26/11/04 : Bug trouve et resolu dans la fonction DestroyMap +// 21/11/04 : Creation du module V1.0 + +//***************************************************************************** +// Definition des equivalences : +//***************************************************************************** +#define MAP_X 76 +#define MAP_Y 12 +#define MAP_SIZE MAP_X * MAP_Y + +#define MOUSE 0x01 +#define CHEESE 0x02 +#define OBSTACLE 0x04 +#define INPUT 0x08 +#define OUTPUT 0x10 + +#define OBSTACLE_COUNT 100 +#define CHEESE_COUNT 650 + +#define MOUSE_FULL 0x01 +#define MOUSE_EMPTY 0x02 +#define CHEESE_FOUND 0x04 +#define MOUSE_EXITED 0x08 + +#define MOUSE_SPEED_MAX 1000 +#define MOUSE_SPEED_MIN 4 + +typedef unsigned int Color_t; + +struct Point{ + int X; + int Y; + }; + +typedef struct Point Point_t; + +#define Set(Reg, Flag) Reg = (Reg | Flag) +#define Reset(Reg, Flag) Reg = (Reg &(~Flag)) +#define IsSet(Reg, Flag) (Reg & Flag) + + +//***************************************************************************** +// Structure de gestion d'un element +//***************************************************************************** +struct Element{ + sos_ui32_t Type;//Type d'element + sos_ui32_t Status; + Color_t Color;//Couleur de l'element + Point_t P;//Coordonnees de l'element + struct sos_thread * ThreadID;//Thread associe a la souris + int Way;//Direction de la souris + }; + +typedef struct Element Element_t; + +//***************************************************************************** +// Prototypes des fonctions/procedures : +//***************************************************************************** +static void DrawMap(void); +static sos_ret_t CreateMap(void); +static sos_ret_t InitMapInput(Element_t * * pMap); +static sos_ret_t InitMapOutput(Element_t * * pMap); +static sos_ret_t ElementInit(Element_t * * pMap, unsigned int Type); +static void Mouse(unsigned long Param); +static void MouseMove(Point_t * P); +static Point_t ChoosePosition(Element_t * pMouse, int Positions[], int Count); +static int EvaluatePositions(Point_t Org, int Positions[], Point_t * Cheese); +static sos_bool_t IsCollision(Point_t Org, Point_t p, Point_t *Cheese); +static sos_bool_t AffectMovement(Point_t Org, Point_t p); +static void MouseCreator(void); +static sos_ret_t CreateMouse(void); + +//***************************************************************************** +// Variables globales de ce module : +//***************************************************************************** + +static Element_t * * pMap; +static struct sos_ksema SemMap; +static struct sos_ksema SemMouse; +static int MouseCount = 0; +static int CheeseCount = 0; +static int ObstacleCount = 0; +static int MouseSpeed = 100; + +//***************************************************************************** +// Koalys Glue +//***************************************************************************** +void DrawPixel(int x, int y, Color_t color) +{ + sos_x86_videomem_putchar(y+3, x+2, color, 219); +} + + + +//***************************************************************************** +// Point d'entre de la 'simulation' +//***************************************************************************** +void MouseSim(void) +{ + //Creation du semaphore de protection de la carte + SOS_ASSERT_FATAL(SOS_OK == sos_ksema_init(& SemMap, "SemMap", 1, + SOS_KWQ_ORDER_FIFO)); + + //Creation du semaphore de creation de souris + SOS_ASSERT_FATAL(SOS_OK == sos_ksema_init(& SemMouse, "SemMouse", 2, + SOS_KWQ_ORDER_FIFO)); + + //Creation de la carte + SOS_ASSERT_FATAL(SOS_OK == CreateMap()); + + //Creation du thread createur de souris + SOS_ASSERT_FATAL(sos_create_kernel_thread("MouseCreator", + (sos_kernel_thread_start_routine_t)MouseCreator, + 0, SOS_SCHED_PRIO_TS_LOWEST-1) != NULL); + +} + + +//***************************************************************************** +// But de la fonction : Creer et initialiser la carte +// Entree : Aucune +// Parametre retourne : ERROR si la memoire est insuffisante, TRUE sinon +//***************************************************************************** +static sos_ret_t CreateMap(void) +{ + pMap = (Element_t * *)sos_kmalloc(MAP_SIZE * sizeof(Element_t *), 0); + if(pMap == NULL) return -SOS_ENOMEM; + + //Mettre la carte a 0 + memset(pMap, 0, MAP_SIZE * sizeof(Element_t *)); + + //Initialisation de l'entree de la carte + if(SOS_OK != InitMapInput(pMap)) + {//Memoire insuffisante + return -SOS_EFATAL; + } + + //Initialisation de la sortie de la carte + if(InitMapOutput(pMap) != SOS_OK) + {//Memoire insuffisante + return -SOS_EFATAL; + } + + //Initialisation du fromage + if(ElementInit(pMap, CHEESE) != SOS_OK) + {//Memoire insuffisante + return -SOS_EFATAL; + } + + //Initialisation des obstacles + if(ElementInit(pMap, OBSTACLE) != SOS_OK) + {//Memoire insuffisante + return -SOS_EFATAL; + } + + DrawMap();//Afficher la carte creee + + return SOS_OK; +} + +//***************************************************************************** +// But de la procedure : Dessiner la carte a l'ecran +// Entree : Aucune +// Sortie : Aucune +//***************************************************************************** +static void DrawMap(void) +{ + unsigned int I; + + for(I = 0; I < MAP_SIZE; I++) + { + if(pMap[I] != NULL) + { + DrawPixel(I % MAP_X, I/MAP_X, pMap[I]->Color); + } + else DrawPixel(I % MAP_X, I/MAP_X, SOS_X86_VIDEO_FG_BLACK); + } + sos_x86_videomem_printf(23, 0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Souris = %d; Fromages = %d; Obstacles = %d ", + MouseCount, CheeseCount, ObstacleCount); +} + +//***************************************************************************** +// But de la fonction : Initialiser l'entree de la carte +// Entree : +// pMap : Pointeur sur la carte +// Parametre retourne : ERROR si memoire insuffisante, TRUE sinon +//***************************************************************************** +static sos_ret_t InitMapInput(Element_t * * pMap) +{ + Element_t * pElement; + + //Definir le point d'entree + pElement = (Element_t *)sos_kmalloc(sizeof(Element_t), 0); + if(pElement == NULL) return -SOS_ENOMEM; + + //Initialiser l'entree + pElement->Type = INPUT; + pElement->Status = 0; + pElement->Color = SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE; + pElement->P.X = 0; + pElement->P.Y = MAP_Y / 2; + pElement->ThreadID = 0; + + pMap[(pElement->P.Y * MAP_X) + pElement->P.X] = pElement; + + return SOS_OK; +} + +//***************************************************************************** +// But de la fonction : Initialiser la sortie de la carte +// Entree : +// pMap : Pointeur sur la carte +// Parametre retourne : ERROR si memoire insuffisante, TRUE sinon +//***************************************************************************** +static sos_ret_t InitMapOutput(Element_t * * pMap) +{ + Element_t * pElement; + + //Definir le point de sortie + pElement = (Element_t *)sos_kmalloc(sizeof(Element_t), 0); + if(pElement == NULL) return -SOS_ENOMEM; + + //Initialiser l'entree + pElement->Type = OUTPUT; + pElement->Status = 0; + pElement->Color = SOS_X86_VIDEO_FG_LTBLUE; + pElement->P.X = MAP_X - 1; + pElement->P.Y = MAP_Y / 2; + pElement->ThreadID = 0; + + pMap[(pElement->P.Y * MAP_X) + pElement->P.X] = pElement; + + return SOS_OK; +} + +//***************************************************************************** +// But de la fonction : Initialiser un type d'objet sur la carte +// Entree : +// pMap : Pointeur sur la carte +// Type : Type d'objet a initialiser +// Parametre retourne : ERROR si memoire insuffisante, TRUE sinon +//***************************************************************************** +static sos_ret_t ElementInit(Element_t * * pMap, unsigned int Type) +{ + unsigned int I, J; + unsigned int Max; + Color_t Color; + + if(Type == CHEESE) + {//Type d'element = fromage + Max = CHEESE_COUNT; + Color = SOS_X86_VIDEO_FG_YELLOW; + } + else if(Type == OBSTACLE) + {//Type d'element = Obstacle + Max = OBSTACLE_COUNT; + Color = SOS_X86_VIDEO_FG_GREEN; + } + else + {//Aucune autre type reconnu + return -SOS_EINVAL; + } + + for(I = 0; I < Max; I++) + {//Tirer les fromages + J = random(); + J += random(); + J %= MAP_SIZE; + if(pMap[J] == NULL) + {//Si l'emplacement est libre + pMap[J] = (Element_t *)sos_kmalloc(sizeof(Element_t), + 0); + if(pMap[J] == NULL) return -SOS_ENOMEM; + + pMap[J]->Type = Type; + //Initialiser l'element + if(Type == CHEESE) + {//Type d'element = fromage + CheeseCount++; + } + else if(Type == OBSTACLE) + {//Type d'element = Obstacle + ObstacleCount++; + } + + pMap[J]->Color = Color; + pMap[J]->Status = 0; + pMap[J]->Color = Color; + pMap[J]->P.X = J % MAP_X; + pMap[J]->P.Y = J / MAP_X; + pMap[J]->ThreadID = 0; + } + } + + return SOS_OK; +} + + +//***************************************************************************** +// But du thread : Deplacer la souris sur la carte selon les regles etablies. +// Regles : +// - La souris doit se placer devant l'entree puis commence a recolter du +// fromage. +// - Des que la souris a ramasse un morceau de fromage, elle doit aller en +// entree de la carte afin de deposer sa recolte. +// - Si une souris a prouve sa recolte, une autre souris est creee. +// - Si une souris prend la sortie, elle est eliminee. +//***************************************************************************** +static void Mouse(unsigned long Param) +{ + Element_t * pMouse = (Element_t *)Param; + Point_t P; + + SOS_ASSERT_FATAL(pMouse != NULL); + + //Position de depart de la souris + P = pMouse->P; + P = pMouse->P; + + while(1) + { + int delay_ms; + struct sos_time delay; + + //La souris doit se deplacer + sos_ksema_down(& SemMap, NULL); + + MouseMove(&P); + + sos_ksema_up(& SemMap); + + // Est-ce que la souris est sortie ? + if (IsSet(pMouse->Status, MOUSE_EXITED)) + // Oui => on sort + break; + + // Delai entre MOUSE_SPEED_MIN et MouseSpeed - 1 + delay_ms = MOUSE_SPEED_MIN + (random() % MouseSpeed); + delay.sec = delay_ms / 1000; + delay.nanosec = (delay_ms % 1000) * 1000000; + sos_thread_sleep(& delay); + } + + // Libere la structure associee + sos_kfree((sos_vaddr_t)pMouse); +} + +//***************************************************************************** +// But de la procedure : Deplacer la souris de maniere aleatoire sur la carte +// Entrees : +// P : Position courante de la souris +// Sorties : +// P : Position suivante de la souris +//***************************************************************************** +static void MouseMove(Point_t * P) +{ + Point_t Org; + Point_t p; + Point_t Cheese; + int Positions[8]; + int Count = 0; + Element_t * pMouse; + + Org = *P; + + pMouse = pMap[Org.X + (Org.Y * MAP_X)]; + + Count = EvaluatePositions(Org, Positions, &Cheese); + + if(Count == 0) return; + + p = Org; + + if(IsSet(pMouse->Status, CHEESE_FOUND)) + {//Prendre le fromage + Reset(pMouse->Status, CHEESE_FOUND); + p = Cheese; + } + else + {//Choisir une position au hasard + p = ChoosePosition(pMouse, Positions, Count); + } + if(AffectMovement(Org, p) == FALSE) return; + //Deplacer la souris + pMap[Org.X + (Org.Y * MAP_X)] = NULL; + pMap[p.X + (p.Y * MAP_X)] = pMouse; + pMouse->P = p; + //Mettre a jour l'affichage + DrawPixel(Org.X, Org.Y, SOS_X86_VIDEO_FG_BLACK); + DrawPixel(p.X, p.Y, pMouse->Color); + sos_x86_videomem_printf( 23,0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, "Souris = %d; Fromages = %d; Obstacles = %d ", MouseCount, CheeseCount, ObstacleCount); + //Mettre a jour les coordonnees + *P = p; +} + +//***************************************************************************** +// But de la fonction : Choisir un mouvement +// Entree : +// pMouse : Pointeur sur la souris +// Positions : Tableau de position possible +// Count :Nombre de positions valides +// Sortie : Aucune +// Parametre retourne : Position choisie +//***************************************************************************** +static Point_t ChoosePosition(Element_t * pMouse, int Positions[], int Count) +{ + int I, J; + Point_t p; + + for(J = 0; J < Count; J++) + {//Chercher dans le tableau si cette position est disponible + I = Positions[J]; + if(I == pMouse->Way) + {//Poursuivre ce sens d'avance + p = pMouse->P; + switch(I) + { + case 0: + p.Y++; + break; + case 1: + p.X++; + p.Y++; + break; + case 2: + p.X++; + break; + case 3: + p.Y--; + p.X++; + break; + case 4: + p.Y--; + break; + case 5: + p.Y--; + p.X--; + break; + case 6: + p.X--; + break; + case 7: + p.X--; + p.Y++; + break; + } + return p; + } + } + + J = random() % Count; + I = Positions[J]; + if(((I + 4) % 8) == pMouse->Way) + {//Eviter le sens inverse + J = (J + 1) % Count; + I = Positions[J]; + } + + p = pMouse->P; + switch(I) + {//Repere le deplacement + case 0: + p.Y++; + break; + case 1: + p.X++; + p.Y++; + break; + case 2: + p.X++; + break; + case 3: + p.Y--; + p.X++; + break; + case 4: + p.Y--; + break; + case 5: + p.Y--; + p.X--; + break; + case 6: + p.X--; + break; + case 7: + p.X--; + p.Y++; + break; + } + + pMouse->Way = I;//Memoriser la direction selectionnee + + return p; +} + +//***************************************************************************** +// But de la fonction : Evaluer les positions possibles et les memoriser dans +// un tableau de positions si aucun fromage n'a ete detecte. Si du fromage a +// ete detecte, il sera selectionne en premier. La presence d'un fromage est +// indiquee par le drapeau CHEESE_FOUND +// Entree : +// Org : Position de la souris +// Sorties : +// Positions : Tableau de positions valides +// Cheese : Position du fromage +// Parametre retourne : Nombre de positions valides +//***************************************************************************** +static int EvaluatePositions(Point_t Org, int Positions[], Point_t * Cheese) +{ + int I; + int Count = 0; + Point_t p; + Point_t CheesePos; + + for(I = 0; I < 8; I++) + {//Explorer toute les directions + p = Org; + switch(I) + {//Repere le deplacement + case 0: + p.Y++; + break; + case 1: + p.X++; + p.Y++; + break; + case 2: + p.X++; + break; + case 3: + p.Y--; + p.X++; + break; + case 4: + p.Y--; + break; + case 5: + p.Y--; + p.X--; + break; + case 6: + p.X--; + break; + case 7: + p.X--; + p.Y++; + break; + } + //Tester la collision + if(IsCollision(Org, p, &CheesePos) == FALSE) + {//La souris n'a rencontre aucun obstacle + Positions[Count] = I; + Count++; + } + } + + *Cheese = CheesePos; + + return Count; +} + +//***************************************************************************** +// But de la fonction : Affecter un mouvement a la souris +// Entrees : +// Org : Coordonnees de la souris +// p : Coordonnees voulu par la souris +// Parametre retourne : TRUE si le mouvement a eu lieu, FALSE sinon +//***************************************************************************** +static sos_bool_t AffectMovement(Point_t Org, Point_t p) +{ + Element_t * pMouse = pMap[Org.X + (Org.Y * MAP_X)]; + Element_t * pElement; + + pElement = pMap[p.X + (p.Y * MAP_X)]; + + //La place est libre + if(pElement == NULL) return TRUE;//Autoriser le mouvement + + switch(pElement->Type) + { + case CHEESE: + // Liberer l'emplacement memoire du fromage + sos_kfree((sos_vaddr_t)pElement); + pMap[p.X + (p.Y * MAP_X)] = NULL; + + //Donner le fromage a la souris + Set(pMouse->Status, MOUSE_FULL); + Reset(pMouse->Status, MOUSE_EMPTY); + pMouse->Color = SOS_X86_VIDEO_FG_MAGENTA; + CheeseCount--; + return TRUE; + case OUTPUT: + //Supprimer la souris + pMap[Org.X + (Org.Y * MAP_X)] = NULL; + MouseCount--; + DrawPixel(Org.X, Org.Y, SOS_X86_VIDEO_FG_BLACK); + sos_x86_videomem_printf( 23,0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, + "Souris = %d; Fromages = %d; Obstacles = %d ", + MouseCount, CheeseCount, + ObstacleCount); + Set(pMouse->Status, MOUSE_EXITED); + return FALSE; + default : + return FALSE; + } + + return FALSE; +} + +//***************************************************************************** +// But de la fonction : Tester si une collision a eu lieu avec un obstacle +// Entrees : +// Org : Coordonnees de la souris +// p : coordonnees desirees par la souris +// Sortie : +// Cheese : Coordonnees du fromage +// Parametre retourne : TRUE si une collision a eu lieu, FALSE sinon +//***************************************************************************** +static sos_bool_t IsCollision(Point_t Org, Point_t p, Point_t *Cheese) +{ + Element_t * pMouse = pMap[Org.X + (Org.Y * MAP_X)]; + Element_t * pElement; + + //Tester les bordures de la map + if((p.X < 0)||(p.Y < 0)) return TRUE; + + if((p.Y >= MAP_Y)||(p.X >= MAP_X)) return TRUE; + + pElement = pMap[p.X + (p.Y * MAP_X)]; + + //L'element est vide + if(pElement == NULL) return FALSE; + + //Si du fromage a ete trouve, stopper la recherche + if(IsSet(pMouse->Status, CHEESE_FOUND)) return FALSE; + + switch(pElement->Type) + { + case CHEESE: + if(IsSet(pMouse->Status, MOUSE_FULL)) return TRUE; + //Indiquer que du fromage a ete trouve + Set(pMouse->Status, CHEESE_FOUND); + //Retenir la position du fromage + (*Cheese).X = p.X; + (*Cheese).Y = p.Y; + break; + case INPUT: + if(IsSet(pMouse->Status, MOUSE_EMPTY)) return TRUE; + //Remplir les reserves de fromage + Set(pMouse->Status, MOUSE_EMPTY); + Reset(pMouse->Status, MOUSE_FULL); + pMouse->Color = SOS_X86_VIDEO_FG_LTRED; + //Autoriser la creation d'une autre souris + sos_ksema_up(& SemMouse); + return TRUE; + case OUTPUT: + break; + default : + return TRUE; + } + + return FALSE;//Aucune collision +} + +//***************************************************************************** +// But du thread : Creer une souris et la placer autour de l'entree +//***************************************************************************** +static void MouseCreator(void) +{ + while(1) + { + sos_ksema_down(& SemMouse, NULL); + sos_ksema_down(& SemMap, NULL); + CreateMouse(); + sos_ksema_up(& SemMap); + } +} + +//***************************************************************************** +// But de la fonction : Creer une souris et l'inserer dans la carte +// Entree : Aucune +// Parametre retourne : ERROR si memoire insuffisante, FALSE si souris non +// cree, TRUE sinon +//***************************************************************************** +static sos_ret_t CreateMouse(void) +{ + Element_t * pElement; + unsigned int I; + + Point_t p; + + for(I = 0; I < 8; I++) + {//Explorer tous les emplacements + p.X = 0; + p.Y = MAP_Y / 2; + switch(I) + {//Repere le deplacement + case 0: + p.Y++; + break; + case 1: + p.X++; + p.Y++; + break; + case 2: + p.X++; + break; + case 3: + p.Y--; + p.X++; + break; + case 4: + p.Y--; + break; + case 5: + p.Y--; + p.X--; + break; + case 6: + p.X--; + break; + case 7: + p.X--; + p.Y++; + break; + } + if((p.X >= 0)&&(p.Y >= 0)&&(p.X < MAP_X)&&(p.Y < MAP_Y)) + {//L'emplacement est valide + pElement = pMap[p.X + (p.Y * MAP_X)]; + if(pElement == NULL) + {//Creer la souris + pElement = (Element_t *)sos_kmalloc(sizeof(Element_t), 0); + if(pElement != NULL) + {//Initialiser l'entree + pElement->Type = MOUSE; + Set(pElement->Status, MOUSE_EMPTY); + pElement->Color = SOS_X86_VIDEO_FG_LTRED; + pElement->P = p; + pElement->Way = 0; + pElement->ThreadID + = sos_create_kernel_thread("Mouse", + (sos_kernel_thread_start_routine_t)Mouse, + pElement, SOS_SCHED_PRIO_TS_LOWEST-1); + if(pElement->ThreadID == 0) + { + sos_kfree((sos_vaddr_t)pElement); + pElement = NULL; + return -SOS_ENOMEM; + } + pMap[p.X + (p.Y * MAP_X)] = pElement; + MouseCount++; + + DrawPixel(p.X, p.Y, pElement->Color); + sos_x86_videomem_printf(23, 0, SOS_X86_VIDEO_FG_YELLOW | SOS_X86_VIDEO_BG_BLUE, "Souris = %d; Fromages = %d; Obstacles = %d ", MouseCount, CheeseCount, ObstacleCount); + + return SOS_OK; + } + } + } + } + return -SOS_EBUSY; +} + +//***************************************************************************** +// C'est fini !!!! +//***************************************************************************** diff --git a/sos/physmem.c b/sos/physmem.c new file mode 100644 index 0000000..5026211 --- /dev/null +++ b/sos/physmem.c @@ -0,0 +1,387 @@ +/* Copyright (C) 2004 David Decotigny + Copyright (C) 2000 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include +#include +#include + +#include "physmem.h" + +/** A descriptor for a physical page in SOS */ +struct physical_page_descr +{ + /** The physical base address for the page */ + sos_paddr_t paddr; + + /** The reference count for this physical page. > 0 means that the + page is in the nonfree list. */ + sos_count_t ref_cnt; + + /** + * An additional counter for user-defined use. The management of this + * counter is up to the user, however a simple set of rules applies: + * - it can only be incremented/decremented if the page is referenced + * - when it reaches 0, no automatic action is taken + * The first rule means in particular that a page whose reference + * count reaches 0 (=> will be freed) cannot have a custom_count + * value > 0 ! The system will be HALTED if this ever happens + */ + sos_count_t occupation_cnt; + + + /** Some data associated with the page when it is mapped in kernel space */ + struct sos_kmem_range *kernel_range; + + /** The other pages on the list (nonfree, free) */ + struct physical_page_descr *prev, *next; +}; + +/** These are some markers present in the executable file (see sos.lds) */ +extern char __b_kernel, __e_kernel; + +/** The array of ppage descriptors will be located at this address */ +#define PAGE_DESCR_ARRAY_ADDR \ + SOS_PAGE_ALIGN_SUP((sos_paddr_t) (& __e_kernel)) +static struct physical_page_descr * physical_page_descr_array; + +/** The list of physical pages currently available */ +static struct physical_page_descr *free_ppage; + +/** The list of physical pages currently allocated */ +static struct physical_page_descr *nonfree_ppage; + +/** We will store here the interval of valid physical addresses */ +static sos_paddr_t physmem_base, physmem_top; + +/** We store the number of pages nonfree/free */ +static sos_count_t physmem_total_pages, physmem_nonfree_pages; + +sos_ret_t sos_physmem_subsystem_setup(sos_size_t ram_size, + /* out */sos_paddr_t *kernel_core_base, + /* out */sos_paddr_t *kernel_core_top) +{ + /* The iterator over the page descriptors */ + struct physical_page_descr *ppage_descr; + + /* The iterator over the physical addresses */ + sos_paddr_t ppage_addr; + + /* Make sure ram size is aligned on a page boundary */ + ram_size = SOS_PAGE_ALIGN_INF(ram_size);/* Yes, we may lose at most a page */ + + /* Reset the nonfree/free page lists before building them */ + free_ppage = nonfree_ppage = NULL; + physmem_total_pages = physmem_nonfree_pages = 0; + + /* Make sure that there is enough memory to store the array of page + descriptors */ + *kernel_core_base = SOS_PAGE_ALIGN_INF((sos_paddr_t)(& __b_kernel)); + *kernel_core_top + = PAGE_DESCR_ARRAY_ADDR + + SOS_PAGE_ALIGN_SUP( (ram_size >> SOS_PAGE_SHIFT) + * sizeof(struct physical_page_descr)); + if (*kernel_core_top > ram_size) + return -SOS_ENOMEM; + + /* Page 0-4kB is not available in order to return address 0 as a + means to signal "no page available" */ + physmem_base = SOS_PAGE_SIZE; + physmem_top = ram_size; + + /* Setup the page descriptor arrray */ + physical_page_descr_array + = (struct physical_page_descr*)PAGE_DESCR_ARRAY_ADDR; + + /* Scan the list of physical pages */ + for (ppage_addr = 0, + ppage_descr = physical_page_descr_array ; + ppage_addr < physmem_top ; + ppage_addr += SOS_PAGE_SIZE, + ppage_descr ++) + { + enum { PPAGE_MARK_RESERVED, PPAGE_MARK_FREE, + PPAGE_MARK_KERNEL, PPAGE_MARK_HWMAP } todo; + + memset(ppage_descr, 0x0, sizeof(struct physical_page_descr)); + + /* Init the page descriptor for this page */ + ppage_descr->paddr = ppage_addr; + + /* Reserved : 0 ... base */ + if (ppage_addr < physmem_base) + todo = PPAGE_MARK_RESERVED; + + /* Free : base ... BIOS */ + else if ((ppage_addr >= physmem_base) + && (ppage_addr < BIOS_N_VIDEO_START)) + todo = PPAGE_MARK_FREE; + + /* Used : BIOS */ + else if ((ppage_addr >= BIOS_N_VIDEO_START) + && (ppage_addr < BIOS_N_VIDEO_END)) + todo = PPAGE_MARK_HWMAP; + + /* Free : BIOS ... kernel */ + else if ((ppage_addr >= BIOS_N_VIDEO_END) + && (ppage_addr < (sos_paddr_t) (& __b_kernel))) + todo = PPAGE_MARK_FREE; + + /* Used : Kernel code/data/bss + physcal page descr array */ + else if ((ppage_addr >= *kernel_core_base) + && (ppage_addr < *kernel_core_top)) + todo = PPAGE_MARK_KERNEL; + + /* Free : first page of descr ... end of RAM */ + else + todo = PPAGE_MARK_FREE; + + /* Actually does the insertion in the nonfree/free page lists */ + physmem_total_pages ++; + switch (todo) + { + case PPAGE_MARK_FREE: + ppage_descr->ref_cnt = 0; + list_add_head(free_ppage, ppage_descr); + break; + + case PPAGE_MARK_KERNEL: + case PPAGE_MARK_HWMAP: + ppage_descr->ref_cnt = 1; + list_add_head(nonfree_ppage, ppage_descr); + physmem_nonfree_pages ++; + break; + + default: + /* Reserved page: nop */ + break; + } + } + + return SOS_OK; +} + + +sos_paddr_t sos_physmem_ref_physpage_new(sos_bool_t can_block) +{ + struct physical_page_descr *ppage_descr; + + if (! free_ppage) + return (sos_paddr_t)NULL; + + /* Retrieve a page in the free list */ + ppage_descr = list_pop_head(free_ppage); + + /* The page is assumed not to be already in the nonfree list */ + SOS_ASSERT_FATAL(ppage_descr->ref_cnt == 0); + + /* Mark the page as nonfree (this of course sets the ref count to 1) */ + ppage_descr->ref_cnt ++; + + /* The page descriptor should be unmodified since previous + deallocation. Otherwise this means that something unauthorized + overwrote the page descriptor table contents ! */ + SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0); + SOS_ASSERT_FATAL(ppage_descr->kernel_range == NULL); + + /* Put the page in the nonfree list */ + list_add_tail(nonfree_ppage, ppage_descr); + physmem_nonfree_pages ++; + + return ppage_descr->paddr; +} + + +/** + * Helper function to get the physical page descriptor for the given + * physical page address. + * + * @return NULL when out-of-bounds or non-page-aligned + */ +inline static struct physical_page_descr * +get_page_descr_at_paddr(sos_paddr_t ppage_paddr) +{ + /* Don't handle non-page-aligned addresses */ + if (ppage_paddr & SOS_PAGE_MASK) + return NULL; + + /* Don't support out-of-bounds requests */ + if ((ppage_paddr < physmem_base) || (ppage_paddr >= physmem_top)) + return NULL; + + return physical_page_descr_array + (ppage_paddr >> SOS_PAGE_SHIFT); +} + + +sos_ret_t sos_physmem_ref_physpage_at(sos_paddr_t ppage_paddr) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + /* Increment the reference count for the page */ + ppage_descr->ref_cnt ++; + + /* If the page is newly referenced (ie we are the only owners of the + page => ref cnt == 1), transfer it in the nonfree pages list */ + if (ppage_descr->ref_cnt == 1) + { + /* The page descriptor should be unmodified since previous + deallocation. Otherwise this means that something unauthorized + overwrote the page descriptor table contents ! */ + SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0); + SOS_ASSERT_FATAL(ppage_descr->kernel_range == NULL); + + list_delete(free_ppage, ppage_descr); + list_add_tail(nonfree_ppage, ppage_descr); + physmem_nonfree_pages ++; + + /* The page is newly referenced */ + return FALSE; + } + + /* The page was already referenced by someone */ + return TRUE; +} + + +sos_ret_t +sos_physmem_unref_physpage(sos_paddr_t ppage_paddr) +{ + /* By default the return value indicates that the page is still + used */ + sos_ret_t retval = FALSE; + + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + /* Don't do anything if the page is not in the nonfree list */ + if (ppage_descr->ref_cnt <= 0) + return -SOS_EINVAL; + + /* Unreference the page, and, when no mapping is active anymore, put + the page in the free list */ + ppage_descr->ref_cnt--; + if (ppage_descr->ref_cnt == 0) + { + /* Make sure that the occupation counter is set to 0 */ + SOS_ASSERT_FATAL(ppage_descr->occupation_cnt == 0); + + /* Reset associated kernel range */ + ppage_descr->kernel_range = NULL; + + /* Transfer the page, considered NON-FREE, to the free list */ + list_delete(nonfree_ppage, ppage_descr); + physmem_nonfree_pages --; + + list_add_head(free_ppage, ppage_descr); + + /* Indicate that the page is now unreferenced */ + retval = TRUE; + } + + /* The page was already referenced by someone */ + return retval; +} + + +sos_ret_t sos_physmem_get_physpage_refcount(sos_paddr_t ppage_paddr) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + return ppage_descr->ref_cnt; +} + + +sos_ret_t sos_physmem_inc_physpage_occupation(sos_paddr_t ppage_paddr) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + /* Don't do anything if the page is not in the nonfree list */ + SOS_ASSERT_FATAL(ppage_descr->ref_cnt > 0); + + ppage_descr->occupation_cnt ++; + return (ppage_descr->occupation_cnt > 1); +} + + +sos_ret_t sos_physmem_dec_physpage_occupation(sos_paddr_t ppage_paddr) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + /* Don't do anything if the page is not in the nonfree list */ + SOS_ASSERT_FATAL(ppage_descr->ref_cnt > 0); + SOS_ASSERT_FATAL(ppage_descr->occupation_cnt > 0); + + ppage_descr->occupation_cnt --; + return (ppage_descr->occupation_cnt == 0); +} + + +struct sos_kmem_range* sos_physmem_get_kmem_range(sos_paddr_t ppage_paddr) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return NULL; + + return ppage_descr->kernel_range; +} + + +sos_ret_t sos_physmem_set_kmem_range(sos_paddr_t ppage_paddr, + struct sos_kmem_range *range) +{ + struct physical_page_descr *ppage_descr + = get_page_descr_at_paddr(ppage_paddr); + + if (! ppage_descr) + return -SOS_EINVAL; + + ppage_descr->kernel_range = range; + return SOS_OK; +} + +sos_ret_t sos_physmem_get_state(/* out */sos_count_t *total_ppages, + /* out */sos_count_t *nonfree_ppages) +{ + if (total_ppages) + *total_ppages = physmem_total_pages; + if (nonfree_ppages) + *nonfree_ppages = physmem_nonfree_pages; + return SOS_OK; +} + diff --git a/sos/physmem.h b/sos/physmem.h new file mode 100644 index 0000000..a95965e --- /dev/null +++ b/sos/physmem.h @@ -0,0 +1,205 @@ +/* Copyright (C) 2004 David Decotigny + Copyright (C) 2000 The KOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_PHYSMEM_H_ +#define _SOS_PHYSMEM_H_ + +/** + * @file physmem.h + * + * Physical pages of memory + */ + +#include +#include +#include + +/** The size of a physical page (arch-dependent) */ +#define SOS_PAGE_SIZE (4*1024) + +/** The corresponding shift */ +#define SOS_PAGE_SHIFT 12 /* 4 kB = 2^12 B */ + +/** The corresponding mask */ +#define SOS_PAGE_MASK ((1<<12) - 1) + +#define SOS_PAGE_ALIGN_INF(val) \ + SOS_ALIGN_INF((val), SOS_PAGE_SIZE) +#define SOS_PAGE_ALIGN_SUP(val) \ + SOS_ALIGN_SUP((val), SOS_PAGE_SIZE) +#define SOS_IS_PAGE_ALIGNED(val) \ + SOS_IS_ALIGNED((val), SOS_PAGE_SIZE) + +/** + * This is the reserved physical interval for the x86 video memory and + * BIOS area. In physmem.c, we have to mark this area as "nonfree" in + * order to prevent from allocating it. And in paging.c, we'd better + * map it in virtual space if we really want to be able to print to + * the screen (for debugging purpose, at least): for this, the + * simplest is to identity-map this area in virtual space (note + * however that this mapping could also be non-identical). + */ +#define BIOS_N_VIDEO_START 0xa0000 +#define BIOS_N_VIDEO_END 0x100000 + + +/** + * Initialize the physical memory subsystem, for the physical area [0, + * ram_size). This routine takes into account the BIOS and video + * areas, to prevent them from future allocations. + * + * @param ram_size The size of the RAM that will be managed by this subsystem + * + * @param kernel_core_base The lowest address for which the kernel + * assumes identity mapping (ie virtual address == physical address) + * will be stored here + * + * @param kernel_core_top The top address for which the kernel + * assumes identity mapping (ie virtual address == physical address) + * will be stored here + */ +sos_ret_t sos_physmem_subsystem_setup(sos_size_t ram_size, + /* out */sos_paddr_t *kernel_core_base, + /* out */sos_paddr_t *kernel_core_top); + + +/** + * Retrieve the total number of pages, and the number of free pages + * + * @note both parameters may be NULL + */ +sos_ret_t sos_physmem_get_state(/* out */sos_count_t *total_ppages, + /* out */sos_count_t *nonfree_ppages); + + +/** + * Get a free page. + * + * @return The (physical) address of the (physical) page allocated, or + * NULL when none currently available. + * + * @param can_block TRUE if the function is allowed to block + * @note The page returned has a reference count equal to 1. + */ +sos_paddr_t sos_physmem_ref_physpage_new(sos_bool_t can_block); + + +/** + * Increment the reference count of a given physical page. Useful for + * VM code which tries to map a precise physical address. + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + * + * @return TRUE when the page was previously referenced, FALSE when + * the page was previously unreferenced, <0 when the page address is + * invalid. + */ +sos_ret_t sos_physmem_ref_physpage_at(sos_paddr_t ppage_paddr); + + +/** + * Decrement the reference count of the given physical page. When the + * reference count of the page reaches 0, the page is marked free, ie + * is available for future sos_physmem_ref_physpage_new() + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + * + * @return FALSE when the page is still referenced, TRUE when the page + * is now unreferenced, <0 when the page address is invalid + */ +sos_ret_t sos_physmem_unref_physpage(sos_paddr_t ppage_paddr); + + +/** + * Return the reference count of the given page + * + * @return >= 0 (the referebce count of the page) if the physical + * address is valid, or an error status + */ +sos_ret_t sos_physmem_get_physpage_refcount(sos_paddr_t ppage_paddr); + + + +/* + * In some cases (Page Tables on x86 for example), the physical pages + * might contain a set of slots available for "allocation" (x86 + * example: a PT contains a set of PTE). We provide here a set of + * functions to manage a per-page additional counter that holds this + * number of slots currently in use in the page. + * + * A newly allocated physical page has a occupation count of 0. + * + * The management of this counter is up to the subsystem that manages + * the page (mainly: paging), however a simple set of rules applies: + * - it can only be incremented/decremented if the page is referenced + * - when it reaches 0, no automatic action is undertaken + * The first rule means in particular that a page whose reference + * count reaches 0 (=> being freed) cannot have a occupation_cnt + * value > 0 ! The system will be HALTED if this ever happens + */ + + +/** + * Increment the occupation count of the given physical page. + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + * + * @return TRUE when the occupation count was previously NON-ZERO, FALSE + * when this occupation count was 0, <0 when the page address is invalid. + * + * @note the page MUST be marked as REFERENCED by somebody ! + */ +sos_ret_t sos_physmem_inc_physpage_occupation(sos_paddr_t ppage_paddr); + + +/** + * Decrement the occupation count of the given physical page. + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + * + * @return FALSE when the occupation count is still NON-ZERO, TRUE when the + * page now is ZERO, <0 when the page address is invalid + * + * @note the page MUST be marked as REFERENCED by somebody ! + */ +sos_ret_t sos_physmem_dec_physpage_occupation(sos_paddr_t ppage_paddr); + + +#include + +/** + * Return the kernel memory allocation range associated with the given + * physical page, or NULL when page has no associated range + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + */ +struct sos_kmem_range* sos_physmem_get_kmem_range(sos_paddr_t ppage_paddr); + + +/** + * Set the kernel memory allocation range associated to the given + * physical page. + * + * @param ppage_paddr Physical address of the page (MUST be page-aligned) + * + * @return error if page is invalid + */ +sos_ret_t sos_physmem_set_kmem_range(sos_paddr_t ppage_paddr, + struct sos_kmem_range *range); + +#endif /* _SOS_PHYSMEM_H_ */ diff --git a/sos/process.c b/sos/process.c new file mode 100644 index 0000000..f15e2ae --- /dev/null +++ b/sos/process.c @@ -0,0 +1,597 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include /* for SOS_PAGE_SIZE */ + +#include + +#include "process.h" + +#define SOS_PROCESS_MAX_OPENED_FILES 64 +#define SOS_PROCESS_MAX_NAMELEN 32 + + +/** + * Definition of a process in SOS. A process is basically the + * collection of all the resources requested by a user program. The + * threads are one of these resources, the mm_context is one other + * example of such resources. + */ +struct sos_process +{ + char name[SOS_PROCESS_MAX_NAMELEN]; + + /** Process identifier */ + sos_pid_t pid; + + /** First important resource: the address space */ + struct sos_umem_vmm_as *address_space; + + /** Second important resource: the CPU execution contexts, aka the + threads executing in the context of this process. */ + struct sos_thread *thread_list; + sos_count_t nb_threads; + + /** Reference counter, including threads */ + sos_count_t ref_cnt; + + /** The array of opened file descriptors */ + struct sos_fs_opened_file * fds[SOS_PROCESS_MAX_OPENED_FILES]; + + /** Where the root of the process is (for chroot support). May be NULL */ + struct sos_fs_opened_file * root; + + /** Where the current working dir of the process is */ + struct sos_fs_opened_file * cwd; + + /** To chain the processes in the global list of active processes + and PIDs */ + struct sos_hash_linkage hlink_pidtable; +}; + + +/** The list of processes in the system */ +static struct sos_hash_table * process_hash; + + +/** The bitmap for PID allocation */ +static void * bitmap_pid; + + +/** Number of bits in the PID bitmap */ +#define SOS_PROCESS_PID_BITMAP_NBITS \ + (8 * SOS_PROCESS_PID_BITMAP_NPAGES * SOS_PAGE_SIZE) + + +/** The PID where we start looking for a free slot */ +static sos_pid_t next_base_pid; + + +/** The cache for the sos_process structures */ +struct sos_kslab_cache *cache_struct_process; + + +/** + * Helper function for debugging purposes + */ +void sos_process_dumplist(void) +{ + struct sos_thread * cur_thr = sos_thread_get_current(); + int nb_procs = 0; + + /* Internal function used to dump infos about a single process */ + sos_bool_t process_iterator(void* custom_data, + struct sos_process * proc) + { + struct sos_thread * thr; + int nb_thrs; + + nb_procs ++; + sos_bochs_printf(" proc %d @%p: '%s', %d threads, %d refs\n", + proc->pid, proc, proc->name?proc->name:"(none)", + proc->nb_threads, proc->ref_cnt); + + list_foreach_forward_named(proc->thread_list, thr, nb_thrs, + prev_in_process, next_in_process) + { + if (thr == cur_thr) + sos_bochs_printf(" thr@%p: [CURRENT]\n", thr); + else + sos_bochs_printf(" thr@%p: %cstate=%d eip=%p\n", + thr, + sos_cpu_context_is_in_user_mode(thr->cpu_state)?'u':'k', + thr->state, + (void*)sos_cpu_context_get_PC(thr->cpu_state)); + } + + return TRUE; + } + + sos_bochs_printf("\n"); + sos_hash_walk(process_hash, (sos_hash_map_func_t*)process_iterator, NULL); + sos_bochs_printf(" ======= %d processes =======\n", nb_procs); + sos_bochs_printf("\n"); +} + + +sos_ret_t sos_process_subsystem_setup() +{ + + /* Create the cache for the process structures */ + cache_struct_process = sos_kmem_cache_create("struct_process", + sizeof(struct sos_process), + 3, + 0, + SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO); + if (NULL == cache_struct_process) + return -SOS_ENOMEM; + + /* Allocate pages for the PID bitmap */ + bitmap_pid = (void*) sos_kmem_vmm_alloc(SOS_PROCESS_PID_BITMAP_NPAGES, + SOS_KMEM_VMM_MAP); + if (NULL == bitmap_pid) + { + sos_kmem_cache_destroy(cache_struct_process); + return -SOS_ENOMEM; + } + + /* The next process might (and actually will !) have PID 1 */ + next_base_pid = 1; + + /* Create the hash for the process list */ + process_hash = sos_hash_create("pidtable", struct sos_process, + NULL, NULL, 37, + pid, hlink_pidtable); + if (NULL == process_hash) + { + sos_kmem_cache_destroy(cache_struct_process); + sos_kmem_vmm_free((sos_vaddr_t)bitmap_pid); + return -SOS_ENOMEM; + } + + return SOS_OK; +} + + +/** Helper function to allocate a PID entry and bind it to the given process */ +static sos_ret_t register_process(struct sos_process * proc) +{ + sos_ret_t retval; + sos_pid_t pid; + + /* First of all: look for a free PID */ + while (TRUE) + { + pid = sos_bitmap_ffc(bitmap_pid, + SOS_PROCESS_PID_BITMAP_NBITS, + next_base_pid); + if (pid <= 0) + { + if (next_base_pid > 1) + { + /* try again from the beginning */ + next_base_pid = 1; + continue; + } + else + /* Bad news: no PID left at all ! */ + return -SOS_EAGAIN; + } + + if (! sos_bitmap_test_and_set(bitmap_pid, pid)) + /* Could allocate it ! */ + break; + } + + /* Set the base PID for next process */ + if (pid < SOS_PROCESS_PID_BITMAP_NBITS) + next_base_pid = pid + 1; + else + next_base_pid = 1; + + /* Now bind the process into the process hash */ + proc->pid = pid; + + retval = sos_hash_insert(process_hash, proc); + if (SOS_OK != retval) + { + /* Bad news: cannot register in hash */ + SOS_ASSERT_FATAL(sos_bitmap_test_and_clear(bitmap_pid, pid)); + return retval; + } + + return SOS_OK; +} + + +/** Helper function to deallocate a PID entry and unbind it to the given process */ +static sos_ret_t unregister_process(struct sos_process * proc) +{ + SOS_ASSERT_FATAL(SOS_OK == sos_hash_remove(process_hash, proc)); + SOS_ASSERT_FATAL(sos_bitmap_test_and_clear(bitmap_pid, proc->pid)); + proc->pid = 0; + return SOS_OK; +} + + +struct sos_process *sos_process_create(const char *name, + sos_bool_t do_copy_current_process) +{ + sos_ret_t retval = SOS_OK; + sos_ui32_t flags; + struct sos_process *proc; + + proc = (struct sos_process*) sos_kmem_cache_alloc(cache_struct_process, 0); + if (! proc) + return NULL; + + /* proc is already filled with 0 (cache has SOS_KSLAB_CREATE_ZERO + flag) */ + + /* Copy the file descriptors when needed */ + if (do_copy_current_process) + { + struct sos_process * myself = sos_thread_get_current()->process; + int fd; + + for (fd = 0 ; fd < SOS_PROCESS_MAX_OPENED_FILES ; fd++) + if (NULL != myself->fds[fd]) + { + retval = sos_fs_duplicate_opened_file(myself->fds[fd], + proc, + & proc->fds[fd]); + if (SOS_OK != retval) + goto end_create_proc; + } + + retval = sos_fs_duplicate_opened_file(myself->root, + proc, + & proc->root); + if (SOS_OK != retval) + goto end_create_proc; + + retval = sos_fs_duplicate_opened_file(myself->cwd, + proc, + & proc->cwd); + if (SOS_OK != retval) + goto end_create_proc; + } + + if (do_copy_current_process) + { + struct sos_process * myself = sos_thread_get_current()->process; + proc->address_space + = sos_umem_vmm_duplicate_as(sos_process_get_address_space(myself), + proc); + } + else + proc->address_space = sos_umem_vmm_create_empty_as(proc); + + if (NULL == proc->address_space) + { + /* Error */ + retval = -SOS_ENOMEM; + goto end_create_proc; + } + + if (!name) + { + struct sos_thread * cur_thr = sos_thread_get_current(); + if (do_copy_current_process) + name = cur_thr->process->name; + else + name = "[UNNAMED]"; + } + + strzcpy(proc->name, name, SOS_PROCESS_MAX_NAMELEN); + + /* Add it to the global list of processes */ + sos_disable_IRQs(flags); + retval = register_process(proc); + if (SOS_OK == retval) + proc->ref_cnt = 1; /* Mark the process as referenced */ + sos_restore_IRQs(flags); + + end_create_proc: + if (SOS_OK != retval) + { + int fd; + + /* Close the file descriptors */ + for (fd = 0 ; fd < SOS_PROCESS_MAX_OPENED_FILES ; fd++) + if (NULL != proc->fds[fd]) + { + sos_fs_close(proc->fds[fd]); + proc->fds[fd] = NULL; + } + + if (proc->root) + sos_fs_close(proc->root); + if (proc->cwd) + sos_fs_close(proc->cwd); + proc->root = proc->cwd = NULL; + + sos_kmem_cache_free((sos_vaddr_t) proc); + proc = NULL; + } + + return proc; +} + + +inline +sos_ret_t sos_process_ref(struct sos_process *proc) +{ + sos_ui32_t flags; + sos_disable_IRQs(flags); + proc->ref_cnt ++; + sos_restore_IRQs(flags); + return SOS_OK; +} + + +sos_pid_t sos_process_get_pid(const struct sos_process *proc) +{ + return proc->pid; +} + + +sos_count_t sos_process_get_nb_threads(const struct sos_process *proc) +{ + sos_count_t retval; + sos_ui32_t flags; + sos_disable_IRQs(flags); + retval = proc->nb_threads; + sos_restore_IRQs(flags); + return retval; +} + + +struct sos_mm_context * +sos_process_get_mm_context(const struct sos_process *proc) +{ + return sos_umem_vmm_get_mm_context(proc->address_space); +} + + +struct sos_umem_vmm_as * +sos_process_get_address_space(const struct sos_process *proc) +{ + return proc->address_space; +} + + +sos_ret_t sos_process_set_address_space(struct sos_process *proc, + struct sos_umem_vmm_as *new_as) +{ + int fd; + + /* Close the FD that are not allowed to be duplicated */ + for (fd = 0 ; fd < SOS_PROCESS_MAX_OPENED_FILES ; fd++) + if ( (NULL != proc->fds[fd]) + && (proc->fds[fd]->open_flags & SOS_FS_OPEN_CLOSEONEXEC) ) + { + sos_fs_close(proc->fds[fd]); + proc->fds[fd] = NULL; + } + + if (proc->address_space) + { + sos_ret_t retval = sos_umem_vmm_delete_as(proc->address_space); + if (SOS_OK != retval) + return retval; + } + + proc->address_space = new_as; + return SOS_OK; +} + + +struct sos_fs_opened_file * +sos_process_get_root(const struct sos_process *proc) +{ + return proc->root; +} + + +struct sos_fs_opened_file * +sos_process_get_cwd(const struct sos_process *proc) +{ + return proc->cwd; +} + + +struct sos_fs_opened_file * +sos_process_get_opened_file(const struct sos_process *proc, + int fd) +{ + if ((fd < 0) || (fd >= SOS_PROCESS_MAX_OPENED_FILES)) + return NULL; + return proc->fds[fd]; +} + + +sos_ret_t +sos_process_chroot(struct sos_process *proc, + struct sos_fs_opened_file * new_root, + struct sos_fs_opened_file ** old_root) +{ + *old_root = proc->root; + proc->root = new_root; + + return SOS_OK; +} + + +sos_ret_t +sos_process_chdir(struct sos_process *proc, + struct sos_fs_opened_file * new_cwd, + struct sos_fs_opened_file ** old_cwd) +{ + *old_cwd = proc->cwd; + proc->cwd = new_cwd; + + return SOS_OK; +} + + +sos_ret_t +sos_process_register_opened_file(struct sos_process *proc, + struct sos_fs_opened_file * of, + int start_search_at_fd) +{ + int i; + for (i = start_search_at_fd ; + i < SOS_PROCESS_MAX_OPENED_FILES ; + i++) + if (NULL == proc->fds[i]) + { + proc->fds[i] = of; + return i; + } + + return -SOS_EMFILE; +} + + +sos_ret_t +sos_process_unregister_opened_file(struct sos_process *proc, + int fd) +{ + if ((fd < 0) || (fd >= SOS_PROCESS_MAX_OPENED_FILES)) + return -SOS_EBADF; + + proc->fds[fd] = NULL; + return SOS_OK; +} + + + +/* *************************************************** + * Restricted functions + */ + + +sos_ret_t sos_process_register_thread(struct sos_process *in_proc, + struct sos_thread *thr) +{ + sos_ui32_t flags; + + /* The process is assumed to be referenced by somebody */ + SOS_ASSERT_FATAL(in_proc->ref_cnt > 0); + + /* Only threads that are being created are allowed to be attached to + a process */ + SOS_ASSERT_FATAL(thr->state == SOS_THR_CREATED); + + /* Update the list of the threads in the process */ + thr->process = in_proc; + + /* Increment the reference count for the process */ + sos_process_ref(in_proc); + + /* Add the thread to the process thread's list */ + sos_disable_IRQs(flags); + list_add_tail_named(in_proc->thread_list, thr, + prev_in_process, next_in_process); + in_proc->nb_threads ++; + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +/** The function responsible for releasing the resources held by the + process. */ +sos_ret_t sos_process_unref(struct sos_process *proc) +{ + sos_ui32_t flags; + sos_ret_t retval; + int fd; + + SOS_ASSERT_FATAL(proc->ref_cnt > 0); + + sos_disable_IRQs(flags); + proc->ref_cnt --; + if (proc->ref_cnt > 0) + { + sos_restore_IRQs(flags); + return -SOS_EBUSY; + } + unregister_process(proc); + sos_restore_IRQs(flags); + + /* Close the file descriptors */ + for (fd = 0 ; fd < SOS_PROCESS_MAX_OPENED_FILES ; fd++) + if (NULL != proc->fds[fd]) + { + sos_fs_close(proc->fds[fd]); + proc->fds[fd] = NULL; + } + + sos_fs_close(proc->root); + sos_fs_close(proc->cwd); + proc->root = proc->cwd = NULL; + + /* First: free the user address space */ + retval = sos_umem_vmm_delete_as(proc->address_space); + SOS_ASSERT_FATAL(SOS_OK == retval); + + /* Free the process structure */ + sos_kmem_cache_free((sos_vaddr_t)proc); + + return SOS_OK; +} + + +sos_ret_t sos_process_unregister_thread(struct sos_thread *thr) +{ + sos_ui32_t flags; + struct sos_process * in_proc = thr->process; + + SOS_ASSERT_FATAL(thr->state == SOS_THR_ZOMBIE); + + /* Update the list of the threads in the process */ + thr->process = NULL; + sos_disable_IRQs(flags); + list_delete_named(in_proc->thread_list, thr, + prev_in_process, next_in_process); + SOS_ASSERT_FATAL(in_proc->nb_threads > 0); + in_proc->nb_threads --; + sos_restore_IRQs(flags); + + /* Unreference the process */ + sos_process_unref(in_proc); + + return SOS_OK; +} + + +sos_ret_t sos_process_set_name(struct sos_process * proc, + const char * new_name) +{ + strzcpy(proc->name, new_name, SOS_PROCESS_MAX_NAMELEN); + return SOS_OK; +} diff --git a/sos/process.h b/sos/process.h new file mode 100644 index 0000000..86306a5 --- /dev/null +++ b/sos/process.h @@ -0,0 +1,238 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_PROCESS_H_ +#define _SOS_PROCESS_H_ + +/** + * @file process.h + * + * SOS Definition of a process and associated management API. A + * process is basically the collection of all the resources requested + * by a user program. The threads are one of these resources, the + * mm_context is one other example of such resources. In SOS, a + * "process" is mainly a resource manager, a container. It does not + * provide much advanced functionality apart from a reference counter. + * + * Only the user threads belong to a process. The kernel + * threads don't because the resources they hold are held by the whole + * kernel, thus by ALL the threads (kernel AND user): the notion of + * "process" doesn't have any meaning for them because they don't own + * any proper resource (apart from their stack). + */ + +#include + +/** + * The definition of an SOS process is opaque. @see process.c + */ +struct sos_process; + +#include +#include + + +/** + * Default size of a user stack (8 MB) + */ +#define SOS_DEFAULT_USER_STACK_SIZE (8 << 20) + + +/** Number of pages for the pid bitmap (1 page = 32k PIDs) */ +#define SOS_PROCESS_PID_BITMAP_NPAGES 1 + + +/** + * Initialization of the process subsystem + */ +sos_ret_t sos_process_subsystem_setup(void); + + +/** + * Initialize a new process and return a reference to it. This + * means that: + * - A new mm_context has been initialized + * - No threads belong to this process yet + * - If do_copy_current_process is FALSE: + * - Nothing is mapped in user space + * - No other resource is used + * - If do_copy_current_process is TRUE: + * - Same user mapping as in current thread's process + * - Same other resources used as for current thread's processs + * + * @return NULL on error (not enough memory) + */ +struct sos_process *sos_process_create(const char *name, + sos_bool_t do_copy_current_process); + + +/** + * Signal we're using another reference to a process. + */ +sos_ret_t sos_process_ref(struct sos_process *proc); + + +/** + * Release the reference to a process. If nobody holds a reference to + * it and if the process does not have any thread anymore, the process + * is destroyed. + * + * @return -SOS_EBUSY when the process is still referenced afterwards + */ +sos_ret_t sos_process_unref(struct sos_process *proc); + + +/** + * Return the PID of the process + */ +sos_pid_t sos_process_get_pid(const struct sos_process *proc); + + +/** + * Return the number of threads currently registered in the process + */ +sos_count_t sos_process_get_nb_threads(const struct sos_process *proc); + + +/** + * Retrieve the address of the MMU configuration description + * + * @return NULL on error + */ +struct sos_mm_context * +sos_process_get_mm_context(const struct sos_process *proc); + + +/** + * Retrieve the address space for the process + * + * @return NULL on error + */ +struct sos_umem_vmm_as * +sos_process_get_address_space(const struct sos_process *proc); + + +/** + * Retrieve the root FS node of the process + * + * @return NULL on error + */ +struct sos_fs_opened_file * +sos_process_get_root(const struct sos_process *proc); + + +/** + * Retrieve the current working dir of the process + * + * @return NULL on error + */ +struct sos_fs_opened_file * +sos_process_get_cwd(const struct sos_process *proc); + + +/** + * Retrieve the opened file structure corresponding to the given FD + * + * @return NULL on error + */ +struct sos_fs_opened_file * +sos_process_get_opened_file(const struct sos_process *proc, + int fd); + + +/** + * Change the root directory for the process + */ +sos_ret_t +sos_process_chroot(struct sos_process *proc, + struct sos_fs_opened_file * new_root, + struct sos_fs_opened_file ** old_root); + + +/** + * Change the working directory of the process + */ +sos_ret_t +sos_process_chdir(struct sos_process *proc, + struct sos_fs_opened_file * new_cwd, + struct sos_fs_opened_file ** old_cwd); + + +/** + * Allocate a new file descriptor for file + * + * @return >=0 on success, <0 on error (errno) + */ +sos_ret_t +sos_process_register_opened_file(struct sos_process *proc, + struct sos_fs_opened_file * of, + int start_search_at_fd); + + +/** + * Free the given file descriptor + * + * @return >=0 on success, <0 on error (errno) + */ +sos_ret_t +sos_process_unregister_opened_file(struct sos_process *proc, + int fd); + + +/* *************************************************** + * Restricted functions + */ + + +/** + * Change the name of the process + */ +sos_ret_t sos_process_set_name(struct sos_process * proc, + const char * new_name); + + +/** + * Attach the given thread to the process + * + * @note This function is called internally by thread.c . The thread + * is assumed to be in the "CREATED" state, ie one cannot attach a + * thread to some other process once it has been created. + */ +sos_ret_t sos_process_register_thread(struct sos_process *in_proc, + struct sos_thread *thr); + + +/** + * Remove the given thread from the given process threads' list. When + * the process becomes empty (ie does not contain any thread anymore + * and is not referenced by anybody), all its resources are released. + * + * @note This function is called internally by thread.c . The thread + * is assumed to be in the "ZOMBIE" state. + */ +sos_ret_t sos_process_unregister_thread(struct sos_thread *thr); + + +/** + * Replace the address space of the process by another one + * + * @note The previous address space (if any) is deleted ! + */ +sos_ret_t sos_process_set_address_space(struct sos_process *proc, + struct sos_umem_vmm_as *new_as); + +#endif /* _SOS_PROCESS_H_ */ diff --git a/sos/sched.c b/sos/sched.c new file mode 100644 index 0000000..5b1e03b --- /dev/null +++ b/sos/sched.c @@ -0,0 +1,338 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "sched.h" + + +/** + * The definition of the scheduler queue. We could have used a normal + * kwaitq here, it would have had the same properties (regarding + * priority ordering mainly). But we don't bother with size + * considerations here (in kwaitq, we had better make the kwaitq + * structure as small as possible because there are a lot of kwaitq in + * the system: at least 1 per opened file), so that we can implement a + * much faster way of handling the prioritized jobs. + */ +struct sos_sched_queue +{ + unsigned int nr_threads; + struct sos_thread *thread_list[SOS_SCHED_NUM_PRIO]; +}; + + +/** + * We manage 2 queues: a queue being scanned for ready threads + * (active_queue) and a queue to store the threads the threads having + * expired their time quantuum. + */ +static struct sos_sched_queue *active_queue, *expired_queue; + + +/** + * The instances for the active/expired queues + */ +static struct sos_sched_queue sched_queue[2]; + + +/** + * The array giving the timeslice corresponding to each priority level + */ +struct sos_time time_slice[SOS_SCHED_NUM_PRIO]; + + +sos_ret_t sos_sched_subsystem_setup() +{ + sos_sched_priority_t prio; + + memset(sched_queue, 0x0, sizeof(sched_queue)); + active_queue = & sched_queue[0]; + expired_queue = & sched_queue[1]; + + /* pre-compute time slices */ + for (prio = SOS_SCHED_PRIO_TS_HIGHEST ; + prio <= SOS_SCHED_PRIO_TS_LOWEST ; + prio ++) + { + unsigned int ms; + ms = SOS_TIME_SLICE_MIN + + (SOS_TIME_SLICE_MAX - SOS_TIME_SLICE_MIN) + * (prio - SOS_SCHED_PRIO_TS_HIGHEST) + / (SOS_SCHED_PRIO_TS_LOWEST - SOS_SCHED_PRIO_TS_HIGHEST); + time_slice[prio].sec = ms / 1000; + time_slice[prio].nanosec = 1000000UL * (ms % 1000); + } + + return SOS_OK; +} + + +/** + * Helper function to add a thread in a ready queue AND to change the + * state of the given thread to "READY". + * + * @param insert_at_tail TRUE to tell to add the thread at the end of + * the ready list. Otherwise it is added at the head of it. + */ +static sos_ret_t add_in_ready_queue(struct sos_sched_queue *q, + struct sos_thread *thr, + sos_bool_t insert_at_tail) +{ + sos_sched_priority_t prio; + + SOS_ASSERT_FATAL( (SOS_THR_CREATED == thr->state) + || (SOS_THR_RUNNING == thr->state) /* Yield */ + || (SOS_THR_BLOCKED == thr->state) ); + + /* Add the thread to the CPU queue */ + prio = sos_thread_get_priority(thr); + if (insert_at_tail) + list_add_tail_named(q->thread_list[prio], thr, + ready.rdy_prev, ready.rdy_next); + else + list_add_head_named(q->thread_list[prio], thr, + ready.rdy_prev, ready.rdy_next); + thr->ready.rdy_queue = q; + q->nr_threads ++; + + /* Ok, thread is now really ready to be (re)started */ + thr->state = SOS_THR_READY; + + return SOS_OK; +} + + +sos_ret_t sos_sched_set_ready(struct sos_thread *thr) +{ + sos_ret_t retval; + + /* Don't do anything for already ready threads */ + if (SOS_THR_READY == thr->state) + return SOS_OK; + + /* Reset the CPU time used in the quantuum */ + memset(& thr->user_time_spent_in_slice, 0x0, sizeof(struct sos_time)); + + if (SOS_SCHED_PRIO_IS_RT(sos_thread_get_priority(thr))) + { + /* Real-time thread: schedule it for the present turn */ + retval = add_in_ready_queue(active_queue, thr, TRUE); + } + else + { + /* Non real-time thread: schedule it for next turn */ + retval = add_in_ready_queue(expired_queue, thr, TRUE); + } + + return retval; +} + + +sos_ret_t sos_sched_change_priority(struct sos_thread *thr, + sos_sched_priority_t priority) +{ + struct sos_thread *thread_list; + SOS_ASSERT_FATAL(SOS_THR_READY == thr->state); + + /* Temp variable */ + thread_list + = thr->ready.rdy_queue->thread_list[sos_thread_get_priority(thr)]; + + list_delete_named(thread_list, thr, ready.rdy_prev, ready.rdy_next); + + /* Update lists */ + thread_list = thr->ready.rdy_queue->thread_list[priority]; + list_add_tail_named(thread_list, thr, ready.rdy_prev, ready.rdy_next); + thr->ready.rdy_queue->thread_list[priority] = thread_list; + + return SOS_OK; +} + + +/** + * Helper function to determine whether the current thread expired its + * time quantuum + */ +static sos_bool_t +thread_expired_its_quantuum(struct sos_thread *thr) +{ + sos_sched_priority_t prio = sos_thread_get_priority(thr); + + /* No timesharing/round-robin for "real-time" threads */ + if (SOS_SCHED_PRIO_IS_RT(prio)) + return FALSE; + + /* Current (user) thread expired its time quantuum ? A kernel + thread never expires because sos_sched_do_timer_tick() below + won't update its user_time_spent_in_slice */ + if (sos_time_cmp(& thr->user_time_spent_in_slice, + & time_slice[prio]) >= 0) + return TRUE; + + return FALSE; +} + + +struct sos_thread * sos_reschedule(struct sos_thread *current_thread, + sos_bool_t do_yield) +{ + sos_sched_priority_t prio; + + /* Force the current thread to release the CPU if it expired its + quantuum */ + if (thread_expired_its_quantuum(current_thread)) + { + /* Reset the CPU time used in the quantuum */ + memset(& current_thread->user_time_spent_in_slice, + 0x0, sizeof(struct sos_time)); + + do_yield = TRUE; + } + + if (SOS_THR_ZOMBIE == current_thread->state) + { + /* Don't think of returning to this thread since it is + terminated */ + /* Nop */ + } + else if (SOS_THR_BLOCKED != current_thread->state) + { + /* Take into account the current executing thread unless it is + marked blocked */ + if (do_yield) + { + /* Ok, reserve it for next turn */ + if (SOS_SCHED_PRIO_IS_RT(sos_thread_get_priority(current_thread))) + add_in_ready_queue(active_queue, current_thread, TRUE); + else + add_in_ready_queue(expired_queue, current_thread, TRUE); + } + else + { + /* Put it at the head of the active list */ + add_in_ready_queue(active_queue, current_thread, FALSE); + } + } + + + /* Active queue is empty ? */ + if (active_queue->nr_threads <= 0) + { + /* Yes: Exchange it with the expired queue */ + struct sos_sched_queue *q; + q = active_queue; + active_queue = expired_queue; + expired_queue = q; + } + + /* Now loop over the priorities in the active queue, looking for a + non-empty queue */ + for (prio = SOS_SCHED_PRIO_HIGHEST ; prio <= SOS_SCHED_PRIO_LOWEST ; prio ++) + { + struct sos_thread *next_thr; + + if (list_is_empty_named(active_queue->thread_list[prio], + ready.rdy_prev, ready.rdy_next)) + continue; + + /* Queue is not empty: take the thread at its head */ + next_thr = list_pop_head_named(active_queue->thread_list[prio], + ready.rdy_prev, ready.rdy_next); + active_queue->nr_threads --; + + return next_thr; + } + + + SOS_FATAL_ERROR("No kernel thread ready ?!"); + return NULL; +} + + +sos_ret_t sos_sched_do_timer_tick() +{ + struct sos_thread *interrupted_thread = sos_thread_get_current(); + struct sos_time tick_duration; + sos_bool_t cur_is_user; + sos_ui32_t nb_user_ready = 0; + sos_ui32_t nb_kernel_ready = 0; + int prio; + + sos_time_get_tick_resolution(& tick_duration); + + /* Update the timing statistics */ + if (sos_cpu_context_is_in_user_mode(interrupted_thread->cpu_state)) + { + cur_is_user = TRUE; + + /* User time */ + sos_time_inc(& interrupted_thread->rusage.ru_utime, + & tick_duration); + + /* Update time spent is current timeslice ONLY for a user thread */ + sos_time_inc(& interrupted_thread->user_time_spent_in_slice, + & tick_duration); + } + else + { + cur_is_user = FALSE; + + /* System time */ + sos_time_inc(& interrupted_thread->rusage.ru_stime, + & tick_duration); + } + + + /* Update load stats */ + for (prio = SOS_SCHED_PRIO_HIGHEST ; prio <= SOS_SCHED_PRIO_LOWEST ; prio ++) + { + struct sos_thread *thr; + int nb_thrs; + + list_foreach_forward_named(active_queue->thread_list[prio], + thr, nb_thrs, + ready.rdy_prev, ready.rdy_next) + { + if (sos_cpu_context_is_in_user_mode(thr->cpu_state)) + nb_user_ready ++; + else + nb_kernel_ready ++; + } + + list_foreach_forward_named(expired_queue->thread_list[prio], + thr, nb_thrs, + ready.rdy_prev, ready.rdy_next) + { + if (sos_cpu_context_is_in_user_mode(thr->cpu_state)) + nb_user_ready ++; + else + nb_kernel_ready ++; + } + } + + sos_load_do_timer_tick(cur_is_user, + nb_user_ready, + nb_kernel_ready); + + return SOS_OK; +} diff --git a/sos/sched.h b/sos/sched.h new file mode 100644 index 0000000..ad73fbf --- /dev/null +++ b/sos/sched.h @@ -0,0 +1,158 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_SCHED_H_ +#define _SOS_SCHED_H_ + + +/** + * @file sched.h + * + * A basic scheduler inspired from the O(1) Linux scheduler. Supports + * 2 classes of thread priorities: + * - so-called 'real-time' threads scheduled according to a simple + * traditional static priority real-time scheduler. "Real-time" round + * robin scheduling is not supported. + * - "fair" time-sharing scheduling for non real-time threads. "fair" + * because no starvation among the non real-time threads is + * possible. Contrary to the original O(1) Linux scheduler, the + * on-line adjustment of the scheduling priorities to cope with + * interactive/non interactive threads discrimination is not + * supported: threads keep having the same priority as long as the + * user does not change it. + * + * The functions below manage CPU queues, and are NEVER responsible + * for context switches (see thread.h for that) or synchronizations + * (see kwaitq.h or the higher levels primitives [mutex, semaphore, + * ...] for that). + * + * @note IMPORTANT: all the functions below are meant to be called + * ONLY by the thread/timer/kwaitq subsystems. DO NOT use them + * directly from anywhere else: use ONLY the thread/kwaitq functions! + * If you still want to call them directly despite this disclaimer, + * simply disable interrupts before clling them. + */ + +#include +#include + + +/** + * The definition of a priority + */ +typedef unsigned char sos_sched_priority_t; + + +#include + + +/** + * Valid priority interval ("real-time" and non real-time threads altogether) + */ +#define SOS_SCHED_PRIO_HIGHEST 0 +#define SOS_SCHED_PRIO_LOWEST 63 +#define SOS_SCHED_NUM_PRIO 64 + + +/** + * Class-specific priorities + */ +#define SOS_SCHED_PRIO_RT_HIGHEST 0 /**< Highest 'real-time' static prio. */ +#define SOS_SCHED_PRIO_RT_LOWEST 15 /**< Lowest 'real-time' static priority */ +#define SOS_SCHED_PRIO_TS_HIGHEST 16 /**< Highest time-sharing priority */ +#define SOS_SCHED_PRIO_TS_LOWEST 63 /**< Lowest time-sharing priority */ + +#define SOS_SCHED_PRIO_DEFAULT 40 /**< Default priority */ + + +/** + * Helper macros (Yes, priorities ordered in decreasing numerical value) + * + * @note: The use of this function is RESERVED + */ +#define SOS_SCHED_PRIO_CMP(prio1,prio2) ((prio1) - (prio2)) + +#define SOS_SCHED_PRIO_IS_VALID(prio) \ + ({ int __prio = (int)(prio); \ + ((__prio) <= SOS_SCHED_PRIO_LOWEST) \ + && \ + ((__prio) >= SOS_SCHED_PRIO_HIGHEST); }) + +#define SOS_SCHED_PRIO_IS_RT(prio) \ + ({ int __prio = (int)(prio); \ + ((__prio) <= SOS_SCHED_PRIO_RT_LOWEST) \ + && \ + ((__prio) >= SOS_SCHED_PRIO_RT_HIGHEST); }) + + +/** + * The time slices for 'time-sharing' user threads, in ms + */ +#define SOS_TIME_SLICE_MIN 10 /* for SOS_SCHED_PRIO_TS_HIGHEST */ +#define SOS_TIME_SLICE_MAX 200 /* for SOS_SCHED_PRIO_TS_LOWEST */ + + +/** + * Initialize the scheduler + * + * @note: The use of this function is RESERVED + */ +sos_ret_t sos_sched_subsystem_setup(void); + + +/** + * Mark the given thread as ready + * + * @note: The use of this function is RESERVED + */ +sos_ret_t sos_sched_set_ready(struct sos_thread * thr); + + +/** + * Return the identifier of the next thread to run. Also removes it + * from the ready list, but does NOT set is as current_thread ! + * + * @param current_thread TCB of the thread calling the function + * + * @param do_yield When TRUE, put the current executing thread at the + * end of the ready list. Otherwise it is kept at the head of it. + * + * @note: The use of this function is RESERVED + */ +struct sos_thread * sos_reschedule(struct sos_thread * current_thread, + sos_bool_t do_yield); + +/** + * Called by thread subsystem each time a READY thread's priority is + * changed + * + * @note: The use of this function is RESERVED (to thread.c) + */ +sos_ret_t sos_sched_change_priority(struct sos_thread * thr, + sos_sched_priority_t priority); + + +/** + * Account for the execution of a time tick. Should be called + * immediately before the timer ISR calls sos_reschedule() before + * returning to thread context. + * + * @note The use of this function is RESERVED (to timer IRQ) + */ +sos_ret_t sos_sched_do_timer_tick(void); + +#endif /* _SOS_WAITQUEUE_H_ */ diff --git a/sos/syscall.c b/sos/syscall.c new file mode 100644 index 0000000..f165ce0 --- /dev/null +++ b/sos/syscall.c @@ -0,0 +1,1482 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "syscall.h" + + +/** To get rid of gcc's "dereferencing type-punned pointer will break + strict-aliasing rules" warning */ +#define SYSCALL_VAR32_PTR(ui32_variable) \ + ((void*)&(ui32_variable)) + + +/** + * THE syscall entry point + */ +sos_ret_t sos_do_syscall(int syscall_id, + const struct sos_cpu_state *user_ctxt) +{ + sos_ret_t retval; + + + switch(syscall_id) + { + case SOS_SYSCALL_ID_EXIT: + { + unsigned int status; + + retval = sos_syscall_get1arg(user_ctxt, & status); + if (SOS_OK != retval) + break; + sos_thread_exit(); + retval = -SOS_EFATAL; /* Not reached */ + } + break; + + case SOS_SYSCALL_ID_FORK: + { + struct sos_thread *cur_thr, *new_thr; + struct sos_process *new_proc; + + cur_thr = sos_thread_get_current(); + + /* Duplicate the current process (and its address space) */ + new_proc = sos_process_create(NULL, TRUE); + if (! new_proc) + { + retval = -SOS_ENOMEM; + break; + } + + /* Create *the* thread in this new processs, copy of the + current user thread (same registers, EXCEPT eax which is + set to 0) */ + new_thr = + sos_duplicate_user_thread(NULL, new_proc, + cur_thr, + user_ctxt, + 0 /* fork return value for child ! */); + if (! new_thr) + { + sos_process_unref(new_proc); + retval = -SOS_ENOMEM; + break; + } + + sos_process_unref(new_proc); + + /* Return to the "parent" thread with a value different from + 0. Unix says it should be the "PID" of the child. We don't + have such a "PID" notion for now */ + retval = (sos_ui32_t)sos_process_get_pid(new_proc); + } + break; + + case SOS_SYSCALL_ID_GETPID: + { + struct sos_thread *cur_thr; + struct sos_process * proc; + + cur_thr = sos_thread_get_current(); + proc = cur_thr->process; + + retval = (sos_ui32_t) sos_process_get_pid(proc); + } + break; + + case SOS_SYSCALL_ID_EXEC: + { + struct sos_thread *cur_thr, *new_thr; + struct sos_process * proc; + struct sos_umem_vmm_as *new_as; + sos_uaddr_t user_str, ustack, start_uaddr; + sos_uaddr_t src_argaddr; + sos_size_t len_args; + sos_size_t len; + char * str; + + cur_thr = sos_thread_get_current(); + proc = cur_thr->process; + + /* Make sure the process has exactly 1 thread in it */ + if (sos_process_get_nb_threads(proc) != 1) + { + retval = -SOS_EBUSY; + break; + } + + /* Get the user arguments */ + retval = sos_syscall_get4args(user_ctxt, & user_str, & len, + & src_argaddr, & len_args); + if (SOS_OK != retval) + break; + + /* Don't go any further if the arg/env array is obviously too + large */ + if (SOS_DEFAULT_USER_STACK_SIZE <= len_args) + { + retval = -SOS_EINVAL; + break; + } + + /* Copy the program name into kernel sppace */ + retval = sos_strndup_from_user(& str, user_str, len + 1, 0); + if (SOS_OK != retval) + { + break; + } + + /* Create a new empty address space to map the program */ + new_as = sos_umem_vmm_create_empty_as(proc); + if (! new_as) + { + sos_kfree((sos_vaddr_t)str); + retval = -SOS_ENOMEM; + break; + } + + /* Map the program in it */ + start_uaddr = sos_binfmt_elf32_map(new_as, str); + if (start_uaddr == (sos_uaddr_t)NULL) + { + sos_umem_vmm_delete_as(new_as); + sos_kfree((sos_vaddr_t)str); + retval = -SOS_ENOENT; + break; + } + + /* Allocate space for the user stack (8MB) */ +#define SOS_DEFAULT_USER_STACK_SIZE (8 << 20) + ustack = (SOS_PAGING_UPPER_USER_ADDRESS + - SOS_DEFAULT_USER_STACK_SIZE) + + 1; + retval = sos_dev_zero_map(new_as, &ustack, SOS_DEFAULT_USER_STACK_SIZE, + SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE, + /* PRIVATE */ 0); + if (SOS_OK != retval) + { + sos_umem_vmm_delete_as(new_as); + sos_kfree((sos_vaddr_t)str); + break; + } + + /* ustack now refers to the initial stack pointer of the new + user thread: update it here */ + ustack = SOS_ALIGN_INF((ustack + SOS_DEFAULT_USER_STACK_SIZE + - len_args), 4); + + if (len_args != sos_usercpy(new_as, ustack, + NULL, src_argaddr, + len_args)) + { + sos_umem_vmm_delete_as(new_as); + sos_kfree((sos_vaddr_t)str); + retval = -SOS_EFAULT; + break; + } + + /* Now create the user thread */ + new_thr = sos_create_user_thread(NULL, + proc, + start_uaddr, + 0, 0, + ustack, + SOS_SCHED_PRIO_TS_LOWEST); + if (! new_thr) + { + sos_umem_vmm_delete_as(new_as); + sos_kfree((sos_vaddr_t)str); + retval = -SOS_ENOMEM; + break; + } + + sos_process_set_name(proc, str); + + /* Switch to this address space */ + retval = sos_process_set_address_space(proc, + new_as); + if (SOS_OK != retval) + { + sos_umem_vmm_delete_as(new_as); + sos_kfree((sos_vaddr_t)str); + break; + } + + /* The current thread must exit now */ + sos_kfree((sos_vaddr_t)str); + sos_thread_exit(); + retval = -SOS_EFATAL; + } + break; + + case SOS_SYSCALL_ID_MUNMAP: + { + sos_uaddr_t start_uaddr; + sos_size_t size; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(start_uaddr), + SYSCALL_VAR32_PTR(size)); + if (SOS_OK != retval) + break; + + retval = sos_umem_vmm_unmap(my_as, start_uaddr, size); + } + break; + + case SOS_SYSCALL_ID_MPROTECT: + { + sos_uaddr_t start_uaddr; + sos_size_t size; + sos_ui32_t new_access_rights; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(start_uaddr), + SYSCALL_VAR32_PTR(size), + SYSCALL_VAR32_PTR(new_access_rights)); + if (SOS_OK != retval) + break; + + retval = sos_thread_prepare_user_space_access(NULL, (sos_vaddr_t)NULL); + if (SOS_OK != retval) + break; + + retval = sos_umem_vmm_chprot(my_as, start_uaddr, size, + new_access_rights); + + sos_thread_end_user_space_access(); + } + break; + + case SOS_SYSCALL_ID_MRESIZE: + { + sos_uaddr_t old_uaddr; + sos_size_t old_size; + sos_uaddr_t *uptr_new_uaddr; + sos_uaddr_t new_uaddr; + sos_size_t new_size; + sos_ui32_t flags; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get5args(user_ctxt, + SYSCALL_VAR32_PTR(old_uaddr), + SYSCALL_VAR32_PTR(old_size), + SYSCALL_VAR32_PTR(uptr_new_uaddr), + SYSCALL_VAR32_PTR(new_size), + SYSCALL_VAR32_PTR(flags)); + if (SOS_OK != retval) + break; + + if (sizeof(new_uaddr) != sos_memcpy_from_user((sos_vaddr_t)& new_uaddr, + (sos_uaddr_t) + uptr_new_uaddr, + sizeof(new_uaddr))) + { + retval = -SOS_EFAULT; + break; + } + + retval = sos_thread_prepare_user_space_access(NULL, (sos_vaddr_t)NULL); + if (SOS_OK != retval) + break; + + retval = sos_umem_vmm_resize(my_as, old_uaddr, old_size, + & new_uaddr, new_size, flags); + sos_thread_end_user_space_access(); + if (SOS_OK != retval) + break; + + if (sizeof(new_uaddr) + == sos_memcpy_to_user((sos_uaddr_t)uptr_new_uaddr, + (sos_vaddr_t)&new_uaddr, + sizeof(new_uaddr))) + { + retval = -SOS_EFAULT; + break; + } + } + break; + + + case SOS_SYSCALL_ID_MSYNC: + { + sos_uaddr_t start_uaddr; + sos_size_t size; + sos_ui32_t flags; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(start_uaddr), + SYSCALL_VAR32_PTR(size), + SYSCALL_VAR32_PTR(flags)); + if (SOS_OK != retval) + break; + + retval = sos_thread_prepare_user_space_access(NULL, (sos_vaddr_t)NULL); + if (SOS_OK != retval) + break; + retval = sos_umem_vmm_sync(my_as, start_uaddr, size, flags); + sos_thread_end_user_space_access(); + } + break; + + + case SOS_SYSCALL_ID_NEW_THREAD: + { + sos_uaddr_t start_func; + sos_ui32_t start_arg1, start_arg2; + sos_size_t stack_size; + sos_uaddr_t stack_uaddr; + + struct sos_thread * new_thr; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get4args(user_ctxt, + SYSCALL_VAR32_PTR(start_func), + SYSCALL_VAR32_PTR(start_arg1), + SYSCALL_VAR32_PTR(start_arg2), + SYSCALL_VAR32_PTR(stack_size)); + if (SOS_OK != retval) + break; + + if (stack_size <= 0) + { + retval = -SOS_EINVAL; + break; + } + + /* Allocate the stack */ + stack_uaddr = 0; + stack_size = SOS_PAGE_ALIGN_SUP(stack_size); + retval = sos_dev_zero_map(my_as, & stack_uaddr, stack_size, + SOS_VM_MAP_PROT_READ | SOS_VM_MAP_PROT_WRITE, + /* PRIVATE */ 0); + if (SOS_OK != retval) + break; + + /* Now create the user thread */ + new_thr = sos_create_user_thread(NULL, + sos_thread_get_current()->process, + start_func, + start_arg1, + start_arg2, + stack_uaddr + stack_size - 4, + SOS_SCHED_PRIO_TS_LOWEST); + + if (! new_thr) + { + sos_umem_vmm_unmap(my_as, stack_uaddr, stack_size); + retval = -SOS_ENOMEM; + break; + } + + } + break; + + case SOS_SYSCALL_ID_NANOSLEEP: + { + struct sos_time delay; + + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(delay.sec), + SYSCALL_VAR32_PTR(delay.nanosec)); + if (SOS_OK != retval) + break; + + retval = sos_thread_sleep(& delay); + } + break; + + case SOS_SYSCALL_ID_BRK: + { + sos_uaddr_t new_top_heap; + struct sos_umem_vmm_as * my_as; + + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + + retval = sos_syscall_get1arg(user_ctxt, + SYSCALL_VAR32_PTR(new_top_heap)); + if (SOS_OK != retval) + break; + + retval = sos_thread_prepare_user_space_access(NULL, (sos_vaddr_t)NULL); + if (SOS_OK != retval) + break; + + retval = sos_umem_vmm_brk(my_as, new_top_heap); + sos_thread_end_user_space_access(); + } + break; + + + /** + * File system interface + */ + case SOS_SYSCALL_ID_MOUNT: + { + sos_uaddr_t user_src; + sos_size_t srclen; + char * kernel_src = NULL; + sos_uaddr_t user_dst; + sos_size_t dstlen; + char * kernel_dst; + sos_ui32_t mountflags; + sos_uaddr_t user_fstype; + char * kernel_fstype; + sos_uaddr_t user_args; + char * kernel_args = NULL; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get7args(user_ctxt, + SYSCALL_VAR32_PTR(user_src), + SYSCALL_VAR32_PTR(srclen), + SYSCALL_VAR32_PTR(user_dst), + SYSCALL_VAR32_PTR(dstlen), + SYSCALL_VAR32_PTR(user_fstype), + SYSCALL_VAR32_PTR(mountflags), + SYSCALL_VAR32_PTR(user_args)); + if (SOS_OK != retval) + break; + + if (user_src != (sos_uaddr_t)NULL) + { + retval = sos_strndup_from_user(&kernel_src, user_src, srclen, 0); + if (SOS_OK != retval) + break; + } + + retval = sos_strndup_from_user(&kernel_dst, user_dst, dstlen, 0); + if (SOS_OK != retval) + { + if (kernel_src) + sos_kfree((sos_vaddr_t)kernel_src); + break; + } + + retval = sos_strndup_from_user(& kernel_fstype, user_fstype, 256, 0); + if (SOS_OK != retval) + { + if (kernel_src) + sos_kfree((sos_vaddr_t)kernel_src); + sos_kfree((sos_vaddr_t)kernel_dst); + break; + } + + if (user_args != (sos_uaddr_t)NULL) + { + retval = sos_strndup_from_user(& kernel_args, user_args, 1024, 0); + if (SOS_OK != retval) + { + if (kernel_src) + sos_kfree((sos_vaddr_t)kernel_src); + sos_kfree((sos_vaddr_t)kernel_dst); + sos_kfree((sos_vaddr_t)kernel_fstype); + break; + } + } + + retval = sos_fs_mount(proc, kernel_src, srclen, + kernel_dst, dstlen, + kernel_fstype, + mountflags, + kernel_args, + NULL); + if (kernel_src) + sos_kfree((sos_vaddr_t)kernel_src); + sos_kfree((sos_vaddr_t)kernel_dst); + sos_kfree((sos_vaddr_t)kernel_fstype); + if (kernel_args) + sos_kfree((sos_vaddr_t)kernel_args); + } + break; + + case SOS_SYSCALL_ID_UMOUNT: + { + sos_uaddr_t user_str; + sos_size_t len; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_umount(proc, + path, len); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_SYNC: + { + sos_fs_sync_all_fs(); + retval = SOS_OK; + } + break; + + case SOS_SYSCALL_ID_VFSTAT64: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_uaddr_t user_vfstat_struct; + struct sos_fs_statfs kernel_vfstat_struct; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(user_vfstat_struct)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_vfstat(proc, path, len, & kernel_vfstat_struct); + sos_kfree((sos_vaddr_t)path); + if (SOS_OK != retval) + break; + + if (sizeof(kernel_vfstat_struct) + != sos_memcpy_to_user(user_vfstat_struct, + (sos_vaddr_t) & kernel_vfstat_struct, + sizeof(kernel_vfstat_struct))) + retval = -SOS_EFAULT; + } + break; + + case SOS_SYSCALL_ID_OPEN: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_ui32_t open_flags; + sos_ui32_t access_rights; + char * path; + struct sos_fs_opened_file * of; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get4args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(open_flags), + SYSCALL_VAR32_PTR(access_rights)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_open(proc, + path, len, + open_flags, + access_rights, + & of); + sos_kfree((sos_vaddr_t)path); + if (SOS_OK != retval) + break; + + retval = sos_process_register_opened_file(proc, of, 0); + if (retval < 0) + { + sos_fs_close(of); + break; + } + } + break; + + case SOS_SYSCALL_ID_CLOSE: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get1arg(user_ctxt, + SYSCALL_VAR32_PTR(fd)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + retval = sos_process_unregister_opened_file(proc, fd); + if (SOS_OK != retval) + break; + + retval = sos_fs_close(of); + } + break; + + case SOS_SYSCALL_ID_READ: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_uaddr_t uaddr_buf; + sos_uaddr_t uaddr_buflen; + sos_size_t kernel_buflen; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(uaddr_buf), + SYSCALL_VAR32_PTR(uaddr_buflen)); + if (SOS_OK != retval) + break; + + /* Retrieve the value for "buflen" */ + retval = sos_memcpy_from_user((sos_vaddr_t)& kernel_buflen, + uaddr_buflen, + sizeof(kernel_buflen)); + if (sizeof(kernel_buflen) != retval) + { + retval = -SOS_EFAULT; + break; + } + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual reading */ + retval = sos_fs_read(of, uaddr_buf, & kernel_buflen); + + /* Send successful number of bytes read to user */ + sos_memcpy_to_user(uaddr_buflen, + (sos_vaddr_t)& kernel_buflen, + sizeof(kernel_buflen)); + } + break; + + case SOS_SYSCALL_ID_READDIR: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_uaddr_t uaddr_direntry; + struct sos_fs_dirent direntry; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(uaddr_direntry)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual readdir */ + retval = sos_fs_readdir(of, & direntry); + if (SOS_OK != retval) + break; + + /* Send direntry structure to user */ + if (sizeof(direntry) != sos_memcpy_to_user(uaddr_direntry, + (sos_vaddr_t)& direntry, + sizeof(direntry))) + retval = -SOS_EFAULT; + } + break; + + case SOS_SYSCALL_ID_WRITE: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_uaddr_t uaddr_buf; + sos_uaddr_t uaddr_buflen; + sos_size_t kernel_buflen; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(uaddr_buf), + SYSCALL_VAR32_PTR(uaddr_buflen)); + if (SOS_OK != retval) + break; + + /* Retrieve the value for "buflen" */ + retval = sos_memcpy_from_user((sos_vaddr_t)& kernel_buflen, + uaddr_buflen, + sizeof(kernel_buflen)); + if (sizeof(kernel_buflen) != retval) + { + retval = -SOS_EFAULT; + break; + } + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual writing */ + retval = sos_fs_write(of, uaddr_buf, & kernel_buflen); + + /* Send successful number of bytes written to user */ + sos_memcpy_to_user(uaddr_buflen, + (sos_vaddr_t)& kernel_buflen, + sizeof(kernel_buflen)); + } + break; + + case SOS_SYSCALL_ID_SEEK64: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_uaddr_t uaddr_offset; + sos_seek_whence_t whence; + sos_lsoffset_t kernel_offset, result_position; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(uaddr_offset), + SYSCALL_VAR32_PTR(whence)); + if (SOS_OK != retval) + break; + + /* Retrieve the value for "buflen" */ + retval = sos_memcpy_from_user((sos_vaddr_t)& kernel_offset, + uaddr_offset, + sizeof(kernel_offset)); + if (sizeof(kernel_offset) != retval) + { + retval = -SOS_EFAULT; + break; + } + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual seek */ + retval = sos_fs_seek(of, kernel_offset, whence, & result_position); + + /* Send successful number of bytes written to user */ + sos_memcpy_to_user(uaddr_offset, + (sos_vaddr_t)& result_position, + sizeof(kernel_offset)); + } + break; + + case SOS_SYSCALL_ID_FTRUNCATE64: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_lsoffset_t length; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(length)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual trunc */ + retval = sos_fs_ftruncate(of, length); + } + break; + + case SOS_SYSCALL_ID_FSMMAP: + { + sos_uaddr_t ptr_hint_uaddr; + sos_uaddr_t hint_uaddr; + sos_size_t length; + sos_ui32_t prot; + sos_ui32_t flags; + int fd; + sos_ui32_t offs64_hi; + sos_ui32_t offs64_lo; + sos_luoffset_t offset_in_resource; + struct sos_fs_opened_file * of; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get7args(user_ctxt, + SYSCALL_VAR32_PTR(ptr_hint_uaddr), + SYSCALL_VAR32_PTR(length), + SYSCALL_VAR32_PTR(prot), + SYSCALL_VAR32_PTR(flags), + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(offs64_hi), + SYSCALL_VAR32_PTR(offs64_lo)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Compute 64 bits offset value */ + offset_in_resource = offs64_hi; + offset_in_resource <<= 32; + offset_in_resource |= offs64_lo; + + retval = sos_memcpy_from_user((sos_vaddr_t)& hint_uaddr, + ptr_hint_uaddr, + sizeof(hint_uaddr)); + if (sizeof(hint_uaddr) != retval) + { + retval = -SOS_EFAULT; + break; + } + + retval = sos_fs_mmap(of, & hint_uaddr, length, prot, flags, + offset_in_resource); + if (SOS_OK == retval) + { + if (sizeof(hint_uaddr) + != sos_memcpy_to_user(ptr_hint_uaddr, + (sos_vaddr_t)& hint_uaddr, + sizeof(hint_uaddr))) + { + sos_umem_vmm_unmap(sos_process_get_address_space(proc), + hint_uaddr, length); + retval = -SOS_EFAULT; + } + } + + } + break; + + case SOS_SYSCALL_ID_FSYNC: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get1arg(user_ctxt, + SYSCALL_VAR32_PTR(fd)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual sync */ + retval = sos_fs_fsync(of); + } + break; + + case SOS_SYSCALL_ID_FCNTL: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_ui32_t cmd, arg; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(cmd), + SYSCALL_VAR32_PTR(arg)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual fcntl */ + retval = sos_fs_fcntl(of, cmd, arg); + } + break; + + case SOS_SYSCALL_ID_IOCTL: + { + struct sos_fs_opened_file * of; + struct sos_process * proc; + sos_ui32_t cmd, arg; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(fd), + SYSCALL_VAR32_PTR(cmd), + SYSCALL_VAR32_PTR(arg)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Do the actual ioctl */ + retval = sos_fs_ioctl(of, cmd, arg); + } + break; + + case SOS_SYSCALL_ID_CREAT: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_ui32_t access_rights; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(access_rights)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_creat(proc, + path, len, + access_rights); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_LINK: + case SOS_SYSCALL_ID_RENAME: + { + sos_uaddr_t user_oldpath, user_newpath; + sos_size_t oldpathlen, newpathlen; + char * kernel_oldpath, * kernel_newpath; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get4args(user_ctxt, + SYSCALL_VAR32_PTR(user_oldpath), + SYSCALL_VAR32_PTR(oldpathlen), + SYSCALL_VAR32_PTR(user_newpath), + SYSCALL_VAR32_PTR(newpathlen)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&kernel_oldpath, + user_oldpath, + oldpathlen, 0); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&kernel_newpath, + user_newpath, + newpathlen, 0); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t) kernel_oldpath); + break; + } + + if (syscall_id == SOS_SYSCALL_ID_LINK) + retval = sos_fs_link(proc, + kernel_oldpath, oldpathlen, + kernel_newpath, newpathlen); + else + retval = sos_fs_rename(proc, + kernel_oldpath, oldpathlen, + kernel_newpath, newpathlen); + sos_kfree((sos_vaddr_t)kernel_oldpath); + sos_kfree((sos_vaddr_t)kernel_newpath); + } + break; + + case SOS_SYSCALL_ID_UNLINK: + { + sos_uaddr_t user_str; + sos_size_t len; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_unlink(proc, + path, len); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_SYMLINK: + { + sos_uaddr_t user_path, user_targetpath; + sos_size_t pathlen, targetpathlen; + char * kernel_path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get4args(user_ctxt, + SYSCALL_VAR32_PTR(user_path), + SYSCALL_VAR32_PTR(pathlen), + SYSCALL_VAR32_PTR(user_targetpath), + SYSCALL_VAR32_PTR(targetpathlen)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&kernel_path, + user_path, + pathlen, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_symlink(proc, + kernel_path, pathlen, + user_targetpath, targetpathlen); + sos_kfree((sos_vaddr_t)kernel_path); + } + break; + + case SOS_SYSCALL_ID_MKNOD: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_ui32_t access_rights; + int type; + char * path; + struct sos_fs_dev_id_t dev_id; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get6args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(type), + SYSCALL_VAR32_PTR(access_rights), + SYSCALL_VAR32_PTR(dev_id.device_class), + SYSCALL_VAR32_PTR(dev_id.device_instance)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + switch (type) + { + case SOS_FS_NODE_REGULAR_FILE: + retval = sos_fs_creat(proc, path, len, access_rights); + break; + + case SOS_FS_NODE_DIRECTORY: + retval = sos_fs_mkdir(proc, path, len, access_rights); + break; + + case SOS_FS_NODE_SYMLINK: + retval = -SOS_ENOSUP; + break; + + case SOS_FS_NODE_DEVICE_CHAR: + case SOS_FS_NODE_DEVICE_BLOCK: + retval = sos_fs_mknod(proc, + path, len, type, access_rights, &dev_id); + break; + + default: + retval = -SOS_EINVAL; + break; + } + + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_MKDIR: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_ui32_t access_rights; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(access_rights)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_mkdir(proc, + path, len, access_rights); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_RMDIR: + { + sos_uaddr_t user_str; + sos_size_t len; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_rmdir(proc, path, len); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_CHMOD: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_ui32_t access_rights; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get3args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(access_rights)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_chmod(proc, path, len, access_rights); + sos_kfree((sos_vaddr_t)path); + } + break; + + case SOS_SYSCALL_ID_STAT64: + { + sos_uaddr_t user_str; + sos_size_t len; + sos_uaddr_t user_stat_struct; + struct sos_fs_stat kernel_stat_struct; + int nofollow; + char * path; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get4args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len), + SYSCALL_VAR32_PTR(nofollow), + SYSCALL_VAR32_PTR(user_stat_struct)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_stat(proc, path, len, nofollow, & kernel_stat_struct); + sos_kfree((sos_vaddr_t)path); + if (SOS_OK != retval) + break; + + if (sizeof(kernel_stat_struct) + != sos_memcpy_to_user(user_stat_struct, + (sos_vaddr_t) & kernel_stat_struct, + sizeof(kernel_stat_struct))) + retval = -SOS_EFAULT; + } + break; + + case SOS_SYSCALL_ID_CHROOT: + case SOS_SYSCALL_ID_CHDIR: + { + sos_uaddr_t user_str; + sos_size_t len; + char * path; + struct sos_fs_opened_file * of, * old_of; + struct sos_process * proc; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get2args(user_ctxt, + SYSCALL_VAR32_PTR(user_str), + SYSCALL_VAR32_PTR(len)); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(&path, user_str, len, 0); + if (SOS_OK != retval) + break; + + retval = sos_fs_open(proc, + path, len, + SOS_FS_OPEN_DIRECTORY, + SOS_FS_OPEN_READ, + & of); + sos_kfree((sos_vaddr_t)path); + if (SOS_OK != retval) + break; + + if (syscall_id == SOS_SYSCALL_ID_CHROOT) + retval = sos_process_chroot(proc, of, & old_of); + else + retval = sos_process_chdir(proc, of, & old_of); + + if (retval < 0) + { + sos_fs_close(of); + break; + } + + sos_fs_close(old_of); + } + break; + + case SOS_SYSCALL_ID_FCHDIR: + { + struct sos_fs_opened_file * of, * new_of, * old_of; + struct sos_process * proc; + int fd; + + proc = sos_thread_get_current()->process; + retval = sos_syscall_get1arg(user_ctxt, + SYSCALL_VAR32_PTR(fd)); + if (SOS_OK != retval) + break; + + of = sos_process_get_opened_file(proc, fd); + if (NULL == of) + { + retval = -SOS_EBADF; + break; + } + + /* Duplicate this FD */ + retval = sos_fs_duplicate_opened_file(of, proc, & new_of); + if (SOS_OK != retval) + break; + + /* Do the actual chdir */ + retval = sos_process_chdir(proc, new_of, & old_of); + if (retval < 0) + { + sos_fs_close(new_of); + break; + } + + sos_fs_close(old_of); + } + break; + + case SOS_SYSCALL_ID_BOCHS_WRITE: + { + sos_uaddr_t user_str; + sos_size_t len; + char * str; + retval = sos_syscall_get2args(user_ctxt, & user_str, & len); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(& str, user_str, len + 1, 0); + if (SOS_OK == retval) + { + sos_bochs_printf("THR 0x%x: ", + (unsigned)sos_thread_get_current()); + retval = sos_bochs_putstring(str); + retval = len; + sos_kfree((sos_vaddr_t)str); + } + } + break; + + + /* *********************************************** + * Debug syscalls (will be removed in the future) + */ + + + /** + * Syscall 4012: hexdump of a user-space memory region + * args: addr_start size, retval=ignored + */ + case 4012: + { + sos_uaddr_t user_str; + unsigned int len; + unsigned char * str; + + retval = sos_syscall_get2args(user_ctxt, & user_str, & len); + if (SOS_OK != retval) + break; + + str = (unsigned char*)sos_kmalloc(len, 0); + if (str) + { + sos_bochs_printf("THR %p, Hexdump(0x%x, %d):\n", + sos_thread_get_current(), user_str, len); + retval = sos_memcpy_from_user((sos_vaddr_t) str, user_str, len); + sos_bochs_printf(" (Successfully copied %d out of %d)\n", + retval, len); + if (retval > 0) + sos_bochs_hexdump(str, retval); + + sos_kfree((sos_vaddr_t)str); + } + else + retval = -SOS_ENOMEM; + } + break; + + + /** + * Syscall 4004: lists the VR of the current thread's address space + * args: debug_string, retval=ignored + */ + case 4004: + { + sos_uaddr_t ustr; + char * kstr; + struct sos_umem_vmm_as * my_as; + + retval = sos_syscall_get1arg(user_ctxt, & ustr); + if (SOS_OK != retval) + break; + + retval = sos_strndup_from_user(& kstr, ustr, 256, 0); + if (SOS_OK != retval) + break; + + extern void sos_dump_as(const struct sos_umem_vmm_as *, const char *); + my_as + = sos_process_get_address_space(sos_thread_get_current()->process); + sos_dump_as(my_as, kstr); + sos_kfree((sos_vaddr_t)kstr); + } + break; + + + /** + * Syscall 4008: dump the list of processes in the system + * args: none, retval=ignored + */ + case 4008: + { + extern void sos_process_dumplist(void); + sos_process_dumplist(); + retval = SOS_OK; + } + break; + + default: + sos_bochs_printf("Syscall: UNKNOWN[%d]\n", syscall_id); + retval = -SOS_ENOSUP; + } + + return retval; +} diff --git a/sos/syscall.h b/sos/syscall.h new file mode 100644 index 0000000..881aff1 --- /dev/null +++ b/sos/syscall.h @@ -0,0 +1,121 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_SYSCALL_H_ +#define _SOS_SYSCALL_H_ + +/** + * @file syscall.h + * + * SOS Syscalls identifiers and handler. The handler is called by the + * underlying assembler routine (see hwcore/syscall.h) + */ + + +/** + * The user services offered by the SOS kernel + */ +#define SOS_SYSCALL_ID_EXIT 67 /**< Args: retval, retval=NONE */ + + +/** + * Basic low-level user thread API + */ +#define SOS_SYSCALL_ID_NEW_THREAD 128 /**< Args: start_func start_arg1 start_arg2 stack_size, retval=errno */ +#define SOS_SYSCALL_ID_NANOSLEEP 129 /**< Args: sec nanosec, retval=errno */ + + +/** + * User memory mappings management + */ +#define SOS_SYSCALL_ID_GETPID 256 /**< Args: NONE, retval=cur. proc. PID */ +#define SOS_SYSCALL_ID_FORK 257 /**< Args: NONE, retval={father:child_pd, child:0} */ +#define SOS_SYSCALL_ID_EXEC 258 /**< Args: ptr_exec_path strlen_exec_path addr_args len_args, retval=errno */ +#define SOS_SYSCALL_ID_MUNMAP 260 /**< Args: start_uaddr size, retval=errno */ +#define SOS_SYSCALL_ID_MPROTECT 261 /**< Args: start_uaddr size access_rights, retval=errno */ +#define SOS_SYSCALL_ID_MRESIZE 262 /**< Args: old_uaddr old_size ptr_new_uaddr new_size flags, retval=errno */ +#define SOS_SYSCALL_ID_MSYNC 263 /**< Args: start_uaddr size flags, retval=errno */ + +/** + * Heap management + */ +#define SOS_SYSCALL_ID_BRK 303 /**< Args: 0/new_top_heap, retval=top_heap */ + +/** + * File system interface + */ +#define SOS_SYSCALL_ID_MOUNT 555 /**< Args: uaddr_src srclen uaddr_dst dstlen uaddr_fstype flags uaddr_args, retval=errno */ +#define SOS_SYSCALL_ID_UMOUNT 556 /**< Args: uaddr_path pathlen, retval=errno */ +#define SOS_SYSCALL_ID_SYNC 557 /**< Args: none, retval=errno */ +#define SOS_SYSCALL_ID_VFSTAT64 558 /**< Args: uaddr_path pathlen uaddr_vfstat_struct, retval=errno */ + +#define SOS_SYSCALL_ID_OPEN 559 /**< Args: path pathlen flags access_rights, retval=fd */ +#define SOS_SYSCALL_ID_CLOSE 560 /**< Args: fd, retval=errno */ +#define SOS_SYSCALL_ID_READ 561 /**< Args: fd uaddr_buf uaddr_buflen, retval=errno */ +#define SOS_SYSCALL_ID_READDIR 562 /**< Args: fd uaddr_dirent, retval=errno */ +#define SOS_SYSCALL_ID_WRITE 563 /**< Args: fd uaddr_buf uaddr_buflen, retval=errno */ +#define SOS_SYSCALL_ID_SEEK64 564 /**< Args: fd uaddr_offset whence, retval=errno */ +#define SOS_SYSCALL_ID_FTRUNCATE64 565 /**< Args: fd length, retval=errno */ +#define SOS_SYSCALL_ID_FSMMAP 566 /**< Args: &hint len prot flags fd uoffs64_hi uoffs64_lo, retval=errno */ +#define SOS_SYSCALL_ID_FSYNC 567 /**< Args: fd, retval=errno */ +#define SOS_SYSCALL_ID_FCNTL 568 /**< Args: fd cmd arg, retval=errno */ +/* fcntl possible commands */ +# define SOS_FCNTL_DUPFD 0x4201 +# define SOS_FCNTL_GETFD 0x4202 +# define SOS_FCNTL_SETFD 0x4203 +# define SOS_FCNTL_GETFL 0x4204 +# define SOS_FCNTL_SETFL 0x4205 +#define SOS_SYSCALL_ID_IOCTL 569 /**< Args: fd cmd arg, retval=errno */ + +#define SOS_SYSCALL_ID_CREAT 570 /**< Args: uaddr_path pathlen access_rights, retval=errno */ +#define SOS_SYSCALL_ID_LINK 571 /**< Args: uaddr_oldpath oldpathlen uaddr_newpath newpathlen, retval=errno */ +#define SOS_SYSCALL_ID_RENAME 572 /**< Args: uaddr_oldpath oldpathlen uaddr_newpath newpathlen, retval=errno */ +#define SOS_SYSCALL_ID_UNLINK 573 /**< Args: uaddr_path pathlen, retval=errno */ +#define SOS_SYSCALL_ID_SYMLINK 574 /**< Args: uaddr_path pathlen uaddr_target targetlen, retval=errno */ +#define SOS_SYSCALL_ID_MKNOD 575 /**< Args: uaddr_path pathlen type access_rights major minor, retval=errno */ + +#define SOS_SYSCALL_ID_MKDIR 576 /**< Args: uaddr_path pathlen access_rights, retval=errno */ +#define SOS_SYSCALL_ID_RMDIR 577 /**< Args: uaddr_path pathlen, retval=errno */ + +#define SOS_SYSCALL_ID_CHMOD 578 /**< Args: uaddr_path pathlen access_rights, retval=errno */ +#define SOS_SYSCALL_ID_STAT64 579 /**< Args: uaddr_path pathlen nofollow uaddr_stat_struct, retval=errno */ + +#define SOS_SYSCALL_ID_CHROOT 580 /**< Args: uaddr_path pathlen, retval=errno */ +#define SOS_SYSCALL_ID_CHDIR 581 /**< Args: uaddr_path pathlen, retval=errno */ +#define SOS_SYSCALL_ID_FCHDIR 582 /**< Args: fd, retval=errno */ + + +#define SOS_SYSCALL_ID_BOCHS_WRITE 43 /**< Args: string, retval=num_printed */ + + +#if defined(KERNEL_SOS) && !defined(ASM_SOURCE) + +#include +#include + +/** + * "The" SOS syscall handler + * + * @param syscall_id The identifier of the syscall service + * @param user_ctxt The user context making the syscall + */ +sos_ret_t sos_do_syscall(int syscall_id, + const struct sos_cpu_state *user_ctxt); + +#endif /* defined(KERNEL_SOS) && !defined(ASM_SOURCE) */ + +#endif /* _SOS_SYSCALL_H_ */ diff --git a/sos/thread.c b/sos/thread.c new file mode 100644 index 0000000..76c8a45 --- /dev/null +++ b/sos/thread.c @@ -0,0 +1,899 @@ +/* Copyright (C) 2004,2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "thread.h" + + +/** + * The size of the stack of a kernel thread + */ +#define SOS_THREAD_KERNEL_STACK_SIZE (1*SOS_PAGE_SIZE) + + +/** + * The identifier of the thread currently running on CPU. + * + * We only support a SINGLE processor, ie a SINGLE thread + * running at any time in the system. This greatly simplifies the + * implementation of the system, since we don't have to complicate + * things in order to retrieve the identifier of the threads running + * on the CPU. On multiprocessor systems the current_thread below is + * an array indexed by the id of the CPU, so that the challenge is to + * retrieve the identifier of the CPU. This is usually done based on + * the stack address (Linux implementation) or on some form of TLS + * ("Thread Local Storage": can be implemented by way of LDTs for the + * processes, accessed through the fs or gs registers). + */ +static volatile struct sos_thread *current_thread = NULL; + + +/* + * The list of threads currently in the system. + * + * @note We could have used current_thread for that... + */ +static struct sos_thread *thread_list = NULL; + + +/** + * The Cache of thread structures + */ +static struct sos_kslab_cache *cache_thread; + + +struct sos_thread *sos_thread_get_current() +{ + SOS_ASSERT_FATAL(current_thread->state == SOS_THR_RUNNING); + return (struct sos_thread*)current_thread; +} + + +inline static sos_ret_t _set_current(struct sos_thread *thr) +{ + SOS_ASSERT_FATAL(thr->state == SOS_THR_READY); + current_thread = thr; + current_thread->state = SOS_THR_RUNNING; + return SOS_OK; +} + + +sos_ret_t sos_thread_subsystem_setup(sos_vaddr_t init_thread_stack_base_addr, + sos_size_t init_thread_stack_size) +{ + struct sos_thread *myself; + + /* Allocate the cache of threads */ + cache_thread = sos_kmem_cache_create("thread", + sizeof(struct sos_thread), + 2, + 0, + SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO); + if (! cache_thread) + return -SOS_ENOMEM; + + /* Allocate a new thread structure for the current running thread */ + myself = (struct sos_thread*) sos_kmem_cache_alloc(cache_thread, + SOS_KSLAB_ALLOC_ATOMIC); + if (! myself) + return -SOS_ENOMEM; + + /* Initialize the thread attributes */ + strzcpy(myself->name, "[kinit]", SOS_THR_MAX_NAMELEN); + myself->state = SOS_THR_CREATED; + myself->priority = SOS_SCHED_PRIO_LOWEST; + myself->kernel_stack_base_addr = init_thread_stack_base_addr; + myself->kernel_stack_size = init_thread_stack_size; + + /* Do some stack poisoning on the bottom of the stack, if needed */ + sos_cpu_state_prepare_detect_kernel_stack_overflow(myself->cpu_state, + myself->kernel_stack_base_addr, + myself->kernel_stack_size); + + /* Add the thread in the global list */ + list_singleton_named(thread_list, myself, gbl_prev, gbl_next); + + /* Ok, now pretend that the running thread is ourselves */ + myself->state = SOS_THR_READY; + _set_current(myself); + + return SOS_OK; +} + + +struct sos_thread * +sos_create_kernel_thread(const char *name, + sos_kernel_thread_start_routine_t start_func, + void *start_arg, + sos_sched_priority_t priority) +{ + __label__ undo_creation; + sos_ui32_t flags; + struct sos_thread *new_thread; + + if (! start_func) + return NULL; + if (! SOS_SCHED_PRIO_IS_VALID(priority)) + return NULL; + + /* Allocate a new thread structure for the current running thread */ + new_thread + = (struct sos_thread*) sos_kmem_cache_alloc(cache_thread, + SOS_KSLAB_ALLOC_ATOMIC); + if (! new_thread) + return NULL; + + /* Initialize the thread attributes */ + strzcpy(new_thread->name, ((name)?name:"[NONAME]"), SOS_THR_MAX_NAMELEN); + new_thread->state = SOS_THR_CREATED; + new_thread->priority = priority; + + /* Allocate the stack for the new thread */ + new_thread->kernel_stack_base_addr = sos_kmalloc(SOS_THREAD_KERNEL_STACK_SIZE, 0); + new_thread->kernel_stack_size = SOS_THREAD_KERNEL_STACK_SIZE; + if (! new_thread->kernel_stack_base_addr) + goto undo_creation; + + /* Initialize the CPU context of the new thread */ + if (SOS_OK + != sos_cpu_kstate_init(& new_thread->cpu_state, + (sos_cpu_kstate_function_arg1_t*) start_func, + (sos_ui32_t) start_arg, + new_thread->kernel_stack_base_addr, + new_thread->kernel_stack_size, + (sos_cpu_kstate_function_arg1_t*) sos_thread_exit, + (sos_ui32_t) NULL)) + goto undo_creation; + + /* Add the thread in the global list */ + sos_disable_IRQs(flags); + list_add_tail_named(thread_list, new_thread, gbl_prev, gbl_next); + sos_restore_IRQs(flags); + + /* Mark the thread ready */ + if (SOS_OK != sos_sched_set_ready(new_thread)) + goto undo_creation; + + /* Normal non-erroneous end of function */ + return new_thread; + + undo_creation: + if (new_thread->kernel_stack_base_addr) + sos_kfree((sos_vaddr_t) new_thread->kernel_stack_base_addr); + sos_kmem_cache_free((sos_vaddr_t) new_thread); + return NULL; +} + + +/** + * Helper function to create a new user thread. If model_thread is + * given, then the new thread will be the copy of this + * thread. Otherwise the thread will have its initial SP/PC correctly + * initialized with the user_initial_PC/SP arguments + */ +static struct sos_thread * +create_user_thread(const char *name, + struct sos_process *process, + const struct sos_thread * model_thread, + const struct sos_cpu_state * model_uctxt, + sos_uaddr_t user_initial_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_sched_priority_t priority) +{ + __label__ undo_creation; + sos_ui32_t flags; + struct sos_thread *new_thread; + + if (model_thread) + { + SOS_ASSERT_FATAL(model_uctxt); + } + else + { + if (! SOS_SCHED_PRIO_IS_VALID(priority)) + return NULL; + } + + /* For a user thread, the process must be given */ + if (! process) + return NULL; + + /* Allocate a new thread structure for the current running thread */ + new_thread + = (struct sos_thread*) sos_kmem_cache_alloc(cache_thread, + SOS_KSLAB_ALLOC_ATOMIC); + if (! new_thread) + return NULL; + + /* Initialize the thread attributes */ + strzcpy(new_thread->name, ((name)?name:"[NONAME]"), SOS_THR_MAX_NAMELEN); + new_thread->state = SOS_THR_CREATED; + if (model_thread) + new_thread->priority = model_thread->priority; + else + new_thread->priority = priority; + + /* Allocate the stack for the new thread */ + new_thread->kernel_stack_base_addr = sos_kmalloc(SOS_THREAD_KERNEL_STACK_SIZE, 0); + new_thread->kernel_stack_size = SOS_THREAD_KERNEL_STACK_SIZE; + if (! new_thread->kernel_stack_base_addr) + goto undo_creation; + + /* Initialize the CPU context of the new thread */ + if (model_thread) + { + if (SOS_OK + != sos_cpu_ustate_duplicate(& new_thread->cpu_state, + model_uctxt, + user_start_arg1, + new_thread->kernel_stack_base_addr, + new_thread->kernel_stack_size)) + goto undo_creation; + } + else + { + if (SOS_OK + != sos_cpu_ustate_init(& new_thread->cpu_state, + user_initial_PC, + user_start_arg1, + user_start_arg2, + user_initial_SP, + new_thread->kernel_stack_base_addr, + new_thread->kernel_stack_size)) + goto undo_creation; + } + + /* Attach the new thread to the process */ + if (SOS_OK != sos_process_register_thread(process, new_thread)) + goto undo_creation; + + /* Add the thread in the global list */ + sos_disable_IRQs(flags); + list_add_tail_named(thread_list, new_thread, gbl_prev, gbl_next); + sos_restore_IRQs(flags); + + /* Mark the thread ready */ + if (SOS_OK != sos_sched_set_ready(new_thread)) + goto undo_creation; + + /* Normal non-erroneous end of function */ + return new_thread; + + undo_creation: + if (new_thread->kernel_stack_base_addr) + sos_kfree((sos_vaddr_t) new_thread->kernel_stack_base_addr); + sos_kmem_cache_free((sos_vaddr_t) new_thread); + return NULL; +} + + +struct sos_thread * +sos_create_user_thread(const char *name, + struct sos_process *process, + sos_uaddr_t user_initial_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_sched_priority_t priority) +{ + return create_user_thread(name, process, NULL, NULL, + user_initial_PC, + user_start_arg1, + user_start_arg2, + user_initial_SP, + priority); +} + + +/** + * Create a new user thread, copy of the given user thread with the + * given user context + */ +struct sos_thread * +sos_duplicate_user_thread(const char *name, + struct sos_process *process, + const struct sos_thread * model_thread, + const struct sos_cpu_state * model_uctxt, + sos_ui32_t retval) +{ + return create_user_thread(name, process, model_thread, model_uctxt, + 0, retval, 0, 0, 0); +} + + +/** + * Helper function to switch to the correct MMU configuration to suit + * the_thread's needs. + * - When switching to a user-mode thread, force the reconfiguration + * of the MMU + * - When switching to a kernel-mode thread, only change the MMU + * configuration if the thread was squatting someone else's space + */ +static void _prepare_mm_context(struct sos_thread *the_thread) +{ + /* Going to restore a thread in user mode ? */ + if (sos_cpu_context_is_in_user_mode(the_thread->cpu_state) + == TRUE) + { + /* Yes: force the MMU to be correctly setup with the correct + user's address space */ + + /* The thread should be a user thread */ + SOS_ASSERT_FATAL(the_thread->process != NULL); + + /* It should not squat any other's address space */ + SOS_ASSERT_FATAL(the_thread->squatted_address_space == NULL); + + /* Perform an MMU context switch if needed */ + sos_umem_vmm_set_current_as(sos_process_get_address_space(the_thread->process)); + } + + /* Restore the address space currently in use */ + else + sos_umem_vmm_set_current_as(the_thread->squatted_address_space); +} + + +/** Function called after thr has terminated. Called from inside the context + of another thread, interrupts disabled */ +static void delete_thread(struct sos_thread *thr) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + list_delete_named(thread_list, thr, gbl_prev, gbl_next); + sos_restore_IRQs(flags); + + sos_kfree((sos_vaddr_t) thr->kernel_stack_base_addr); + + /* Not allowed to squat any user space at deletion time */ + SOS_ASSERT_FATAL(NULL == thr->squatted_address_space); + + /* For a user thread: remove the thread from the process threads' list */ + if (thr->process) + SOS_ASSERT_FATAL(SOS_OK == sos_process_unregister_thread(thr)); + + memset(thr, 0x0, sizeof(struct sos_thread)); + sos_kmem_cache_free((sos_vaddr_t) thr); +} + + +void sos_thread_exit() +{ + sos_ui32_t flags; + struct sos_thread *myself, *next_thread; + + /* Interrupt handlers are NOT allowed to exit the current thread ! */ + SOS_ASSERT_FATAL(! sos_servicing_irq()); + + myself = sos_thread_get_current(); + + /* Refuse to end the current executing thread if it still holds a + resource ! */ + SOS_ASSERT_FATAL(list_is_empty_named(myself->kwaitq_list, + prev_entry_for_thread, + next_entry_for_thread)); + + /* Prepare to run the next thread */ + sos_disable_IRQs(flags); + myself->state = SOS_THR_ZOMBIE; + next_thread = sos_reschedule(myself, FALSE); + + /* Make sure that the next_thread is valid */ + sos_cpu_state_detect_kernel_stack_overflow(next_thread->cpu_state, + next_thread->kernel_stack_base_addr, + next_thread->kernel_stack_size); + + /* + * Perform an MMU context switch if needed + */ + _prepare_mm_context(next_thread); + + /* No need for sos_restore_IRQs() here because the IRQ flag will be + restored to that of the next thread upon context switch */ + + /* Immediate switch to next thread */ + _set_current(next_thread); + sos_cpu_context_exit_to(next_thread->cpu_state, + (sos_cpu_kstate_function_arg1_t*) delete_thread, + (sos_ui32_t) myself); +} + + +sos_sched_priority_t sos_thread_get_priority(struct sos_thread *thr) +{ + if (! thr) + thr = (struct sos_thread*)current_thread; + + return thr->priority; +} + + +sos_thread_state_t sos_thread_get_state(struct sos_thread *thr) +{ + if (! thr) + thr = (struct sos_thread*)current_thread; + + return thr->state; +} + + +typedef enum { YIELD_MYSELF, BLOCK_MYSELF } switch_type_t; +/** + * Helper function to initiate a context switch in case the current + * thread becomes blocked, waiting for a timeout, or calls yield. + */ +static sos_ret_t _switch_to_next_thread(switch_type_t operation) +{ + struct sos_thread *myself, *next_thread; + + SOS_ASSERT_FATAL(current_thread->state == SOS_THR_RUNNING); + + /* Interrupt handlers are NOT allowed to block ! */ + SOS_ASSERT_FATAL(! sos_servicing_irq()); + + myself = (struct sos_thread*)current_thread; + + /* Make sure that if we are to be marked "BLOCKED", we have any + reason of effectively being blocked */ + if (BLOCK_MYSELF == operation) + { + myself->state = SOS_THR_BLOCKED; + } + + /* Identify the next thread */ + next_thread = sos_reschedule(myself, YIELD_MYSELF == operation); + + /* Avoid context switch if the context does not change */ + if (myself != next_thread) + { + /* Sanity checks for the next thread */ + sos_cpu_state_detect_kernel_stack_overflow(next_thread->cpu_state, + next_thread->kernel_stack_base_addr, + next_thread->kernel_stack_size); + + /* + * Perform an MMU context switch if needed + */ + _prepare_mm_context(next_thread); + + /* + * Actual CPU context switch + */ + _set_current(next_thread); + sos_cpu_context_switch(& myself->cpu_state, next_thread->cpu_state); + + /* Back here ! */ + SOS_ASSERT_FATAL(current_thread == myself); + SOS_ASSERT_FATAL(current_thread->state == SOS_THR_RUNNING); + } + else + { + /* No context switch but still update ID of current thread */ + _set_current(next_thread); + } + + return SOS_OK; +} + + +/** + * Helper function to change the thread's priority in all the + * waitqueues associated with the thread. + */ +static sos_ret_t _change_waitq_priorities(struct sos_thread *thr, + sos_sched_priority_t priority) +{ + struct sos_kwaitq_entry *kwq_entry; + int nb_waitqs; + + list_foreach_forward_named(thr->kwaitq_list, kwq_entry, nb_waitqs, + prev_entry_for_thread, next_entry_for_thread) + { + SOS_ASSERT_FATAL(SOS_OK == sos_kwaitq_change_priority(kwq_entry->kwaitq, + kwq_entry, + priority)); + } + + return SOS_OK; +} + + +sos_ret_t sos_thread_set_priority(struct sos_thread *thr, + sos_sched_priority_t priority) +{ + __label__ exit_set_prio; + sos_ui32_t flags; + sos_ret_t retval; + + + if (! SOS_SCHED_PRIO_IS_VALID(priority)) + return -SOS_EINVAL; + + if (! thr) + thr = (struct sos_thread*)current_thread; + + sos_disable_IRQs(flags); + + /* Signal kwaitq subsystem that the priority of the thread in all + the waitq it is waiting in should be updated */ + retval = _change_waitq_priorities(thr, priority); + if (SOS_OK != retval) + goto exit_set_prio; + + /* Signal scheduler that the thread, currently in a waiting list, + should take into account the change of priority */ + if (SOS_THR_READY == thr->state) + retval = sos_sched_change_priority(thr, priority); + + /* Update priority */ + thr->priority = priority; + + exit_set_prio: + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t sos_thread_yield() +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + + retval = _switch_to_next_thread(YIELD_MYSELF); + + sos_restore_IRQs(flags); + return retval; +} + + +/** + * Internal sleep timeout management + */ +struct sleep_timeout_params +{ + struct sos_thread *thread_to_wakeup; + sos_bool_t timeout_triggered; +}; + + +/** + * Callback called when a timeout happened + */ +static void sleep_timeout(struct sos_timeout_action *act) +{ + struct sleep_timeout_params *sleep_timeout_params + = (struct sleep_timeout_params*) act->routine_data; + + /* Signal that we have been woken up by the timeout */ + sleep_timeout_params->timeout_triggered = TRUE; + + /* Mark the thread ready */ + SOS_ASSERT_FATAL(SOS_OK == + sos_thread_force_unblock(sleep_timeout_params + ->thread_to_wakeup)); +} + + +sos_ret_t sos_thread_sleep(struct sos_time *timeout) +{ + sos_ui32_t flags; + struct sleep_timeout_params sleep_timeout_params; + struct sos_timeout_action timeout_action; + sos_ret_t retval; + + /* Block forever if no timeout is given */ + if (NULL == timeout) + { + sos_disable_IRQs(flags); + retval = _switch_to_next_thread(BLOCK_MYSELF); + sos_restore_IRQs(flags); + + return retval; + } + + /* Initialize the timeout action */ + sos_time_init_action(& timeout_action); + + /* Prepare parameters used by the sleep timeout callback */ + sleep_timeout_params.thread_to_wakeup + = (struct sos_thread*)current_thread; + sleep_timeout_params.timeout_triggered = FALSE; + + sos_disable_IRQs(flags); + + /* Now program the timeout ! */ + SOS_ASSERT_FATAL(SOS_OK == + sos_time_register_action_relative(& timeout_action, + timeout, + sleep_timeout, + & sleep_timeout_params)); + + /* Prepare to block: wait for sleep_timeout() to wakeup us in the + timeout kwaitq, or for someone to wake us up in any other + waitq */ + retval = _switch_to_next_thread(BLOCK_MYSELF); + /* Unblocked by something ! */ + + /* Unblocked by timeout ? */ + if (sleep_timeout_params.timeout_triggered) + { + /* Yes */ + SOS_ASSERT_FATAL(sos_time_is_zero(& timeout_action.timeout)); + retval = SOS_OK; + } + else + { + /* No: We have probably been woken up while in some other + kwaitq */ + SOS_ASSERT_FATAL(SOS_OK == sos_time_unregister_action(& timeout_action)); + retval = -SOS_EINTR; + } + + sos_restore_IRQs(flags); + + /* Update the remaining timeout */ + memcpy(timeout, & timeout_action.timeout, sizeof(struct sos_time)); + + return retval; +} + + +sos_ret_t sos_thread_force_unblock(struct sos_thread *thread) +{ + sos_ret_t retval; + sos_ui32_t flags; + + if (! thread) + return -SOS_EINVAL; + + sos_disable_IRQs(flags); + + /* Thread already woken up ? */ + retval = SOS_OK; + switch(sos_thread_get_state(thread)) + { + case SOS_THR_RUNNING: + case SOS_THR_READY: + /* Do nothing */ + break; + + case SOS_THR_ZOMBIE: + retval = -SOS_EFATAL; + break; + + default: + retval = sos_sched_set_ready(thread); + break; + } + + sos_restore_IRQs(flags); + + return retval; +} + + +void sos_thread_dump_backtrace(sos_bool_t on_console, + sos_bool_t on_bochs) +{ + sos_vaddr_t stack_bottom = current_thread->kernel_stack_base_addr; + sos_size_t stack_size = current_thread->kernel_stack_size; + + void backtracer(sos_vaddr_t PC, + sos_vaddr_t params, + sos_ui32_t depth, + void *custom_arg) + { + sos_ui32_t invalid = 0xffffffff, *arg1, *arg2, *arg3, *arg4; + + /* Get the address of the first 3 arguments from the + frame. Among these arguments, 0, 1, 2, 3 arguments might be + meaningful (depending on how many arguments the function may + take). */ + arg1 = (sos_ui32_t*)params; + arg2 = (sos_ui32_t*)(params+4); + arg3 = (sos_ui32_t*)(params+8); + arg4 = (sos_ui32_t*)(params+12); + + /* Make sure the addresses of these arguments fit inside the + stack boundaries */ +#define INTERVAL_OK(b,v,u) ( ((b) <= (sos_vaddr_t)(v)) \ + && ((sos_vaddr_t)(v) < (u)) ) + if (!INTERVAL_OK(stack_bottom, arg1, stack_bottom + stack_size)) + arg1 = &invalid; + if (!INTERVAL_OK(stack_bottom, arg2, stack_bottom + stack_size)) + arg2 = &invalid; + if (!INTERVAL_OK(stack_bottom, arg3, stack_bottom + stack_size)) + arg3 = &invalid; + if (!INTERVAL_OK(stack_bottom, arg4, stack_bottom + stack_size)) + arg4 = &invalid; + + /* Print the function context for this frame */ + if (on_bochs) + sos_bochs_printf("[%d] PC=0x%x arg1=0x%x arg2=0x%x arg3=0x%x\n", + (unsigned)depth, (unsigned)PC, + (unsigned)*arg1, (unsigned)*arg2, + (unsigned)*arg3); + + if (on_console) + sos_x86_videomem_printf(23-depth, 3, + SOS_X86_VIDEO_BG_BLUE + | SOS_X86_VIDEO_FG_LTGREEN, + "[%d] PC=0x%x arg1=0x%x arg2=0x%x arg3=0x%x arg4=0x%x", + (unsigned)depth, PC, + (unsigned)*arg1, (unsigned)*arg2, + (unsigned)*arg3, (unsigned)*arg4); + + } + + sos_backtrace(NULL, 15, stack_bottom, stack_size, + backtracer, NULL); +} + + + +/* ********************************************** + * Restricted functions + */ + + +sos_ret_t +sos_thread_prepare_user_space_access(struct sos_umem_vmm_as * dest_as, + sos_vaddr_t fixup_retvaddr) +{ + sos_ret_t retval; + sos_ui32_t flags; + + if (! dest_as) + { + /* Thread is not a user thread: do nothing */ + if (! current_thread->process) + return -SOS_EINVAL; + + dest_as = sos_process_get_address_space(current_thread->process); + } + + sos_disable_IRQs(flags); + SOS_ASSERT_FATAL(NULL == current_thread->squatted_address_space); + SOS_ASSERT_FATAL(0 == current_thread->fixup_uaccess.return_vaddr); + + /* Change the MMU configuration and init the fixup return address */ + retval = sos_umem_vmm_set_current_as(dest_as); + if (SOS_OK == retval) + { + current_thread->squatted_address_space = dest_as; + current_thread->fixup_uaccess.return_vaddr = fixup_retvaddr; + current_thread->fixup_uaccess.faulted_uaddr = 0; + } + + sos_restore_IRQs(flags); + return retval; +} + + +sos_ret_t +sos_thread_end_user_space_access(void) +{ + sos_ret_t retval; + sos_ui32_t flags; + + sos_disable_IRQs(flags); + SOS_ASSERT_FATAL(NULL != current_thread->squatted_address_space); + + /* Don't impose anything regarding the current MMU configuration anymore */ + current_thread->fixup_uaccess.return_vaddr = 0; + current_thread->fixup_uaccess.faulted_uaddr = 0; + + retval = sos_umem_vmm_set_current_as(NULL); + current_thread->squatted_address_space = NULL; + + sos_restore_IRQs(flags); + return retval; +} + + +void sos_thread_prepare_syscall_switch_back(struct sos_cpu_state *cpu_state) +{ + /* Don't preempt the current thread */ + + /* + * Save the state of the interrupted context to make sure that: + * - The list of threads correctly reflects that the thread is back + * in user mode + * - _prepare_mm_context() deals with the correct mm_context + */ + current_thread->cpu_state = cpu_state; + + /* Perform an MMU context switch if needed */ + _prepare_mm_context((struct sos_thread*) current_thread); +} + + +void sos_thread_prepare_exception_switch_back(struct sos_cpu_state *cpu_state) +{ + /* Don't preempt the current thread */ + + /* + * Save the state of the interrupted context to make sure that: + * - The list of threads correctly reflects that the thread is + * running in user or kernel mode + * - _prepare_mm_context() deals with the correct mm_context + */ + current_thread->cpu_state = cpu_state; + + /* Perform an MMU context switch if needed */ + _prepare_mm_context((struct sos_thread*) current_thread); +} + + +void +sos_thread_prepare_irq_servicing(struct sos_cpu_state *interrupted_state) +{ + current_thread->cpu_state = interrupted_state; +} + + +struct sos_cpu_state * +sos_thread_prepare_irq_switch_back(void) +{ + struct sos_thread *myself, *next_thread; + + /* In SOS, threads in kernel mode are NEVER preempted from the + interrupt handlers ! */ + if (! sos_cpu_context_is_in_user_mode(current_thread->cpu_state)) + return current_thread->cpu_state; + + /* + * Here we are dealing only with possible preemption of user threads + * in user context ! + */ + + /* Make sure the thread actually is a user thread */ + SOS_ASSERT_FATAL(current_thread->process != NULL); + + /* Save the state of the interrupted context */ + myself = (struct sos_thread*)current_thread; + + /* Select the next thread to run */ + next_thread = sos_reschedule(myself, FALSE); + + /* Perform an MMU context switch if needed */ + _prepare_mm_context(next_thread); + + /* Setup the next_thread's context into the CPU */ + _set_current(next_thread); + return next_thread->cpu_state; +} diff --git a/sos/thread.h b/sos/thread.h new file mode 100644 index 0000000..ed22cda --- /dev/null +++ b/sos/thread.h @@ -0,0 +1,427 @@ +/* Copyright (C) 2004,2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_THREAD_H_ +#define _SOS_THREAD_H_ + +/** + * @file thread.h + * + * SOS Thread management API + */ + +#include + +/* Forward declaration */ +struct sos_thread; + +#include +#include +#include +#include +#include +#include + +/** + * The possible states of a valid thread + */ +typedef enum { SOS_THR_CREATED, /**< Thread created, not fully initialized */ + SOS_THR_READY, /**< Thread fully initialized or + waiting for CPU after having been + blocked or preempted */ + SOS_THR_RUNNING, /**< Thread currently running on CPU */ + SOS_THR_BLOCKED, /**< Thread waiting for I/O (+ in at LEAST + one kwaitq) and/or sleeping (+ in NO + kwaitq) */ + SOS_THR_ZOMBIE, /**< Thread terminated execution, waiting to + be deleted by kernel */ + } sos_thread_state_t; + + +/** + * TCB (Thread Control Block): structure describing a thread. Don't + * access these fields directly: prefer using the accessor functions + * below. + */ +struct sos_thread +{ +#define SOS_THR_MAX_NAMELEN 32 + char name[SOS_THR_MAX_NAMELEN]; + + sos_thread_state_t state; + sos_sched_priority_t priority; + + /** + * The hardware context of the thread. + * + * It will reflect the CPU state of the thread: + * - From an interrupt handler: the state of the thread at the time + * of the OUTERMOST irq. An IRQ is not allowed to make context + * switches, so this context will remain valid from the begining of + * the outermost IRQ handler to the end of it, no matter if there + * are other IRQ handlers nesting in one another. You may safely + * use it from IRQ handlers to query the state of the interrupted + * thread, no matter if there has been other IRQ handlers + * executing meanwhile. + * - From normal kernel code, exceptions and syscall: the state of + * the thread the last time there was a context switch from this + * thread to another one. Thus this field WON'T reflect the + * current's thread cpu_state in these cases. So, in these cases, + * simply DO NOT USE IT outside thread.c ! Note: for syscall and + * exception handlers, the VALID state of the interrupted thread is + * passed as an argument to the handlers. + */ + struct sos_cpu_state *cpu_state; + + /* Kernel stack parameters */ + sos_vaddr_t kernel_stack_base_addr; + sos_size_t kernel_stack_size; + + /* Process this thread belongs to. Always NULL for a kernel + thread */ + struct sos_process *process; + + /** + * Address space currently "squatted" by the thread, or used to be + * active when the thread was interrupted/preempted. This is the MMU + * configuration expected before the cpu_state of the thread is + * restored on CPU. + * - For kernel threads: should normally be NULL, meaning that the + * thread will squat the current mm_context currently set in the + * MMU. Might be NON NULL when a kernel thread squats a given + * process to manipulate its address space. + * - For user threads: should normally be NULL. More precisely: + * - in user mode: the thread->process.mm_context is ALWAYS + * set on MMU. squatted_mm_context is ALWAYS NULL in this + * situation, meaning that the thread in user mode uses its + * process-space as expected + * - in kernel mode: NULL means that we keep on using the + * mm_context currently set on MMU, which might be the + * mm_context of another process. This is natural since a + * thread in kernel mode normally only uses data in kernel + * space. BTW, this limits the number of TLB flushes. However, + * there are exceptions where this squatted_mm_context will + * NOT be NULL. One is the copy_from/to_user API, which can + * force the effective mm_context so that the MMU will be + * (re)configured upon every context to the thread to match + * the squatted_mm_context. Another exception is when a parent + * thread creates the address space of a child process, in + * which case the parent thread might temporarilly decide to + * switch to the child's process space. + * + * This is the SOS implementation of the Linux "Lazy TLB" and + * address-space loaning. + */ + struct sos_umem_vmm_as *squatted_address_space; + + /* Data specific to each state */ + union + { + struct + { + struct sos_sched_queue *rdy_queue; + struct sos_thread *rdy_prev, *rdy_next; + } ready; + }; /* Anonymous union (gcc extenion) */ + + struct sos_time user_time_spent_in_slice; + + + /** + * When a thread in kernel mode is accessing the user space, it may + * page fault in the usual way only if return_vaddr below is + * set. This structure holds information regarding what to do when a + * page fault from kernel into user space could not be resolved. + * + * @note the fields below should be considered read-only. @see + * sos_thread_prepare_user_space_access() and @see + * sos_thread_end_user_space_access() to modify them. + */ + struct + { + /** This is the address (in kernel code) to return to when a + user-space page fault from a kernel-mode thread could not be + resolved. @see sos_thread_prepare_user_space_access() */ + sos_vaddr_t return_vaddr; + + /** This is the address of the user-space address that caused the + unresolved page fault (set by the page fault handler) */ + sos_uaddr_t faulted_uaddr; + } fixup_uaccess; + + + /* + * Data used by the kwaitq subsystem: list of kwaitqueues the thread + * is waiting for. + * + * @note: a RUNNING or READY thread might be in one or more + * waitqueues ! The only property we have is that, among these + * waitqueues (if any), _at least_ one has woken the thread. + */ + struct sos_kwaitq_entry *kwaitq_list; + + + /** + * Some statistics + */ + struct rusage + { + /* Updated by sched.c */ + struct sos_time ru_utime; /* Time spent in user mode */ + struct sos_time ru_stime; /* Time spent in kernel mode */ + } rusage; + + + /** + * Chaining pointers for the list of threads in the parent process + */ + struct sos_thread *prev_in_process, *next_in_process; + + + /** + * Chaining pointers for global ("gbl") list of threads (debug) + */ + struct sos_thread *gbl_prev, *gbl_next; +}; + + +/** + * Definition of the function executed by a kernel thread + */ +typedef void (*sos_kernel_thread_start_routine_t)(void *arg); + + +/** + * Initialize the subsystem responsible for thread management + * + * Initialize the primary kernel thread so that it can be handled the + * same way as an ordinary thread created by sos_thread_create(). + */ +sos_ret_t sos_thread_subsystem_setup(sos_vaddr_t init_thread_stack_base_addr, + sos_size_t init_thread_stack_size); + + +/** + * Create a new kernel thread + */ +struct sos_thread * +sos_create_kernel_thread(const char *name, + sos_kernel_thread_start_routine_t start_func, + void *start_arg, + sos_sched_priority_t priority); + + +/** + * Create a new user thread + */ +struct sos_thread * +sos_create_user_thread(const char *name, + struct sos_process *process, + sos_uaddr_t user_initial_PC, + sos_ui32_t user_start_arg1, + sos_ui32_t user_start_arg2, + sos_uaddr_t user_initial_SP, + sos_sched_priority_t priority); + + +/** + * Create a new user thread, copy of the given user thread with the + * given user context + */ +struct sos_thread * +sos_duplicate_user_thread(const char *name, + struct sos_process *process, + const struct sos_thread * model_thread, + const struct sos_cpu_state * model_uctxt, + sos_ui32_t retval); + + +/** + * Terminate the execution of the current thread. For kernel threads, + * it is called by default when the start routine returns. + */ +void sos_thread_exit(void) __attribute__((noreturn)); + + +/** + * Get the identifier of the thread currently running on CPU. Trivial + * function. + */ +struct sos_thread *sos_thread_get_current(void); + + +/** + * If thr == NULL, set the priority of the current thread. Trivial + * function. + * + * @note NOT protected against interrupts + */ +sos_sched_priority_t sos_thread_get_priority(struct sos_thread *thr); + + +/** + * If thr == NULL, get the state of the current thread. Trivial + * function. + * + * @note NOT protected against interrupts + */ +sos_thread_state_t sos_thread_get_state(struct sos_thread *thr); + + +/** + * If thr == NULL, set the priority of the current thread + * + * @note NO context-switch ever occurs in this function ! + */ +sos_ret_t sos_thread_set_priority(struct sos_thread *thr, + sos_sched_priority_t priority); + + +/** + * Yield CPU to another ready thread. + * + * @note This is a BLOCKING FUNCTION + */ +sos_ret_t sos_thread_yield(void); + + +/** + * Release the CPU for (at least) the given delay. + * + * @param delay The delay to wait for. If delay == NULL then wait + * forever that any event occurs. + * + * @return SOS_OK when delay expired (and delay is reset to zero), + * -SOS_EINTR otherwise (and delay contains the amount of time + * remaining). + * + * @note This is a BLOCKING FUNCTION + */ +sos_ret_t sos_thread_sleep(/* in/out */struct sos_time *delay); + + +/** + * Mark the given thread as READY (if not already ready) even if it is + * blocked in a kwaitq or in a sleep ! As a result, the interrupted + * kwaitq/sleep function call of the thread will return with + * -SOS_EINTR. + * + * @return -SOS_EINVAL if thread does not exist, or -SOS_EFATAL if + * marked ZOMBIE. + * + * @note As a result, the semaphore/mutex/conditions/... functions + * return values SHOULD ALWAYS be checked ! If they are != SOS_OK, + * then the caller should consider that the resource is not aquired + * because somebody woke the thread by some way. + */ +sos_ret_t sos_thread_force_unblock(struct sos_thread *thread); + +/** + * Dump the backtrace of the current thread to console and/or bochs + */ +void sos_thread_dump_backtrace(sos_bool_t on_console, + sos_bool_t on_bochs); + + +/* ********************************************** + * Restricted functions + */ + + +/** + * Restricted function to indicate that we are to access the given + * user address space from inside the kernel. + * + * @param dest_as The address space we want to access, or NULL to + * access current thread's address space + * + * @param fixup_retvaddr When != 0, the page fault handler should + * accept page faults from the kernel in user space, and resolve them + * in the usual way. The value in retvaddr is where the page fault + * handler has to return to in case the page fault remains + * unresolved. The address of the faulting address is kept in + * éthread->fixup_uaccess.faulted_uaddr + * + * @note typical values for fixup_retvaddr are obtained by "Labels as + * values" (see gcc's doc: operator "&&"). See uaccess.c for example + * code. + */ +sos_ret_t +sos_thread_prepare_user_space_access(struct sos_umem_vmm_as * dest_as, + sos_vaddr_t fixup_retvaddr); + + +/** + * Restricted function to signal we are not accessing any user address + * space anymore + */ +sos_ret_t +sos_thread_end_user_space_access(void); + + +/** + * Restricted callback called when a syscall goes back in user mode, + * to reconfigure the MMU to match that of the current thread's + * process MMU context. + * + * @note The use of this function is RESERVED to the syscall wrapper + */ +void sos_thread_prepare_syscall_switch_back(struct sos_cpu_state *cpu_state); + + +/** + * Restricted callback called when an exception handler goes back to + * the interrupted thread to reconfigure the MMU to match that of the + * current thread's process MMU context. + * + * @note The use of this function is RESERVED to the exception wrappers + */ +void sos_thread_prepare_exception_switch_back(struct sos_cpu_state *cpu_state); + + +/** + * Restricted callback called when an IRQ is entered while the CPU was + * NOT already servicing any other IRQ (ie the outermost IRQ handler + * is entered). This callback simply updates the "cpu_state" field so + * that IRQ handlers always know the state of the interrupted thread, + * even if they are imbricated in other IRQ handlers. + * + * @note The use of this function is RESERVED to the irq wrappers + */ +void +sos_thread_prepare_irq_servicing(struct sos_cpu_state *interrupted_state); + + +/** + * Restricted callback called when the outermost IRQ handler returns, + * to select the thread to return to. This callbacks implements: + * - preemption of user threads in user mode (time sharing / FIFO) + * - non-preemption of user threads in kernel mode (interrupted thread + * is restored on CPU "as is") + * - non-preemption of kernel threads (same remark) + * The MMU is reconfigured correctly to match the address space of the + * selected thread. + * + * @return The CPU context of the thread to return to + * + * @note The use of this function is RESERVED to the irq wrappers + */ +struct sos_cpu_state * +sos_thread_prepare_irq_switch_back(void); + + +#endif /* _SOS_THREAD_H_ */ diff --git a/sos/time.c b/sos/time.c new file mode 100644 index 0000000..5181959 --- /dev/null +++ b/sos/time.c @@ -0,0 +1,355 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "time.h" + + +/** + * Number of nanoseconds in 1 second + */ +#define NS_IN_SEC 1000000000UL + + +/** + * The list of timeout actions waiting for a timeout. The timeout + * actions are stored in the list in increasing initial timeout + * order. Actually, the "timeout" field won't reflect this initial + * timeout: for each element in the list, it stores the timeout + * _difference_ between the timeout action and the previous in the + * list. + */ +static struct sos_timeout_action *timeout_action_list; + + +/** + * Current resolution of a time tick + */ +static struct sos_time tick_resolution; + + +/** + * Time elapsed between boot and last timer tick + * + * @note No 'volatile' here because the tick value is NEVER modified + * while in any of the functions below: it is modified only out of + * these functions by the IRQ timer handler because these functions + * are protected against timer IRQ and are "one shot" (no busy waiting + * for a change in the tick's value). + */ +static struct sos_time last_tick_time; + + +sos_ret_t sos_time_inc(struct sos_time *dest, + const struct sos_time *to_add) +{ + /* nanosec is always < 1e9 so that their sum is always < 2e9, which + is smaller than 2^32-1 */ + sos_ui32_t sigma_ns = dest->nanosec + to_add->nanosec; + + dest->sec += to_add->sec; + dest->sec += sigma_ns / NS_IN_SEC; + dest->nanosec = sigma_ns % NS_IN_SEC; + return SOS_OK; +} + + +sos_ret_t sos_time_dec(struct sos_time *dest, + const struct sos_time *to_dec) +{ + /* nanosec is always < 1e9 so that their difference is always in + (-1e9, 1e9), which is compatible with the (-2^31, 2^31 - 1) + cpacity of a signed dword */ + sos_si32_t diff_ns = ((sos_si32_t)dest->nanosec) + - ((sos_si32_t)to_dec->nanosec); + + /* Make sure substraction is possible */ + SOS_ASSERT_FATAL(dest->sec >= to_dec->sec); + if (dest->sec == to_dec->sec) + SOS_ASSERT_FATAL(dest->nanosec >= to_dec->nanosec); + + dest->sec -= to_dec->sec; + if (diff_ns > 0) + dest->sec += diff_ns / NS_IN_SEC; + else + dest->sec -= ((-diff_ns) / NS_IN_SEC); + dest->nanosec = (NS_IN_SEC + diff_ns) % NS_IN_SEC; + if (diff_ns < 0) + dest->sec --; + return SOS_OK; +} + + +int sos_time_cmp(const struct sos_time *t1, + const struct sos_time *t2) +{ + /* Compare seconds */ + if (t1->sec < t2->sec) + return -1; + else if (t1->sec > t2->sec) + return 1; + + /* seconds are equal => compare nanoseconds */ + else if (t1->nanosec < t2->nanosec) + return -1; + else if (t1->nanosec > t2->nanosec) + return 1; + + /* else: sec and nanosecs are equal */ + return 0; +} + + +sos_bool_t sos_time_is_zero(const struct sos_time *tm) +{ + return ( (0 == tm->sec) && (0 == tm->nanosec) ); +} + + +sos_ret_t sos_time_subsysem_setup(const struct sos_time *initial_resolution) +{ + timeout_action_list = NULL; + last_tick_time = (struct sos_time) { .sec = 0, .nanosec = 0 }; + memcpy(& tick_resolution, initial_resolution, sizeof(struct sos_time)); + + return SOS_OK; +} + + +sos_ret_t sos_time_get_tick_resolution(struct sos_time *resolution) +{ + sos_ui32_t flags; + sos_disable_IRQs(flags); + + memcpy(resolution, & tick_resolution, sizeof(struct sos_time)); + + sos_restore_IRQs(flags); + return SOS_OK; +} + + +sos_ret_t sos_time_set_tick_resolution(const struct sos_time *resolution) +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + memcpy(& tick_resolution, resolution, sizeof(struct sos_time)); + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +sos_ret_t sos_time_get_now(struct sos_time *now) +{ + sos_ui32_t flags; + sos_disable_IRQs(flags); + + memcpy(now, & last_tick_time, sizeof(struct sos_time)); + + sos_restore_IRQs(flags); + return SOS_OK; +} + + +/** + * Helper routine to add the action in the list. MUST be called with + * interrupts disabled ! + */ +static sos_ret_t _add_action(struct sos_timeout_action *act, + const struct sos_time *due_date, + sos_bool_t is_relative_due_date, + sos_timeout_routine_t *routine, + void *routine_data) +{ + struct sos_timeout_action *other, *insert_before; + int nb_act; + + /* Delay must be specified */ + if (due_date == NULL) + return -SOS_EINVAL; + + /* Action container MUST be specified */ + if (act == NULL) + return -SOS_EINVAL; + + /* Refuse to add an empty action */ + if (NULL == routine) + return -SOS_EINVAL; + + /* Refuse to add the action if it is already added */ + if (NULL != act->tmo_next) + return -SOS_EBUSY; + + /* Compute action absolute due date */ + if (is_relative_due_date) + { + /* The provided due_date is relative to the current time */ + memcpy(& act->timeout, & last_tick_time, sizeof(struct sos_time)); + sos_time_inc(& act->timeout, due_date); + } + else + { + /* The provided due_date is absolute (ie relative to the system + boot instant) */ + + if (sos_time_cmp(due_date, & last_tick_time) < 0) + /* Refuse to add a past action ! */ + return -SOS_EINVAL; + + memcpy(& act->timeout, due_date, sizeof(struct sos_time)); + } + + /* Prepare the action data structure */ + act->routine = routine; + act->routine_data = routine_data; + + /* Find the right place in the list of the timeout action. */ + insert_before = NULL; + list_foreach_forward_named(timeout_action_list, + other, nb_act, + tmo_prev, tmo_next) + { + if (sos_time_cmp(& act->timeout, & other->timeout) < 0) + { + insert_before = other; + break; + } + + /* Loop over to next timeout */ + } + + /* Now insert the action in the list */ + if (insert_before != NULL) + list_insert_before_named(timeout_action_list, insert_before, act, + tmo_prev, tmo_next); + else + list_add_tail_named(timeout_action_list, act, + tmo_prev, tmo_next); + + return SOS_OK; +} + + +sos_ret_t +sos_time_register_action_relative(struct sos_timeout_action *act, + const struct sos_time *delay, + sos_timeout_routine_t *routine, + void *routine_data) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = _add_action(act, delay, TRUE, routine, routine_data); + sos_restore_IRQs(flags); + + return retval; +} + + +sos_ret_t +sos_time_register_action_absolute(struct sos_timeout_action *act, + const struct sos_time *date, + sos_timeout_routine_t *routine, + void *routine_data) +{ + sos_ui32_t flags; + sos_ret_t retval; + + sos_disable_IRQs(flags); + retval = _add_action(act, date, FALSE, routine, routine_data); + sos_restore_IRQs(flags); + + return retval; +} + + +/** + * Helper routine to remove the action from the list. MUST be called + * with interrupts disabled ! + */ +static sos_ret_t _remove_action(struct sos_timeout_action *act) +{ + /* Don't do anything if action is not in timeout list */ + if (NULL == act->tmo_next) + return -SOS_EINVAL; + + /* Update the action's remaining timeout */ + if (sos_time_cmp(& act->timeout, & last_tick_time) <= 0) + act->timeout = (struct sos_time){ .sec=0, .nanosec=0 }; + else + sos_time_dec(& act->timeout, & last_tick_time); + + /* Actually remove the action from the list */ + list_delete_named(timeout_action_list, act, + tmo_prev, tmo_next); + act->tmo_prev = act->tmo_next = NULL; + + return SOS_OK; +} + + +sos_ret_t sos_time_unregister_action(struct sos_timeout_action *act) +{ + sos_ret_t retval; + sos_ui32_t flags; + + sos_disable_IRQs(flags); + retval = _remove_action(act); + sos_restore_IRQs(flags); + + return SOS_OK; +} + + +sos_ret_t sos_time_do_tick() +{ + sos_ui32_t flags; + + sos_disable_IRQs(flags); + + /* Update kernel time */ + sos_time_inc(& last_tick_time, & tick_resolution); + + while (! list_is_empty_named(timeout_action_list, tmo_prev, tmo_next)) + { + struct sos_timeout_action *act; + act = list_get_head_named(timeout_action_list, tmo_prev, tmo_next); + + /* Did we go too far in the actions' list ? */ + if (sos_time_cmp(& last_tick_time, & act->timeout) < 0) + { + /* Yes: No need to look further. */ + break; + } + + /* Remove the action from the list */ + _remove_action(act); + + /* Call the action's routine */ + act->routine(act); + } + + sos_restore_IRQs(flags); + return SOS_OK; +} diff --git a/sos/time.h b/sos/time.h new file mode 100644 index 0000000..992240e --- /dev/null +++ b/sos/time.h @@ -0,0 +1,222 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_TIME_H_ +#define _SOS_TIME_H_ + +/** + * @file time.h + * + * Primitives and callbacks related to kernel time management (timer + * IRQ) + */ + +#include +#include +#include + + +/* ======================================================================= + * Library of time manipulation functions + */ +struct sos_time +{ + sos_ui32_t sec; + sos_ui32_t nanosec; +}; + +sos_ret_t sos_time_inc(struct sos_time *dest, + const struct sos_time *to_add); + +sos_ret_t sos_time_dec(struct sos_time *dest, + const struct sos_time *to_dec); + +int sos_time_cmp(const struct sos_time *t1, + const struct sos_time *t2); + +sos_bool_t sos_time_is_zero(const struct sos_time *tm); + + + +/* ======================================================================= + * Kernel time management. This is not the same as the "system-time", + * ie it does not not take into account the system-time adjustments + * (NTP, daylight saving times, etc...): this is the job of a + * system-time subsystem. + */ + + +/** + * Initialize kernel time subsystem. + * + * @param initial_resolution The initial time resolution. MUST be + * consistent with that of the hardware timer + */ +sos_ret_t sos_time_subsysem_setup(const struct sos_time *initial_resolution); + + +/** + * Value of the interval between 2 time ticks. Should be consistent + * with the configuration of the hardware timer. + */ +sos_ret_t sos_time_get_tick_resolution(struct sos_time *resolution); + + +/** + * Change the value of the interval between 2 time ticks. Must be + * called each time the hardware timer is reconfigured. + * + * @note MUST be consistent with that of the hardware timer + */ +sos_ret_t sos_time_set_tick_resolution(const struct sos_time *resolution); + + +/** + * Get the time elapsed since system boot. Does not take into account + * the system-time adjustment (NTP, daylight saving times, etc...): + * this is the job of a system-time subsystem. + */ +sos_ret_t sos_time_get_now(struct sos_time *now); + + + +/* ======================================================================= + * Routines to schedule future execution of routines: "timeout" actions + */ + +/* Forward declaration */ +struct sos_timeout_action; + +/** + * Prototype of a timeout routine. Called with IRQ disabled ! + */ +typedef void (sos_timeout_routine_t)(struct sos_timeout_action *); + + +/** + * The structure of a timeout action. This structure should have been + * opaque to the other parts of the kernel. We keep it public here so + * that struct sos_timeout_action can be allocated on the stack from + * other source files in the kernel. However, all the fields should be + * considered read-only for modules others than time.{ch}. + * + * @note After an action has been allocated (on the stack or kmalloc), + * it MUST be initialized with sos_time_init_action below ! + */ +struct sos_timeout_action +{ + /** PUBLIC: Address of the timeout routine */ + sos_timeout_routine_t *routine; + + /** PUBLIC: (Custom) data available for this routine */ + void *routine_data; + + /** PUBLIC: 2 meanings: + * - before and while in the timeout list: absolute due date of the + * timeout action + * - once removed from timeout list: the time remaining in the + * initial timeout (might be 0 if timeout expired) at removal + * time + */ + struct sos_time timeout; + + /** PRIVATE: To chain the timeout actions */ + struct sos_timeout_action *tmo_prev, *tmo_next; +}; + + +/** + * Initialize a timeout action. MUST be called immediately after + * (stack or kmalloc) allocation of the action. + * + * @param ptr_act Pointer to the action to initialize. + */ +#define sos_time_init_action(ptr_act) \ + ({ (ptr_act)->tmo_prev = (ptr_act)->tmo_next = NULL; /* return */ SOS_OK; }) + + +/** + * Add the given action in the timeout list, so that it will be + * triggered after the specified delay RELATIVE to the time when the + * function gets called. The action is always inserted in the list. + * + * @param act The action to be initialized by the function upon + * insertion in the timeout list. + * + * @param delay Delay until the action is fired. If 0, then it is + * fired at next timer IRQ. The action will be fired in X ticks, with + * X integer and >= delay. + * + * @param routine The timeout routine to call when the timeout will be + * triggered. + * + * @param routine_data The data available to the routine when it will + * be called. + * + * @note 'act' MUST remain valid until it is either fired or removed + * (with sos_time_remove_action) + */ +sos_ret_t +sos_time_register_action_relative(struct sos_timeout_action *act, + const struct sos_time *delay, + sos_timeout_routine_t *routine, + void *routine_data); + + +/** + * Add the given action in the timeout list, so that it will be + * triggered after the specified ABSOLUTE date (relative to system + * boot time). The action is always inserted in the list. + * + * @param act The action to be initialized by the function upon + * insertion in the timeout list. + * + * @param date Absolute date (relative to system boot time) when the + * action will be triggered. + * + * @param routine The timeout routine to call when the timeout will be + * triggered. + * + * @param routine_data The data available to the routine when it will + * be called. + * + * @note 'act' MUST remain valid until it is either fired or removed + * (with sos_time_remove_action) + */ +sos_ret_t +sos_time_register_action_absolute(struct sos_timeout_action *act, + const struct sos_time *date, + sos_timeout_routine_t *routine, + void *routine_data); + + +/** + * The action is removed and its timeout is updated to reflect the + * time remaining. + */ +sos_ret_t sos_time_unregister_action(struct sos_timeout_action *act); + + +/** + * Timer IRQ callback. Call and remove expired actions from the list. + * + * @note The use of this function is RESERVED (to timer IRQ) + */ +sos_ret_t sos_time_do_tick(void); + + +#endif /* _SOS_TIME_H_ */ diff --git a/sos/types.h b/sos/types.h new file mode 100644 index 0000000..2a61286 --- /dev/null +++ b/sos/types.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2004 The SOS Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_TYPES_H_ +#define _SOS_TYPES_H_ + +/** + * @file types.h + * + * SOS basic types definition + */ + +/** Physical address */ +typedef unsigned int sos_paddr_t; + +/** Kernel virtual address */ +typedef unsigned int sos_vaddr_t; + +/** User virtual address */ +typedef unsigned int sos_uaddr_t; + +/** Memory size of an object (positive) */ +typedef unsigned int sos_size_t; +/** Generic positive offset into objects */ +typedef unsigned int sos_uoffset_t; +/** Generic signed offset into objects */ +typedef signed int sos_soffset_t; +/** Generic positive LONG (64 bits) offset into objects */ +typedef unsigned long long int sos_luoffset_t; +/** Generic signed LONG (64 bits) offset into objects */ +typedef signed long long int sos_lsoffset_t; + +/** Generic count of objects */ +typedef unsigned int sos_count_t; +/** Generic count of objects (LARGE) */ +typedef unsigned long long int sos_lcount_t; + +/** Process identifiers */ +typedef unsigned int sos_pid_t; + +/* Low-level sizes */ +typedef unsigned long long int sos_ui64_t; /**< 32b unsigned */ +typedef unsigned long int sos_ui32_t; /**< 32b unsigned */ +typedef unsigned short int sos_ui16_t; /**< 16b unsigned */ +typedef unsigned char sos_ui8_t; /**< 8b unsigned */ + +typedef signed long long int sos_si64_t; /**< 64b signed */ +typedef signed long int sos_si32_t; /**< 32b signed */ +typedef signed short int sos_si16_t; /**< 16b signed */ +typedef signed char sos_si8_t; /**< 8b signed */ + +typedef enum { FALSE=0, TRUE } sos_bool_t; + +/** Not a proper type, but highly useful with basic type + manipulations */ +#define NULL ((void*)0) + +#endif /* _SOS_TYPES_H_ */ diff --git a/sos/uaccess.c b/sos/uaccess.c new file mode 100644 index 0000000..566e8ac --- /dev/null +++ b/sos/uaccess.c @@ -0,0 +1,413 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +#include "uaccess.h" + + +int __uaccess_zero_fool_gcc = 0; +/** + * Make sure gcc optimizations don't go too far and don't suppress the + * code at the given label because 1/ a global return is "above" (wrt + * source code lines), 2/ it is never "goto" + */ +#define KEEP_LABEL(lbl) \ + ({ if (__uaccess_zero_fool_gcc) goto lbl; }) + + +/** + * @param user_as The user address space we want to transfer from/to + * + * @return >=0 : number of bytes successfully transferred, <0 an error code + */ +static sos_ret_t nocheck_user_memcpy(struct sos_umem_vmm_as * user_as, + sos_vaddr_t dest, + sos_vaddr_t src, + sos_size_t size, + sos_bool_t transfer_from_user) +{ + __label__ catch_pgflt; + char *cptr_dst; + const char *cptr_src; + sos_size_t transfer_sz; + sos_ret_t retval; + + KEEP_LABEL(catch_pgflt); + + if (size <= 0) + return 0; + + retval = sos_thread_prepare_user_space_access(user_as, + (sos_vaddr_t) && catch_pgflt); + if (SOS_OK != retval) + return retval; + + /* Version of memcpy that does not alter the stack pointer, in + order to be able to abruptedly jump to catch_pgflt without stack + inconsistency. That's the reason why we don't call the normal + memcpy here: on page fault, it would return back in here, with + the stack frame returning back here again */ + for (cptr_dst = (char*)dest, + cptr_src = (const char*)src, + transfer_sz = size ; + transfer_sz > 0 ; + cptr_dst++, cptr_src++, transfer_sz--) + *cptr_dst = *cptr_src; + + retval = sos_thread_end_user_space_access(); + if (SOS_OK != retval) + return retval; + + return size; + + /* An unresolved page fault occured while accessing user space */ + catch_pgflt: + { + struct sos_thread * cur_thr = sos_thread_get_current(); + sos_uaddr_t faulted_uaddr = cur_thr->fixup_uaccess.faulted_uaddr; + + if (transfer_from_user) + { + if ( (faulted_uaddr < src) || (faulted_uaddr - size > src) ) + sos_display_fatal_error("Unexpected read access in user space"); + retval = faulted_uaddr - src; + } + else + { + if ( (faulted_uaddr < dest) || (faulted_uaddr - size > dest) ) + sos_display_fatal_error("Unexpected write access in user space"); + retval = faulted_uaddr - dest; + } + + sos_thread_end_user_space_access(); + return retval; + } +} + + +sos_ret_t sos_memcpy_from_user(sos_vaddr_t kernel_to, + sos_uaddr_t user_from, + sos_size_t size) +{ + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_from, size) ) + return -SOS_EPERM; + + /* Make sure the copy totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA(kernel_to, size) ) + return -SOS_EPERM; + + return nocheck_user_memcpy(NULL, kernel_to, user_from, size, TRUE); +} + + +sos_ret_t sos_memdup_from_user(sos_vaddr_t * kernel_to, sos_uaddr_t from_user, + sos_size_t length, + sos_ui32_t kmalloc_flags) +{ + sos_ret_t retval; + + if (length <= 0) + return -SOS_EINVAL; + + *kernel_to = sos_kmalloc(length, kmalloc_flags); + if (NULL == (void*) *kernel_to) + return -SOS_ENOMEM; + + retval = sos_memcpy_from_user(*kernel_to, from_user, length); + if ((sos_ret_t)length != retval) + { + sos_kfree((sos_vaddr_t)*kernel_to); + *kernel_to = (sos_vaddr_t) NULL; + retval = -SOS_EFAULT; + } + else + retval = SOS_OK; + + return retval; +} + + +sos_ret_t sos_memcpy_to_user(sos_uaddr_t user_to, + sos_vaddr_t kernel_from, + sos_size_t size) +{ + /* Make sure the source totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA(kernel_from, size) ) + return -SOS_EPERM; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_to, size) ) + return -SOS_EPERM; + + return nocheck_user_memcpy(NULL, user_to, kernel_from, size, FALSE); +} + + +sos_ret_t sos_memcpy_from_specified_userspace(sos_vaddr_t kernel_to, + struct sos_umem_vmm_as * src_as, + sos_uaddr_t user_from, + sos_size_t size) +{ + if (NULL == src_as) + return -SOS_EINVAL; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_from, size) ) + return -SOS_EPERM; + + /* Make sure the copy totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA(kernel_to, size) ) + return -SOS_EPERM; + + return nocheck_user_memcpy(src_as, kernel_to, user_from, size, TRUE); +} + + +sos_ret_t sos_memcpy_to_specified_userspace(struct sos_umem_vmm_as * dst_as, + sos_uaddr_t user_to, + sos_vaddr_t kernel_from, + sos_size_t size) +{ + if (NULL == dst_as) + return -SOS_EINVAL; + + /* Make sure the source totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA(kernel_from, size) ) + return -SOS_EPERM; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_to, size) ) + return -SOS_EPERM; + + return nocheck_user_memcpy(dst_as, user_to, kernel_from, size, FALSE); +} + + +sos_ret_t sos_usercpy(struct sos_umem_vmm_as * dst_as, + sos_uaddr_t dst_uaddr, + struct sos_umem_vmm_as * src_as, + sos_uaddr_t src_uaddr, + sos_size_t size) +{ + sos_vaddr_t kern_addr; + sos_ret_t retval; + + if (size <= 0) + return 0; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(src_uaddr, size) ) + return -SOS_EPERM; + if (! SOS_PAGING_IS_USER_AREA(dst_uaddr, size) ) + return -SOS_EPERM; + + kern_addr = sos_kmalloc(size, 0); + if (! kern_addr) + return -SOS_ENOMEM; + + retval = nocheck_user_memcpy(src_as, kern_addr, src_uaddr, + size, TRUE); + if (retval <= 0) + { + sos_kfree((sos_vaddr_t)kern_addr); + return retval; + } + + retval = nocheck_user_memcpy(dst_as, dst_uaddr, kern_addr, + retval, FALSE); + + sos_kfree((sos_vaddr_t)kern_addr); + + return retval; +} + + +sos_ret_t sos_strnlen_from_user(sos_uaddr_t user_str, sos_size_t max_len) +{ + __label__ catch_pgflt; + const char *sc; + sos_ret_t retval; + + KEEP_LABEL(catch_pgflt); + + if (max_len <= 0) + return 0; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_str, max_len) ) + return -SOS_EPERM; + + retval = sos_thread_prepare_user_space_access(NULL, + (sos_vaddr_t) && catch_pgflt); + if (SOS_OK != retval) + return retval; + + /* Version of strnlen that does not alter the stack pointer, in + order to be able to abruptedly jump to catch_pgflt without stack + inconsistency. That's the reason why we don't call the normal + strnlen here: on page fault, it would return back in here, with + the stack frame returning back here again */ + for (sc = (const char *)user_str; max_len-- && *sc != '\0'; ++sc) + continue; + + + retval = sos_thread_end_user_space_access(); + if (SOS_OK != retval) + return retval; + + return ((sos_uaddr_t)sc - user_str); + + /* An unresolved page fault occured while accessing user space */ + catch_pgflt: + { + sos_thread_end_user_space_access(); + return -SOS_EFAULT; + } +} + + +static sos_ret_t nocheck_user_strzcpy(char *dst, const char *src, + sos_size_t len) +{ + __label__ catch_pgflt; + unsigned int i; + sos_ret_t retval; + + KEEP_LABEL(catch_pgflt); + + if (len <= 0) + return 0; + + retval = sos_thread_prepare_user_space_access(NULL, + (sos_vaddr_t) && catch_pgflt); + if (SOS_OK != retval) + return retval; + + /* Version of strzcpy that does not alter the stack pointer, in + order to be able to abruptedly jump to catch_pgflt without stack + inconsistency. That's the reason why we don't call the normal + strzcpy here: on page fault, it would return back in here, with + the stack frame returning back here again */ + for (i = 0; i < len; i++) + { + dst[i] = src[i]; + if(src[i] == '\0') + break; + } + + dst[len-1] = '\0'; + + retval = sos_thread_end_user_space_access(); + if (SOS_OK != retval) + return retval; + + return SOS_OK; + + /* An unresolved page fault occured while accessing user space */ + catch_pgflt: + { + sos_thread_end_user_space_access(); + return -SOS_EFAULT; + } +} + + +sos_ret_t sos_strzcpy_from_user(char *kernel_to, sos_uaddr_t user_from, + sos_size_t max_len) +{ + if (max_len <= 0) + return -SOS_EINVAL; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_from, max_len) ) + return -SOS_EPERM; + + /* Make sure the copy totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA((sos_vaddr_t)kernel_to, max_len) ) + return -SOS_EPERM; + + return nocheck_user_strzcpy(kernel_to, (const char*)user_from, max_len); +} + + +sos_ret_t sos_strzcpy_to_user(sos_uaddr_t user_to, const char *kernel_from, + sos_size_t max_len) +{ + if (max_len <= 0) + return -SOS_EINVAL; + + /* Make sure the source totally resides inside kernel space */ + if (! SOS_PAGING_IS_KERNEL_AREA((sos_vaddr_t)kernel_from, max_len) ) + return -SOS_EPERM; + + /* Make sure user is trying to access user space */ + if (! SOS_PAGING_IS_USER_AREA(user_to, max_len) ) + return -SOS_EPERM; + + return nocheck_user_strzcpy((char*)user_to, kernel_from, max_len); +} + + +sos_ret_t sos_strndup_from_user(char ** kernel_to, sos_uaddr_t from_user, + sos_size_t max_len, + sos_ui32_t kmalloc_flags) +{ + sos_ret_t retval = sos_strnlen_from_user(from_user, max_len); + if (retval < 0) + return retval; + + *kernel_to = (char*)sos_kmalloc(retval + 1, kmalloc_flags); + if (NULL == *kernel_to) + return -SOS_ENOMEM; + + retval = sos_strzcpy_from_user(*kernel_to, from_user, retval + 1); + if (SOS_OK != retval) + { + sos_kfree((sos_vaddr_t)*kernel_to); + *kernel_to = NULL; + } + + return retval; +} + + +sos_ret_t sos_memcpy_generic_to(sos_genaddr_t to_addr, + sos_vaddr_t kernel_from, + sos_size_t size) +{ + if (to_addr.is_user) + return sos_memcpy_to_user(to_addr.addr, kernel_from, size); + memcpy((void*)to_addr.addr, (const void*)kernel_from, size); + return size; +} + + +sos_ret_t sos_memcpy_generic_from(sos_vaddr_t kernel_from, + sos_genaddr_t from_addr, + sos_size_t size) +{ + if (from_addr.is_user) + return sos_memcpy_from_user(kernel_from, from_addr.addr, size); + memcpy((void*)kernel_from, (const void*)from_addr.addr, size); + return size; +} diff --git a/sos/uaccess.h b/sos/uaccess.h new file mode 100644 index 0000000..219ec03 --- /dev/null +++ b/sos/uaccess.h @@ -0,0 +1,173 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_UACCESS_H_ +#define _SOS_UACCESS_H_ + + +/** + * @file uaccess.h + * + * Routines to access user-space data from inside the kernel space. + */ + +#include +#include +#include + +/** + * Retrieve a bunch of data from the user space of the + * current_thread->process + * + * @return <0 on error ! Return the number of bytes successfully copied + * otherwise. + */ +sos_ret_t sos_memcpy_from_user(sos_vaddr_t kernel_to, + sos_uaddr_t user_from, + sos_size_t size); + + +/** + * Retrieve a bunch of data from the user space of the + * current_thread->process and copy it in a newly allocated kernel + * area + * + * @return NULL on error (including unresolved page fault during + * transfer) + */ +sos_ret_t sos_memdup_from_user(sos_vaddr_t * kernel_to, sos_uaddr_t from_user, + sos_size_t length, + sos_ui32_t kmalloc_flags); + + +/** + * Transfer a bunch of data from kernel space into the user space of + * the current_thread->process + * + * @return <0 n error ! Return the number of bytes successfully copied + * otherwise. + */ +sos_ret_t sos_memcpy_to_user(sos_uaddr_t user_to, + sos_vaddr_t kernel_from, + sos_size_t size); + + +/** + * Variant of sos_memcpy_from_user() + * Retrieve a bunch of data from the user space of the given src_as + * address space into kernel space + * + * @return NULL on error (including unresolved page fault during + * transfer) + * + * @note src_as MUST NOT be NULL + */ +sos_ret_t sos_memcpy_from_specified_userspace(sos_vaddr_t kernel_to, + struct sos_umem_vmm_as * src_as, + sos_uaddr_t user_from, + sos_size_t size); + + +/** + * Variant of sos_memcpy_to_user() + * Transfer a bunch of data from kernel space into the user space of + * the given dst_as address space + * + * @return <0 n error ! Return the number of bytes successfully copied + * otherwise. + * + * @note dst_as MUST NOT be NULL + */ +sos_ret_t sos_memcpy_to_specified_userspace(struct sos_umem_vmm_as * dst_as, + sos_uaddr_t user_to, + sos_vaddr_t kernel_from, + sos_size_t size); + +/** + * Copy data from an user space into another + * @return The number of bytes successfuly copied + * @note dst_as and src_as may be NULL, in which case the current + * thread->process address space is used for the copy + */ +sos_ret_t sos_usercpy(struct sos_umem_vmm_as * dst_as, + sos_uaddr_t dst_uaddr, + struct sos_umem_vmm_as * src_as, + sos_uaddr_t src_uaddr, + sos_size_t size); + + +/** + * @return the length of the given user space string user_str + * (excluding the trailing \0), up to max_len bytes. <0 on error + * (unresolved page fault, etc.) + */ +sos_ret_t sos_strnlen_from_user(sos_uaddr_t user_str, sos_size_t max_len); + + +/** + * Copy the given user space string to kernel space, up to max_len + * bytes (including trailing \0) + * + * @return SOS_OK on success, <0 otherwise (unresolved page fault, etc.) + */ +sos_ret_t sos_strzcpy_from_user(char *kernel_to, sos_uaddr_t user_from, + sos_size_t max_len); + + +/** + * Copy the given kernel string to user space, up to max_len bytes + * (including trailing \0) + * + * @return SOS_OK on success, <0 otherwise (unresolved page fault, etc.) + */ +sos_ret_t sos_strzcpy_to_user(sos_uaddr_t user_to, const char *kernel_from, + sos_size_t max_len); + + +/** + * Copy the given user space string into a new allocated kernel space + * area of the correct size, up to max_len bytes (including trailing + * \0) + * + * @return SOS_OK on success + *kernel_to is set to the freshly + * allocated kernel string, <0 otherwise (unresolved page fault, etc.) + */ +sos_ret_t sos_strndup_from_user(char ** kernel_to, sos_uaddr_t from_user, + sos_size_t max_len, + sos_ui32_t kmalloc_flags); + + +/* + * Special functions to access both kernel/user space + */ +/** Generic (Kernel or User) virtual address. We define a structure to + force the compiler to signal when we call this special function + with a usual address... */ +typedef struct sos_genaddr { sos_bool_t is_user; + sos_ui32_t addr; } sos_genaddr_t; +#define SOS_GENADDR_DECL(name,_is_user,_addr) \ + sos_genaddr_t name = (struct sos_genaddr) { .is_user=_is_user, .addr=_addr } + +sos_ret_t sos_memcpy_generic_to(sos_genaddr_t to_addr, + sos_vaddr_t kernel_from, + sos_size_t size); + +sos_ret_t sos_memcpy_generic_from(sos_vaddr_t kernel_from, + sos_genaddr_t from_addr, + sos_size_t size); + +#endif /* _SOS_UACCESS_H_ */ diff --git a/sos/umem_vmm.c b/sos/umem_vmm.c new file mode 100644 index 0000000..d7dce60 --- /dev/null +++ b/sos/umem_vmm.c @@ -0,0 +1,1757 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "umem_vmm.h" + + +struct sos_umem_vmm_as +{ + /** The process that owns this address space */ + struct sos_process * process; + + /** The MMU configuration of this address space */ + struct sos_mm_context * mm_context; + + /** The list of VRs in this address space */ + struct sos_umem_vmm_vr * list_vr; + + /** Heap location */ + sos_uaddr_t heap_start; + sos_size_t heap_size; /**< Updated by sos_umem_vmm_brk() */ + + /* Memory usage statistics */ + sos_size_t phys_total; /* shared + private */ + struct vm_usage + { + sos_size_t overall; + sos_size_t ro, rw, code /* all: non readable, read and read/write */; + } vm_total, vm_shrd; + + /* Page fault counters */ + sos_size_t pgflt_cow; + sos_size_t pgflt_page_in; + sos_size_t pgflt_invalid; +}; + + +struct sos_umem_vmm_vr +{ + /** The address space owning this VR */ + struct sos_umem_vmm_as *address_space; + + /** The location of the mapping in user space */ + sos_uaddr_t start; + sos_size_t size; + + /** What accesses are allowed (read, write, exec): @see + SOS_VM_MAP_PROT_* flags in hwcore/paging.h */ + sos_ui32_t access_rights; + + /** Flags of the VR. Allowed flags: + * - SOS_VR_MAP_SHARED + */ + sos_ui32_t flags; + + /** + * The callbacks for the VR called along map/unmapping of the + * resource + */ + struct sos_umem_vmm_vr_ops *ops; + + /** Description of the resource being mapped, if any */ + struct sos_umem_vmm_mapped_resource *mapped_resource; + sos_luoffset_t offset_in_resource; + + /** The VRs of an AS are linked together and are accessible by way + of as->list_vr */ + struct sos_umem_vmm_vr *prev_in_as, *next_in_as; + + /** The VRs mapping a given resource are linked together and are + accessible by way of mapped_resource->list_vr */ + struct sos_umem_vmm_vr *prev_in_mapped_resource, *next_in_mapped_resource; +}; + + +/* + * We use special slab caches to allocate AS and VR data structures + */ +static struct sos_kslab_cache * cache_of_as; +static struct sos_kslab_cache * cache_of_vr; + + +/** Temporary function to debug: list the VRs of the given As */ +void sos_dump_as(const struct sos_umem_vmm_as * as, const char *str) +{ + struct sos_umem_vmm_vr *vr; + int nb_vr; + + sos_bochs_printf("AS %p - %s:\n", as, str); + sos_bochs_printf(" physical mem: %x\n", + as->phys_total); + sos_bochs_printf(" VM (all/ro+rw/exec) tot:%x/%x+%x/%x shrd:%x/%x+%x/%x\n", + as->vm_total.overall, + as->vm_total.ro, as->vm_total.rw, as->vm_total.code, + as->vm_shrd.overall, + as->vm_shrd.ro, as->vm_shrd.rw, as->vm_shrd.code); + sos_bochs_printf(" pgflt cow=%d pgin=%d inv=%d\n", + as->pgflt_cow, as->pgflt_page_in, as->pgflt_invalid); + list_foreach_named(as->list_vr, vr, nb_vr, prev_in_as, next_in_as) + { + sos_bochs_printf(" VR[%d]=%x: [%x,%x[ (sz=%x) mr=(%x)+%llx %c%c%c fl=%x\n", + nb_vr, (unsigned)vr, + vr->start, vr->start + vr->size, vr->size, + (unsigned)vr->mapped_resource, + vr->offset_in_resource, + (vr->access_rights & SOS_VM_MAP_PROT_READ)?'r':'-', + (vr->access_rights & SOS_VM_MAP_PROT_WRITE)?'w':'-', + (vr->access_rights & SOS_VM_MAP_PROT_EXEC)?'x':'-', + (unsigned)vr->flags); + } + sos_bochs_printf("FIN (%s)\n", str); +} + + +/** + * Physical address of THE page (full of 0s) used for anonymous + * mappings + */ +sos_paddr_t sos_zero_physpage = 0 /* Initial value prior to allocation */; +sos_vaddr_t sos_zero_kernelpage = 0 /* Initial value prior to allocation */; + + +/* + * Helper functions defined at the bottom of the file + */ + +/** + * Helper function to retrieve the first VR to have a vr->end >= uaddr + */ +static struct sos_umem_vmm_vr * +find_enclosing_or_next_vr(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr); + + +/** + * Helper function to retrieve the first VR that overlaps the given + * interval, if any + */ +static struct sos_umem_vmm_vr * +find_first_intersecting_vr(struct sos_umem_vmm_as * as, + sos_uaddr_t start_uaddr, sos_size_t size); + + +/** + * Helper function to find first address where there is enough + * space. Begin to look for such an interval at or after the given + * address + * + * @param hint_addr The address where to begin the scan, or NULL + */ +static sos_uaddr_t +find_first_free_interval(struct sos_umem_vmm_as * as, + sos_uaddr_t hint_uaddr, sos_size_t size); + + +/** Called each time a VR of the AS changes. Don't cope with any + underlying physcal mapping/unmapping, COW, etc... */ +static void +as_account_change_of_vr_protection(struct sos_umem_vmm_as * as, + sos_bool_t is_shared, + sos_size_t size, + sos_ui32_t prev_access_rights, + sos_ui32_t new_access_rights); + + +sos_ret_t sos_umem_vmm_subsystem_setup() +{ + /* Allocate a new kernel physical page mapped into kernel space and + reset it with 0s */ + sos_zero_kernelpage = sos_kmem_vmm_alloc(1, SOS_KMEM_VMM_MAP); + if (sos_zero_kernelpage == (sos_vaddr_t)NULL) + return -SOS_ENOMEM; + memset((void*)sos_zero_kernelpage, 0x0, SOS_PAGE_SIZE); + + /* Keep a reference to the underlying pphysical page... */ + sos_zero_physpage = sos_paging_get_paddr(sos_zero_kernelpage); + SOS_ASSERT_FATAL(NULL != (void*)sos_zero_physpage); + sos_physmem_ref_physpage_at(sos_zero_physpage); + + /* Allocate the VR/AS caches */ + cache_of_as + = sos_kmem_cache_create("Address space structures", + sizeof(struct sos_umem_vmm_as), + 1, 0, + SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO); + if (! cache_of_as) + { + sos_physmem_unref_physpage(sos_zero_physpage); + return -SOS_ENOMEM; + } + + cache_of_vr + = sos_kmem_cache_create("Virtual Region structures", + sizeof(struct sos_umem_vmm_vr), + 1, 0, + SOS_KSLAB_CREATE_MAP + | SOS_KSLAB_CREATE_ZERO); + if (! cache_of_vr) + { + sos_physmem_unref_physpage(sos_zero_physpage); + sos_kmem_cache_destroy(cache_of_as); + return -SOS_ENOMEM; + } + + return SOS_OK; +} + + +static struct sos_umem_vmm_as * current_address_space = NULL; +struct sos_umem_vmm_as * sos_umem_vmm_get_current_as(void) +{ + return current_address_space; +} + + +sos_ret_t sos_umem_vmm_set_current_as(struct sos_umem_vmm_as * as) +{ + sos_ui32_t flags; + struct sos_umem_vmm_as *prev_as = current_address_space; + + if (current_address_space == as) + return SOS_OK; + + if (NULL != as) + { + sos_disable_IRQs(flags); + sos_process_ref(sos_umem_vmm_get_process(as)); + sos_mm_context_switch_to(sos_umem_vmm_get_mm_context(as)); + current_address_space = as; + sos_restore_IRQs(flags); + } + else + current_address_space = as; + + if (prev_as) + sos_process_unref(sos_umem_vmm_get_process(prev_as)); + + return SOS_OK; +} + + +struct sos_umem_vmm_as * +sos_umem_vmm_create_empty_as(struct sos_process *owner) +{ + struct sos_umem_vmm_as * as + = (struct sos_umem_vmm_as *) sos_kmem_cache_alloc(cache_of_as, 0); + if (! as) + return NULL; + + as->mm_context = sos_mm_context_create(); + if (NULL == as->mm_context) + { + /* Error */ + sos_kmem_cache_free((sos_vaddr_t)as); + return NULL; + } + + as->process = owner; + return as; +} + + +struct sos_umem_vmm_as * +sos_umem_vmm_duplicate_as(struct sos_umem_vmm_as * model_as, + struct sos_process *for_owner) +{ + __label__ undo_creation; + struct sos_umem_vmm_vr * model_vr; + int nb_vr; + + struct sos_umem_vmm_as * new_as + = (struct sos_umem_vmm_as *) sos_kmem_cache_alloc(cache_of_as, 0); + if (! new_as) + return NULL; + + new_as->process = for_owner; + list_init_named(new_as->list_vr, prev_in_as, next_in_as); + + /* + * Switch to the current threads' mm_context, as duplicating it implies + * being able to configure some of its mappings as read-only (for + * COW) + */ + SOS_ASSERT_FATAL(SOS_OK + == sos_thread_prepare_user_space_access(model_as, + (sos_vaddr_t) + NULL)); + + /* Copy the virtual regions */ + list_foreach_named(model_as->list_vr, model_vr, nb_vr, prev_in_as, next_in_as) + { + struct sos_umem_vmm_vr * vr; + + /* Prepare COW on the read/write private mappings */ + if ( !(model_vr->flags & SOS_VR_MAP_SHARED) + && (model_vr->access_rights & SOS_VM_MAP_PROT_WRITE) ) + { + /* Mark the underlying physical pages (if any) as + read-only */ + SOS_ASSERT_FATAL(SOS_OK + == sos_paging_prepare_COW(model_vr->start, + model_vr->size)); + } + + /* Allocate a new virtual region and copy the 'model' into it */ + vr = (struct sos_umem_vmm_vr *) sos_kmem_cache_alloc(cache_of_vr, 0); + if (! vr) + goto undo_creation; + memcpy(vr, model_vr, sizeof(*vr)); + vr->address_space = new_as; + + /* Signal the "new" mapping to the underlying VR mapper */ + if (vr->ops && vr->ops->ref) + vr->ops->ref(vr); + + /* Insert the new VR into the new AS */ + list_add_tail_named(new_as->list_vr, vr, prev_in_as, next_in_as); + + /* Insert the new VR into the list of mappings of the resource */ + list_add_tail_named(model_vr->mapped_resource->list_vr, vr, + prev_in_mapped_resource, + next_in_mapped_resource); + } + + /* Now copy the current MMU configuration */ + new_as->mm_context = sos_mm_context_duplicate(model_as->mm_context); + if (NULL == new_as->mm_context) + goto undo_creation; + + /* Correct behavior */ + new_as->heap_start = model_as->heap_start; + new_as->heap_size = model_as->heap_size; + new_as->phys_total = model_as->phys_total; + memcpy(& new_as->vm_total, & model_as->vm_total, sizeof(struct vm_usage)); + memcpy(& new_as->vm_shrd, & model_as->vm_shrd, sizeof(struct vm_usage)); + SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access()); + return new_as; + + /* Handle erroneous behavior */ + undo_creation: + SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access()); + sos_umem_vmm_delete_as(new_as); + return NULL; +} + + +sos_ret_t +sos_umem_vmm_delete_as(struct sos_umem_vmm_as * as) +{ + while(! list_is_empty_named(as->list_vr, prev_in_as, next_in_as)) + { + struct sos_umem_vmm_vr * vr; + vr = list_get_head_named(as->list_vr, prev_in_as, next_in_as); + + /* Remove the vr from the lists */ + list_pop_head_named(as->list_vr, prev_in_as, next_in_as); + list_delete_named(vr->mapped_resource->list_vr, vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Signal to the underlying VR mapper that the mapping is + suppressed */ + if (vr->ops) + { + if (vr->ops->unmap) + vr->ops->unmap(vr, vr->start, vr->size); + if (vr->ops->unref) + vr->ops->unref(vr); + } + + sos_kmem_cache_free((sos_vaddr_t)vr); + } + + /* Release MMU configuration */ + if (as->mm_context) + sos_mm_context_unref(as->mm_context); + + /* Now unallocate main address space construct */ + sos_kmem_cache_free((sos_vaddr_t)as); + + return SOS_OK; +} + + +struct sos_process * +sos_umem_vmm_get_process(struct sos_umem_vmm_as * as) +{ + return as->process; +} + + +struct sos_mm_context * +sos_umem_vmm_get_mm_context(struct sos_umem_vmm_as * as) +{ + return as->mm_context; +} + + +struct sos_umem_vmm_vr * +sos_umem_vmm_get_vr_at_address(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr) +{ + struct sos_umem_vmm_vr * vr; + vr = find_enclosing_or_next_vr(as, uaddr); + if (! vr) + return NULL; + + /* Ok uaddr <= vr->end, but do we have uaddr > vr->start ? */ + if (uaddr < vr->start) + return NULL; + + return vr; +} + + +struct sos_umem_vmm_as * +sos_umem_vmm_get_as_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->address_space; +} + + +struct sos_umem_vmm_vr_ops * +sos_umem_vmm_get_ops_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->ops; +} + + +sos_ui32_t sos_umem_vmm_get_prot_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->access_rights; +} + + +sos_ui32_t sos_umem_vmm_get_flags_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->flags; +} + + +struct sos_umem_vmm_mapped_resource * +sos_umem_vmm_get_mapped_resource_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->mapped_resource; +} + + +sos_uaddr_t sos_umem_vmm_get_start_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->start; +} + + +sos_size_t sos_umem_vmm_get_size_of_vr(struct sos_umem_vmm_vr * vr) +{ + return vr->size; +} + + +sos_luoffset_t sos_umem_vmm_get_offset_in_resource(struct sos_umem_vmm_vr * vr) +{ + return vr->offset_in_resource; +} + + +sos_ret_t +sos_umem_vmm_set_ops_of_vr(struct sos_umem_vmm_vr * vr, + struct sos_umem_vmm_vr_ops * ops) +{ + /* Don't allow to overwrite any preceding VR ops */ + SOS_ASSERT_FATAL(NULL == vr->ops); + + vr->ops = ops; + return SOS_OK; +} + + +/** + * When resize asks to map the resource elsewhere, make sure not to + * overwrite the offset_in_resource field + */ +#define INTERNAL_MAP_CALLED_FROM_MREMAP (1 << 8) + +sos_ret_t +sos_umem_vmm_map(struct sos_umem_vmm_as * as, + sos_uaddr_t * /*in/out*/uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + struct sos_umem_vmm_mapped_resource * resource, + sos_luoffset_t offset_in_resource) +{ + __label__ return_mmap; + sos_uaddr_t hint_uaddr; + struct sos_umem_vmm_vr *prev_vr, *next_vr, *vr, *preallocated_vr; + sos_bool_t merge_with_preceding, merge_with_next, used_preallocated_vr; + sos_bool_t internal_map_called_from_mremap + = (flags & INTERNAL_MAP_CALLED_FROM_MREMAP); + + sos_ret_t retval = SOS_OK; + used_preallocated_vr = FALSE; + hint_uaddr = *uaddr; + + /* Default mapping address is NULL */ + *uaddr = (sos_vaddr_t)NULL; + + if (! resource) + return -SOS_EINVAL; + if (! resource->mmap) + return -SOS_EPERM; + + if (! SOS_IS_PAGE_ALIGNED(hint_uaddr)) + return -SOS_EINVAL; + + if (size <= 0) + return -SOS_EINVAL; + size = SOS_PAGE_ALIGN_SUP(size); + + if (flags & SOS_VR_MAP_SHARED) + { + /* Make sure the mapped resource allows the required protection flags */ + if ( ( (access_rights & SOS_VM_MAP_PROT_READ) + && !(resource->allowed_access_rights & SOS_VM_MAP_PROT_READ) ) + || ( (access_rights & SOS_VM_MAP_PROT_WRITE) + && !(resource->allowed_access_rights & SOS_VM_MAP_PROT_WRITE) ) + || ( (access_rights & SOS_VM_MAP_PROT_EXEC) + && !(resource->allowed_access_rights & SOS_VM_MAP_PROT_EXEC)) ) + return -SOS_EPERM; + } + + /* Sanity checks over the offset_in_resource parameter */ + if ( !internal_map_called_from_mremap + && ( resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS ) ) + /* Initial offset ignored for anonymous mappings */ + { + /* Nothing to check */ + } + + /* Make sure that the offset in resource won't overflow */ + else if (offset_in_resource + size <= offset_in_resource) + return -SOS_EINVAL; + + /* Filter out unsupported flags */ + access_rights &= (SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE + | SOS_VM_MAP_PROT_EXEC); + flags &= (SOS_VR_MAP_SHARED + | SOS_VR_MAP_FIXED); + + /* Pre-allocate a new VR. Because once we found a valid slot inside + the VR list, we don't want the list to be altered by another + process */ + preallocated_vr + = (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0); + if (! preallocated_vr) + return -SOS_ENOMEM; + + /* Compute the user address of the new mapping */ + if (flags & SOS_VR_MAP_FIXED) + { + /* + * The address is imposed + */ + + /* Make sure the hint_uaddr hint is valid */ + if (! SOS_PAGING_IS_USER_AREA(hint_uaddr, size) ) + { retval = -SOS_EINVAL; goto return_mmap; } + + /* Unmap any overlapped VR */ + retval = sos_umem_vmm_unmap(as, hint_uaddr, size); + if (SOS_OK != retval) + { goto return_mmap; } + } + else + { + /* + * A free range has to be determined + */ + + /* Find a suitable free VR */ + hint_uaddr = find_first_free_interval(as, hint_uaddr, size); + if (! hint_uaddr) + { retval = -SOS_ENOMEM; goto return_mmap; } + } + + /* For anonymous resource mappings, set the initial + offset_in_resource to the initial virtual start address in user + space */ + if ( !internal_map_called_from_mremap + && (resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS ) ) + offset_in_resource = hint_uaddr; + + /* Lookup next and previous VR, if any. This will allow us to merge + the regions, when possible */ + next_vr = find_enclosing_or_next_vr(as, hint_uaddr); + if (next_vr) + { + /* Find previous VR, if any */ + prev_vr = next_vr->prev_in_as; + /* The list is curcular: it may happen that we looped over the + tail of the list (ie the list is a singleton) */ + if (prev_vr->start > hint_uaddr) + prev_vr = NULL; /* No preceding VR */ + } + else + { + /* Otherwise we went beyond the last VR */ + prev_vr = list_get_tail_named(as->list_vr, prev_in_as, next_in_as); + } + + /* Merge with preceding VR ? */ + merge_with_preceding + = ( (NULL != prev_vr) + && (prev_vr->mapped_resource == resource) + && (prev_vr->offset_in_resource + prev_vr->size == offset_in_resource) + && (prev_vr->start + prev_vr->size == hint_uaddr) + && (prev_vr->flags == flags) + && (prev_vr->access_rights == access_rights) ); + + /* Merge with next VR ? */ + merge_with_next + = ( (NULL != next_vr) + && (next_vr->mapped_resource == resource) + && (offset_in_resource + size == next_vr->offset_in_resource) + && (hint_uaddr + size == next_vr->start) + && (next_vr->flags == flags) + && (next_vr->access_rights == access_rights) ); + + if (merge_with_preceding && merge_with_next) + { + /* Widen the prev_vr VR to encompass both the new VR and the next_vr */ + vr = prev_vr; + vr->size += size + next_vr->size; + + /* Remove the next_vr VR */ + list_delete_named(as->list_vr, next_vr, prev_in_as, next_in_as); + list_delete_named(next_vr->mapped_resource->list_vr, next_vr, + prev_in_mapped_resource, next_in_mapped_resource); + + if (next_vr->ops && next_vr->ops->unref) + next_vr->ops->unref(next_vr); + + sos_kmem_vmm_free((sos_vaddr_t) next_vr); + } + else if (merge_with_preceding) + { + /* Widen the prev_vr VR to encompass the new VR */ + vr = prev_vr; + vr->size += size; + } + else if (merge_with_next) + { + /* Widen the next_vr VR to encompass the new VR */ + vr = next_vr; + vr->start -= size; + vr->size += size; + } + else + { + /* Allocate a brand new VR and insert it into the list */ + + vr = preallocated_vr; + used_preallocated_vr = TRUE; + + vr->start = hint_uaddr; + vr->size = size; + vr->access_rights = access_rights; + vr->flags = flags; + vr->mapped_resource = resource; + vr->offset_in_resource = offset_in_resource; + + /* Insert VR in address space */ + vr->address_space = as; + if (prev_vr) + list_insert_after_named(as->list_vr, prev_vr, vr, + prev_in_as, next_in_as); + else + list_add_head_named(as->list_vr, vr, prev_in_as, next_in_as); + + list_add_tail_named(vr->mapped_resource->list_vr, vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Signal the resource we are mapping it */ + if (resource && resource->mmap) + { + retval = resource->mmap(vr); + if (SOS_OK != retval) + { + retval = sos_umem_vmm_unmap(as, vr->start, vr->size); + goto return_mmap; + } + + /* The page_in method is MANDATORY for mapped resources */ + SOS_ASSERT_FATAL(vr->ops && vr->ops->page_in); + } + + if (vr->ops && vr->ops->ref) + vr->ops->ref(vr); + } + + /* Ok, fine, we got it right ! Return the address to the caller */ + *uaddr = hint_uaddr; + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + size, 0, vr->access_rights); + retval = SOS_OK; + + return_mmap: + if (! used_preallocated_vr) + sos_kmem_vmm_free((sos_vaddr_t)preallocated_vr); + + return retval; +} + + +sos_ret_t +sos_umem_vmm_unmap(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size) +{ + struct sos_umem_vmm_vr *vr, *preallocated_vr; + sos_bool_t used_preallocated_vr; + sos_bool_t need_to_change_address_space = FALSE; + + if (! SOS_IS_PAGE_ALIGNED(uaddr)) + return -SOS_EINVAL; + if (size <= 0) + return -SOS_EINVAL; + size = SOS_PAGE_ALIGN_SUP(size); + + /* Make sure the uaddr is valid */ + if (! SOS_PAGING_IS_USER_AREA(uaddr, size) ) + return -SOS_EINVAL; + + /* In some cases, the unmapping might imply a VR to be split into + 2. Actually, allocating a new VR can be a blocking operation, but + actually we can block now, it won't do no harm. But we must be + careful not to block later, while altering the VR lists: that's + why we pre-allocate now. */ + used_preallocated_vr = FALSE; + preallocated_vr + = (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0); + if (! preallocated_vr) + return -SOS_ENOMEM; + + /* Find any VR intersecting with the given interval */ + vr = find_first_intersecting_vr(as, uaddr, size); + + /* Unmap (part of) the VR covered by [uaddr .. uaddr+size[ */ + while (NULL != vr) + { + /* Went past the end of the *circular* list => back at the + beginning ? */ + if (vr->start + vr->size <= uaddr) + /* Yes, stop now */ + break; + + /* Went beyond the region to unmap ? */ + if (uaddr + size <= vr->start) + /* Yes, stop now */ + break; + + /* VR totally unmapped ? */ + if ((vr->start >= uaddr) + && (vr->start + vr->size <= uaddr + size)) + { + struct sos_umem_vmm_vr *next_vr; + + /* Yes: signal we remove it completely */ + if (vr->ops && vr->ops->unmap) + vr->ops->unmap(vr, vr->start, vr->size); + + /* Remove it from the AS list now */ + next_vr = vr->next_in_as; + if (next_vr == vr) /* singleton ? */ + next_vr = NULL; + list_delete_named(as->list_vr, vr, prev_in_as, next_in_as); + + /* Remove from the list of VRs mapping the resource */ + list_delete_named(vr->mapped_resource->list_vr, vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + if (vr->ops && vr->ops->unref) + vr->ops->unref(vr); + + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + vr->size, vr->access_rights, 0); + sos_kmem_vmm_free((sos_vaddr_t)vr); + + /* Prepare next iteration */ + vr = next_vr; + continue; + } + + /* unmapped region lies completely INSIDE the the VR */ + else if ( (vr->start < uaddr) + && (vr->start + vr->size > uaddr + size) ) + { + /* VR has to be split into 2 */ + + /* Use the preallocated VR and copy the VR into it */ + used_preallocated_vr = TRUE; + memcpy(preallocated_vr, vr, sizeof(*vr)); + + /* Adjust the start/size of both VRs */ + preallocated_vr->start = uaddr + size; + preallocated_vr->size = vr->start + vr->size - (uaddr + size); + preallocated_vr->offset_in_resource += uaddr + size - vr->start; + vr->size = uaddr - vr->start; + + /* Insert the new VR into the list */ + list_insert_after_named(as->list_vr, vr, preallocated_vr, + prev_in_as, next_in_as); + list_add_tail_named(vr->mapped_resource->list_vr, preallocated_vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Signal the changes to the underlying resource */ + if (vr->ops && vr->ops->unmap) + vr->ops->unmap(vr, uaddr, size); + if (preallocated_vr->ops && preallocated_vr->ops->ref) + preallocated_vr->ops->ref(preallocated_vr); + + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + size, vr->access_rights, 0); + + /* No need to go further */ + break; + } + + /* Unmapped region only affects the START address of the VR */ + else if (uaddr <= vr->start) + { + sos_size_t translation = uaddr + size - vr->start; + + /* Shift the VR */ + vr->size -= translation; + vr->offset_in_resource += translation; + vr->start += translation; + + /* Signal unmapping */ + if (vr->ops && vr->ops->unmap) + vr->ops->unmap(vr, uaddr + size, + translation); + + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + translation, + vr->access_rights, 0); + + /* No need to go further, we reached the last VR that + overlaps the unmapped region */ + break; + } + + /* Unmapped region only affects the ENDING address of the VR */ + else if (uaddr + size >= vr->start + vr->size) + { + sos_size_t unmapped_size = vr->start + vr->size - uaddr; + + /* Resize VR */ + vr->size = uaddr - vr->start; + + /* Signal unmapping */ + if (vr->ops && vr->ops->unmap) + vr->ops->unmap(vr, uaddr, unmapped_size); + + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + unmapped_size, + vr->access_rights, 0); + + vr = vr->next_in_as; + continue; + } + + sos_display_fatal_error("BUG uaddr=%x sz=%x vr_start=%x, vr_sz=%x", + uaddr, size, vr->start, vr->size); + } + + /* When called from mresize, the address space is already squatted, + so we don't have to change it again */ + need_to_change_address_space + = (as != sos_thread_get_current()->squatted_address_space); + + if (need_to_change_address_space) + SOS_ASSERT_FATAL(SOS_OK + == sos_thread_prepare_user_space_access(as, + (sos_vaddr_t) + NULL)); + + + /* Begin independent sub-block */ + { + sos_ret_t sz_unmapped = sos_paging_unmap_interval(uaddr, size); + SOS_ASSERT_FATAL(sz_unmapped >= 0); + as->phys_total -= sz_unmapped; + } + /* End independent sub-block */ + + if (need_to_change_address_space) + SOS_ASSERT_FATAL(SOS_OK == sos_thread_end_user_space_access()); + + if (! used_preallocated_vr) + sos_kmem_vmm_free((sos_vaddr_t)preallocated_vr); + + return SOS_OK; +} + + +sos_ret_t +sos_umem_vmm_chprot(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size, + sos_ui32_t new_access_rights) +{ + struct sos_umem_vmm_vr *start_vr, *vr, + *preallocated_middle_vr, *preallocated_right_vr; + sos_bool_t used_preallocated_middle_vr, used_preallocated_right_vr; + + if (! SOS_IS_PAGE_ALIGNED(uaddr)) + return -SOS_EINVAL; + if (size <= 0) + return -SOS_EINVAL; + size = SOS_PAGE_ALIGN_SUP(size); + + /* Make sure the uaddr is valid */ + if (! SOS_PAGING_IS_USER_AREA(uaddr, size) ) + return -SOS_EINVAL; + + /* Pre-allocate 2 new VRs (same reason as for unmap). Because chprot + may imply at most 2 regions to be split */ + used_preallocated_middle_vr = FALSE; + used_preallocated_right_vr = FALSE; + preallocated_middle_vr + = (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0); + if (! preallocated_middle_vr) + return -SOS_ENOMEM; + preallocated_right_vr + = (struct sos_umem_vmm_vr *)sos_kmem_cache_alloc(cache_of_vr, 0); + if (! preallocated_right_vr) + { + sos_kmem_vmm_free((sos_vaddr_t)preallocated_middle_vr); + return -SOS_ENOMEM; + } + + /* Find any VR intersecting with the given interval */ + start_vr = find_first_intersecting_vr(as, uaddr, size); + if (NULL == start_vr) + return SOS_OK; + + /* First of all: make sure we are allowed to change the access + rights of all the VRs concerned by the chprot */ + vr = start_vr; + while (TRUE) + { + /* Went past the end of the *circular* list => back at the + begining ? */ + if (vr->start + vr->size <= uaddr) + /* Yes, stop now */ + break; + + /* Went beyond the region to chprot ? */ + if (uaddr + size < vr->start) + /* Yes, stop now */ + break; + + if (vr->flags & SOS_VR_MAP_SHARED) + { + /* Make sure the mapped resource allows the required + protection flags */ + if ( ( (new_access_rights & SOS_VM_MAP_PROT_READ) + && !(vr->mapped_resource->allowed_access_rights + & SOS_VM_MAP_PROT_READ) ) + || ( (new_access_rights & SOS_VM_MAP_PROT_WRITE) + && !(vr->mapped_resource->allowed_access_rights + & SOS_VM_MAP_PROT_WRITE) ) + || ( (new_access_rights & SOS_VM_MAP_PROT_EXEC) + && !(vr->mapped_resource->allowed_access_rights + & SOS_VM_MAP_PROT_EXEC) ) ) + return -SOS_EPERM; + } + + vr = vr->next_in_as; + } + + /* Change the access rights of the VRs covered by [uaddr + .. uaddr+size[ */ + vr = start_vr; + while (TRUE) + { + + /* Went past the end of the *circular* list => back at the + begining ? */ + if (vr->start + vr->size <= uaddr) + /* Yes, stop now */ + break; + + /* Went beyond the region to chprot ? */ + if (uaddr + size <= vr->start) + /* Yes, stop now */ + break; + + /* Access rights unchanged ? */ + if (vr->access_rights == new_access_rights) + /* nop */ + { + vr = vr->next_in_as; + continue; + } + + /* VR totally chprot ? */ + if ((vr->start >= uaddr) + && (vr->start + vr->size <= uaddr + size)) + { + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + vr->size, vr->access_rights, + new_access_rights); + vr->access_rights = new_access_rights; + + if (vr->flags & SOS_VR_MAP_SHARED) + /* For shared mappings: effectively change the access + rights of the physical pages */ + sos_paging_set_prot_of_interval(vr->start, vr->size, + new_access_rights); + else + /* Private mapping */ + { + /* For private mappings, we set the new access_rights + only if it becomes read-only. For private mappings + that become writable, we don't do anything: we keep + the access rights unchanged to preserve the COW + semantics */ + if (! (new_access_rights & SOS_VM_MAP_PROT_WRITE)) + sos_paging_set_prot_of_interval(vr->start, vr->size, + new_access_rights); + } + + vr = vr->next_in_as; + continue; + } + + /* chprot region lies completely INSIDE the VR */ + else if ( (vr->start < uaddr) + && (vr->start + vr->size > uaddr + size) ) + { + /* VR has to be split into 3 */ + + /* Use the preallocated VRs and copy the VR into them */ + SOS_ASSERT_FATAL(! used_preallocated_middle_vr); + SOS_ASSERT_FATAL(! used_preallocated_right_vr); + used_preallocated_middle_vr = TRUE; + memcpy(preallocated_middle_vr, vr, sizeof(*vr)); + used_preallocated_right_vr = TRUE; + memcpy(preallocated_right_vr, vr, sizeof(*vr)); + + /* Adjust the start/size of the VRs */ + preallocated_middle_vr->start = uaddr; + preallocated_middle_vr->size = size; + preallocated_right_vr->start = uaddr + size; + preallocated_right_vr->size = vr->start + vr->size + - (uaddr + size); + preallocated_middle_vr->offset_in_resource + += uaddr - vr->start; + preallocated_right_vr->offset_in_resource + += uaddr + size - vr->start; + vr->size = uaddr - vr->start; + + /* Account for change in VRs */ + preallocated_middle_vr->access_rights = new_access_rights; + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + size, vr->access_rights, + new_access_rights); + + /* Insert the new VRs into the lists */ + list_insert_after_named(as->list_vr, vr, preallocated_middle_vr, + prev_in_as, next_in_as); + list_insert_after_named(as->list_vr, preallocated_middle_vr, + preallocated_right_vr, + prev_in_as, next_in_as); + + list_add_tail_named(vr->mapped_resource->list_vr, + preallocated_middle_vr, + prev_in_mapped_resource, + next_in_mapped_resource); + list_add_tail_named(vr->mapped_resource->list_vr, + preallocated_right_vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Effectively change the access rights of the physical pages */ + if (!(preallocated_middle_vr->flags & SOS_VR_MAP_SHARED) + && (new_access_rights & SOS_VM_MAP_PROT_WRITE)) + /* For private mappings with write access, prepare for COW */ + sos_paging_prepare_COW(preallocated_middle_vr->start, + preallocated_middle_vr->size); + else + sos_paging_set_prot_of_interval(preallocated_middle_vr->start, + preallocated_middle_vr->size, + new_access_rights); + + if (preallocated_right_vr->ops && preallocated_right_vr->ops->ref) + preallocated_right_vr->ops->ref(preallocated_right_vr); + if (preallocated_middle_vr->ops && preallocated_middle_vr->ops->ref) + preallocated_middle_vr->ops->ref(preallocated_middle_vr); + + /* No need to go further */ + break; + } + + /* Chprot region only affects the START address of the VR */ + else if (uaddr <= vr->start) + { + /* Split the region into 2 */ + sos_uoffset_t offset_in_region = uaddr + size - vr->start; + + /* Use the preallocated VRs and copy the VR into them */ + SOS_ASSERT_FATAL(! used_preallocated_middle_vr); + used_preallocated_middle_vr = TRUE; + memcpy(preallocated_middle_vr, vr, sizeof(*vr)); + + /* Adjust the start/size of the VRs */ + preallocated_middle_vr->start += offset_in_region; + preallocated_middle_vr->size -= offset_in_region; + vr->size = offset_in_region; + preallocated_middle_vr->offset_in_resource += offset_in_region; + + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + vr->size, + vr->access_rights, + new_access_rights); + vr->access_rights = new_access_rights; + + /* Insert the new VR into the lists */ + list_insert_after_named(as->list_vr, vr, + preallocated_middle_vr, + prev_in_as, next_in_as); + list_add_tail_named(vr->mapped_resource->list_vr, + preallocated_middle_vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Effectively change the access rights of the physical pages */ + if (!(vr->flags & SOS_VR_MAP_SHARED) + && (new_access_rights & SOS_VM_MAP_PROT_WRITE)) + /* For private mappings with write access, prepare for COW */ + sos_paging_prepare_COW(vr->start, vr->size); + else + sos_paging_set_prot_of_interval(vr->start, vr->size, + new_access_rights); + + if (preallocated_middle_vr->ops && preallocated_middle_vr->ops->ref) + preallocated_middle_vr->ops->ref(preallocated_middle_vr); + + /* Ne need to go further (we reached the last VR that + overlaps the given interval to chprot) */ + break; + } + + /* Chprot region only affects the ENDING address of the VR */ + else if (uaddr + size >= vr->start + vr->size) + { + /* Split the region into 2 */ + sos_uoffset_t offset_in_region = uaddr - vr->start; + + /* Use the preallocated VRs and copy the VR into them */ + SOS_ASSERT_FATAL(! used_preallocated_right_vr); + used_preallocated_right_vr = TRUE; + memcpy(preallocated_right_vr, vr, sizeof(*vr)); + + /* Adjust the start/size of the VRs */ + preallocated_right_vr->start += offset_in_region; + preallocated_right_vr->size -= offset_in_region; + vr->size = offset_in_region; + preallocated_right_vr->offset_in_resource += offset_in_region; + + /* Account for change in VRs */ + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + preallocated_right_vr->size, + vr->access_rights, + new_access_rights); + preallocated_right_vr->access_rights = new_access_rights; + + /* Insert the new VR into the lists */ + list_insert_after_named(as->list_vr, vr, + preallocated_right_vr, + prev_in_as, next_in_as); + list_add_tail_named(vr->mapped_resource->list_vr, + preallocated_right_vr, + prev_in_mapped_resource, + next_in_mapped_resource); + + /* Effectively change the access rights of the physical pages */ + if (!(preallocated_right_vr->flags & SOS_VR_MAP_SHARED) + && (new_access_rights & SOS_VM_MAP_PROT_WRITE)) + /* For private mappings with write access, prepare for COW */ + sos_paging_prepare_COW(preallocated_right_vr->start, + preallocated_right_vr->size); + else + sos_paging_set_prot_of_interval(preallocated_right_vr->start, + preallocated_right_vr->size, + new_access_rights); + + if (preallocated_right_vr->ops && preallocated_right_vr->ops->ref) + preallocated_right_vr->ops->ref(preallocated_right_vr); + + vr = vr->next_in_as; + continue; + } + + sos_display_fatal_error("BUG"); + } + + if (! used_preallocated_middle_vr) + sos_kmem_vmm_free((sos_vaddr_t)preallocated_middle_vr); + if (! used_preallocated_right_vr) + sos_kmem_vmm_free((sos_vaddr_t)preallocated_right_vr); + + return SOS_OK; +} + + +sos_ret_t +sos_umem_vmm_sync(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size, + sos_ui32_t flags) +{ + if (! SOS_IS_PAGE_ALIGNED(uaddr)) + return -SOS_EINVAL; + if (size <= 0) + return -SOS_EINVAL; + size = SOS_PAGE_ALIGN_SUP(size); + + /* Make sure the uaddr is valid */ + if (! SOS_PAGING_IS_USER_AREA(uaddr, size) ) + return -SOS_EINVAL; + + /* Go from page to page, and for each dirty page in the region, call + the sync_page method */ + while (TRUE) + { + struct sos_umem_vmm_vr *vr; + + if (size <= 0) + break; + + /* Find any VR intersecting with the given interval */ + vr = find_first_intersecting_vr(as, uaddr, size); + if (NULL == vr) + break; + + /* For private or anonymous mappings => no backing store */ + if ( !(vr->flags & SOS_VR_MAP_SHARED) + || (vr->mapped_resource->flags & SOS_MAPPED_RESOURCE_ANONYMOUS) + + /* Likewise for non msync-able regions */ + || ! vr->ops->sync_page ) + { + if (size <= vr->size) + break; + + uaddr += vr->size; + size -= vr->size; + } + + /* Find the next dirty page in this VR */ + for ( ; (size > 0) + && (uaddr - vr->start < vr->size) ; + uaddr += SOS_PAGE_SIZE, + size -= SOS_PAGE_SIZE) + if (sos_paging_is_dirty(uaddr)) + { + /* Synchronize it with its backing store */ + vr->ops->sync_page(vr, uaddr, flags); + uaddr += SOS_PAGE_SIZE; + size -= SOS_PAGE_SIZE; + break; + } + } + + return SOS_OK; +} + + +sos_ret_t +sos_umem_vmm_resize(struct sos_umem_vmm_as * as, + sos_uaddr_t old_uaddr, sos_size_t old_size, + sos_uaddr_t *new_uaddr, sos_size_t new_size, + sos_ui32_t flags) +{ + sos_luoffset_t new_offset_in_resource; + sos_bool_t must_move_vr = FALSE; + struct sos_umem_vmm_vr *vr, *prev_vr, *next_vr; + + /* Make sure the new uaddr is valid */ + if (! SOS_PAGING_IS_USER_AREA(*new_uaddr, new_size) ) + return -SOS_EINVAL; + + old_uaddr = SOS_PAGE_ALIGN_INF(old_uaddr); + old_size = SOS_PAGE_ALIGN_SUP(old_size); + if (! SOS_IS_PAGE_ALIGNED(*new_uaddr)) + return -SOS_EINVAL; + if (new_size <= 0) + return -SOS_EINVAL; + new_size = SOS_PAGE_ALIGN_SUP(new_size); + + /* Lookup a VR overlapping the address range */ + vr = find_first_intersecting_vr(as, old_uaddr, old_size); + if (! vr) + return -SOS_EINVAL; + + /* Make sure there is exactly ONE VR overlapping the area */ + if ( (vr->start > old_uaddr) + || (vr->start + vr->size < old_uaddr + old_size) ) + return -SOS_EINVAL; + + /* Retrieve the prev/next VR if they exist (the VR are on circular + list) */ + prev_vr = vr->prev_in_as; + if (prev_vr->start >= vr->start) + prev_vr = NULL; + next_vr = vr->prev_in_as; + if (next_vr->start <= vr->start) + next_vr = NULL; + + /* + * Compute new offset inside the mapped resource, if any + */ + + /* Don't allow to resize if the uaddr goes beyond the 'offset 0' of + the resource */ + if ( (*new_uaddr < vr->start) + && (vr->start - *new_uaddr > vr->offset_in_resource) ) + return -SOS_EINVAL; + + /* Compute new offset in the resource (overflow-safe) */ + if (vr->start > *new_uaddr) + new_offset_in_resource + = vr->offset_in_resource + - (vr->start - *new_uaddr); + else + new_offset_in_resource + = vr->offset_in_resource + + (*new_uaddr - vr->start); + + /* If other VRs would be affected by this resizing, then the VR must + be moved */ + if (prev_vr && (prev_vr->start + prev_vr->size > *new_uaddr)) + must_move_vr |= TRUE; + if (next_vr && (next_vr->start < *new_uaddr + new_size)) + must_move_vr |= TRUE; + + /* If VR would be out-of-user-space, it must be moved */ + if (! SOS_PAGING_IS_USER_AREA(*new_uaddr, new_size) ) + must_move_vr |= TRUE; + + /* The VR must be moved but the user forbids it */ + if ( must_move_vr && !(flags & SOS_VR_REMAP_MAYMOVE) ) + return -SOS_EINVAL; + + /* If the VR must be moved, we simply map the resource elsewhere and + unmap the current VR */ + if (must_move_vr) + { + sos_uaddr_t uaddr, result_uaddr; + sos_ret_t retval; + + result_uaddr = *new_uaddr; + retval = sos_umem_vmm_map(as, & result_uaddr, new_size, + vr->access_rights, + vr->flags | INTERNAL_MAP_CALLED_FROM_MREMAP, + vr->mapped_resource, + new_offset_in_resource); + if (SOS_OK != retval) + return retval; + + /* Remap the physical pages at their new address */ + for (uaddr = vr->start ; + uaddr < vr->start + vr->size ; + uaddr += SOS_PAGE_SIZE) + { + sos_paddr_t paddr; + sos_ui32_t prot; + sos_uaddr_t vaddr; + + if (uaddr < *new_uaddr) + continue; + if (uaddr > *new_uaddr + new_size) + continue; + + /* Compute destination virtual address (should be + overflow-safe) */ + if (vr->start >= *new_uaddr) + vaddr = result_uaddr + + (uaddr - vr->start) + + (vr->start - *new_uaddr); + else + vaddr = result_uaddr + + (uaddr - vr->start) + - (*new_uaddr - vr->start); + + paddr = sos_paging_get_paddr(uaddr); + if (! paddr) + /* No physical page mapped at this address yet */ + continue; + + prot = sos_paging_get_prot(uaddr); + SOS_ASSERT_FATAL(prot); + + /* Remap it at its destination address */ + retval = sos_paging_map(paddr, vaddr, TRUE, prot); + if (SOS_OK != retval) + { + sos_umem_vmm_unmap(as, result_uaddr, new_size); + return retval; + } + } + + retval = sos_umem_vmm_unmap(as, vr->start, vr->size); + if (SOS_OK != retval) + { + sos_umem_vmm_unmap(as, result_uaddr, new_size); + return retval; + } + + *new_uaddr = result_uaddr; + return retval; + } + + /* Otherwise we simply resize the VR, taking care of unmapping + what's been unmapped */ + + if (*new_uaddr + new_size < vr->start + vr->size) + sos_umem_vmm_unmap(as, *new_uaddr + new_size, + vr->start + vr->size - (*new_uaddr + new_size)); + else + { + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + *new_uaddr + new_size + - (vr->start + vr->size), + 0, vr->access_rights); + vr->size += *new_uaddr + new_size - (vr->start + vr->size); + } + + if (*new_uaddr > vr->start) + sos_umem_vmm_unmap(as, vr->start, *new_uaddr - vr->start); + else + { + as_account_change_of_vr_protection(as, vr->flags & SOS_VR_MAP_SHARED, + vr->start - *new_uaddr, + 0, vr->access_rights); + vr->size += vr->start - *new_uaddr; + vr->start = *new_uaddr; + vr->offset_in_resource = new_offset_in_resource; + } + + SOS_ASSERT_FATAL(vr->start == *new_uaddr); + SOS_ASSERT_FATAL(vr->size == new_size); + SOS_ASSERT_FATAL(vr->offset_in_resource == new_offset_in_resource); + + return SOS_OK; +} + + +sos_ret_t sos_umem_vmm_try_resolve_page_fault(sos_uaddr_t uaddr, + sos_bool_t write_access, + sos_bool_t user_access) +{ + struct sos_umem_vmm_as *as; + struct sos_umem_vmm_vr *vr; + + as = current_address_space; + if (! as) + return -SOS_EFAULT; + + vr = find_first_intersecting_vr(as, uaddr, 1); + if (! vr) + return -SOS_EFAULT; + + /* Write on a read-only VR */ + if (write_access && !(vr->access_rights & SOS_VM_MAP_PROT_WRITE)) + return -SOS_EFAULT; + + /* Write on a COW VR */ + if (write_access && !(vr->flags & SOS_VR_MAP_SHARED)) + { + if (SOS_OK == sos_paging_try_resolve_COW(uaddr)) + { + as->pgflt_cow ++; + return SOS_OK; + } + } + + /* Ask the underlying resource to resolve the page fault */ + if (SOS_OK != vr->ops->page_in(vr, uaddr, write_access)) + { + as->pgflt_invalid ++; + return -SOS_EFAULT; + } + + as->phys_total += SOS_PAGE_SIZE; + as->pgflt_page_in ++; + + /* For a private mapping, keep the mapping read-only */ + if (!(vr->flags & SOS_VR_MAP_SHARED)) + { + sos_paging_prepare_COW(SOS_PAGE_ALIGN_INF(uaddr), + SOS_PAGE_SIZE); + } + + return SOS_OK; +} + + +sos_ret_t +sos_umem_vmm_init_heap(struct sos_umem_vmm_as * as, + sos_uaddr_t heap_start) +{ + SOS_ASSERT_FATAL(! as->heap_start); + + as->heap_start = heap_start; + as->heap_size = 0; + return SOS_OK; +} + + +sos_uaddr_t +sos_umem_vmm_brk(struct sos_umem_vmm_as * as, + sos_uaddr_t new_top_uaddr) +{ + sos_uaddr_t new_start; + sos_size_t new_size; + SOS_ASSERT_FATAL(as->heap_start); + + if (! new_top_uaddr) + return as->heap_start + as->heap_size; + + if (new_top_uaddr == as->heap_start + as->heap_size) + return as->heap_start + as->heap_size; + + if (new_top_uaddr < as->heap_start) + return (sos_uaddr_t)NULL; + + new_top_uaddr = SOS_PAGE_ALIGN_SUP(new_top_uaddr); + new_start = as->heap_start; + new_size = new_top_uaddr - as->heap_start; + + /* First call to brk: we must map /dev/zero */ + if (! as->heap_size) + { + if (SOS_OK != sos_dev_zero_map(as, & as->heap_start, + new_size, + SOS_VM_MAP_PROT_READ + | SOS_VM_MAP_PROT_WRITE, + 0 /* private non-fixed */)) + return (sos_uaddr_t)NULL; + + as->heap_size = new_size; + return as->heap_start + as->heap_size; + } + + /* Otherwise we just have to unmap or resize the region */ + if (new_size <= 0) + { + if (SOS_OK != sos_umem_vmm_unmap(as, + as->heap_start, as->heap_size)) + return (sos_uaddr_t)NULL; + } + else + { + if (SOS_OK != sos_umem_vmm_resize(as, + as->heap_start, as->heap_size, + & new_start, new_size, + 0)) + return (sos_uaddr_t)NULL; + } + + SOS_ASSERT_FATAL(new_start == as->heap_start); + as->heap_size = new_size; + return new_top_uaddr; +} + + +static struct sos_umem_vmm_vr * +find_enclosing_or_next_vr(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr) +{ + struct sos_umem_vmm_vr *vr; + int nb_vr; + + if (! SOS_PAGING_IS_USER_AREA(uaddr, 1) ) + return NULL; + + list_foreach_named(as->list_vr, vr, nb_vr, prev_in_as, next_in_as) + { + /* Equivalent to "if (uaddr < vr->start + vr->size)" but more + robust (resilient to integer overflows) */ + if (uaddr <= vr->start + (vr->size - 1)) + return vr; + } + + return NULL; +} + + +static struct sos_umem_vmm_vr * +find_first_intersecting_vr(struct sos_umem_vmm_as * as, + sos_uaddr_t start_uaddr, sos_size_t size) +{ + struct sos_umem_vmm_vr * vr; + vr = find_enclosing_or_next_vr(as, start_uaddr); + if (! vr) + return NULL; + + if (start_uaddr + size <= vr->start) + return NULL; + + return vr; +} + + +static sos_uaddr_t +find_first_free_interval(struct sos_umem_vmm_as * as, + sos_uaddr_t hint_uaddr, sos_size_t size) +{ + struct sos_umem_vmm_vr * initial_vr, * vr; + + if (hint_uaddr < SOS_PAGING_BASE_USER_ADDRESS) + hint_uaddr = SOS_PAGING_BASE_USER_ADDRESS; + + if (hint_uaddr > SOS_PAGING_UPPER_USER_ADDRESS - size + 1) + return (sos_uaddr_t)NULL; + + initial_vr = vr = find_enclosing_or_next_vr(as, hint_uaddr); + if (! vr) + /* Great, there is nothing after ! */ + return hint_uaddr; + + /* Scan the remaining VRs in the list */ + do + { + /* Is there enough space /before/ that VR ? */ + if (hint_uaddr + size <= vr->start) + /* Great ! */ + return hint_uaddr; + + /* Is there any VR /after/ this one, or do we have to wrap back + at the begining of the user space ? */ + if (vr->next_in_as->start >= hint_uaddr) + /* Ok, the next VR is really after us */ + hint_uaddr = vr->start + vr->size; + else + { + /* No: wrapping up */ + + /* Is there any space before the end of user space ? */ + if (hint_uaddr <= SOS_PAGING_UPPER_USER_ADDRESS - size) + return hint_uaddr; + + hint_uaddr = SOS_PAGING_BASE_USER_ADDRESS; + } + + /* Prepare to look after this VR */ + vr = vr->next_in_as; + } + while (vr != initial_vr); + + /* Reached the end of the list and did not find anything ?... Look + at the space after the last VR */ + + return (sos_uaddr_t)NULL; +} + + +static void +as_account_change_of_vr_protection(struct sos_umem_vmm_as * as, + sos_bool_t is_shared, + sos_size_t size, + sos_ui32_t prev_access_rights, + sos_ui32_t new_access_rights) +{ + if (prev_access_rights == new_access_rights) + return; + +#define _UPDATE_VMSTAT(field,is_increment) \ + ({ if (is_increment > 0) \ + as->field += size; \ + else \ + { SOS_ASSERT_FATAL(as->field >= size); as->field -= size; } }) +#define UPDATE_VMSTAT(field,is_increment) \ + ({ if (is_shared) _UPDATE_VMSTAT(vm_shrd.field, is_increment); \ + _UPDATE_VMSTAT(vm_total.field, is_increment); \ + SOS_ASSERT_FATAL(as->vm_total.field >= as->vm_shrd.field); }) + + if ( (new_access_rights & SOS_VM_MAP_PROT_WRITE) + && !(prev_access_rights & SOS_VM_MAP_PROT_WRITE)) + { + UPDATE_VMSTAT(rw, +1); + if (prev_access_rights & SOS_VM_MAP_PROT_READ) + UPDATE_VMSTAT(ro, -1); + } + else if ( !(new_access_rights & SOS_VM_MAP_PROT_WRITE) + && (prev_access_rights & SOS_VM_MAP_PROT_WRITE)) + { + if (new_access_rights & SOS_VM_MAP_PROT_READ) + UPDATE_VMSTAT(ro, +1); + UPDATE_VMSTAT(rw, -1); + } + else if (new_access_rights & SOS_VM_MAP_PROT_READ) + UPDATE_VMSTAT(ro, +1); + else if (!(new_access_rights & SOS_VM_MAP_PROT_READ)) + UPDATE_VMSTAT(ro, -1); + + if ( (new_access_rights & SOS_VM_MAP_PROT_EXEC) + && !(prev_access_rights & SOS_VM_MAP_PROT_EXEC)) + { + UPDATE_VMSTAT(code, +1); + } + else if ( !(new_access_rights & SOS_VM_MAP_PROT_EXEC) + && (prev_access_rights & SOS_VM_MAP_PROT_EXEC)) + { + UPDATE_VMSTAT(code, -1); + } + + if (new_access_rights && !prev_access_rights) + UPDATE_VMSTAT(overall, +1); + else if (!new_access_rights && prev_access_rights) + UPDATE_VMSTAT(overall, -1); + +} diff --git a/sos/umem_vmm.h b/sos/umem_vmm.h new file mode 100644 index 0000000..d9e5016 --- /dev/null +++ b/sos/umem_vmm.h @@ -0,0 +1,616 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_UMEM_VMM_H_ +#define _SOS_UMEM_VMM_H_ + +/** + * @file umem_vmm.h + * + * Management of the address space of a process in SOS. The so-called + * "address space" of a process consists in the description of the + * virtual addresses that are valid in the user space of a process (in + * SOS: addresses 1G-4G). The kernel-space of a process is managed by + * the "kmem" subsystem, and is kept identical accross all the + * processes in the system. + * + * The umem_vmm subsystem handles the following features: + * - demand-mapping of resourcs (files: mmap): mapping in physical RAM + * will be delayed as much as possible, until the process really + * need to access the mapped addresses + * - mprotect/mremap support + * - private and shared mappings + * - Copy-On-Write (COW) of the private mappings upon fork() to favour + * shared physical memory as much as possible + * - "heap" management (brk/sbrk) + * + * Swap is NOT supported (yet), which means that the following is NOT + * supported: + * - locked/reserved I/O pages (everything is locked in RAM) + * - "safe" demand-mapping of anonymous pages, ie conservative VMM + * allocation (alloc of anonymous pages on the swap) + * Other unsupported features: + * - dynamically-resizable regions (Linux's GROWUP/GROWDOWN vma): the + * user stack is expected to have a suitable virtual size from the + * beginning, or sos_umem_vmm_resize() must be used explicitely to + * resize it + * - no provision of "stack size" accounting, since there are + * multiple stacks (ie user threads) in a process: which stack to + * consider ??? + * + * The address space is divided into "virtual regions" (aka "VR") that + * describe a single mapping, aka a segment of contiguous pages in + * user-space virtual memory. Each such virtual region "maps" a + * "resource" and is characterised by: + * - its base address and length in user-space + * - the allowed accesses, aka "protection" (read-only or read/write) + * - the resource it maps in virtual memory + * + * A so-called resource is typically: + * - a file + * - a device + * - an area initially full of zeros (the VR mapping this are called + * "anonymous mappings") + * + * The implementation is very close to that of Linux and Kos. This is + * a "simple" implementation, not the most elegant one, such as those + * based on "shadow objects" hierarchies as found in BSD 4.4 and Mach, + * or that of Solaris (based on the "anon" lists). Actually, this + * implementation does not use "shadow-objects"/anon list when a COW + * page of a shared mapping is made anonymous. This won't hurt the + * implementation of the basic demand-mapping mechanism; on the + * contrary, it will make things simpler. But this will largely impact + * the implementation of the swap-in/swap-out strategies, as these + * would require a non trivial intrication of low-level and higher + * level algorithms. + */ + + +/** + * Definition of an "address space" in Kos. This is an opaque + * structure defined in umem_vmm.c. Its main role is to list virtual + * regions. It mainly consists in: + * - a reference to the process owning it + * - maximum allowed protection (ie can it be mapped read-only or + * read/write ?) + * - the list of VRs mapping resources + * - a mm_context that reflects the configuration of the MMU + * - the location of the heap for this process + * - statistics + */ +struct sos_umem_vmm_as; + + +/** + * Definition of a "virtual region". Linux would call them "vma" + * (Virtual Memory Area), and Solaris: "segments". It mainly consists + * in: + * - the start/end addresses of the mapping + * - a pointer to the resource that it maps + * - the type of mapping (shared/private) + * - the actual protection flags (@see SOS_VM_MAP_PROT_* flags in + * hwcore/paging.h) + * - a set of callbacks (@see sos_umem_vmm_vr_ops below) automatically + * called by the umem_vmm subsystem each time the VR is modified + */ +struct sos_umem_vmm_vr; + + +/** VR flag: region can be shared between a process and its + children */ +#define SOS_VR_MAP_SHARED (1 << 0) + + +#include +#include + + +/** + * The callbacks applicable on a virtual region. Automatically called + * by the umem_vmm subsystem. + * + * Calling sequences: + * - duplicate_as() (aka fork()): + * vr->ops->ref() + * add vr to lists + * - delete_as() (aka exit()): + * vr->ops->unmap() + * remove vr from lists + * vr->ops->unref() + * - mmap(): + * -> left + new + right VRs can fusion: + * remove right_vr from list + * right_vr->ops->unref() + * -> left + new VRs can fusion: + * nothing + * -> new + right VRs can fusion: + * nothing + * -> isolated: + * add new_vr to lists + * new_vr->map() + * new_vr->ops->ref() + * - munmap(): + * -> VR totally unmapped: + * vr->ops->unmap() + * remove vr from lists + * vr->ops->unref() + * -> VR unmapped in the middle (split into 2): + * add (new) right VR into the lists + * vr->unmap(middle_unmapped_area) + * right_vr->ops->ref() + * -> VR unmapped on its left: + * vr->ops->unmap(left_unmapped_area) + * -> VR unmapped on its right: + * vr->ops->unmap(right_unmapped_area) + * - chprot(): + * -> VR totally chprot: + * nothing + * -> VR chprot in the middle (split into 3): + * add (new) middle+right VRs into the lists + * middle_vr->ops->ref() + * right_vr->ops->ref() + * -> VR chprot on its left (split into 2): + * add (new) right VR into the lists + * right_vr->ops->ref() + * -> VR chprot on its right (split into 2): + * add (new) right VR into the lists + * right_vr->ops->ref() + * - resize(): + * -> if moving the VR: map/unmap + * -> otherwise: nothing + */ +struct sos_umem_vmm_vr_ops +{ + /** + * Called after the virtual region has been inserted + * inside its address space. + * @note Optional + */ + void (*ref)(struct sos_umem_vmm_vr * vr); + + /** + * Called when the virtual region is removed from its + * address space + * @note Optional + */ + void (*unref)(struct sos_umem_vmm_vr * vr); + + /** + * Called when part or all a VR is unmapped + * @note Optional + */ + void (*unmap)(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, sos_size_t size); + + /** + * Called by the page fault handler to map data at the given virtual + * address. In the Linux kernel, this callback is named "nopage". + * + * @note MANDATORY + */ + sos_ret_t (*page_in)(struct sos_umem_vmm_vr * vr, + sos_uaddr_t uaddr, + sos_bool_t write_access); + + + /** + * Called to synchronize the contents of the given page with its + * backing store. This method is responsible for resetting the dirty + * flag of the page (@see sos_paging_set_dirty). Appropriate locking + * MUST be handled internally by this method. + * + * @note Optional + */ + sos_ret_t (*sync_page)(struct sos_umem_vmm_vr * vr, + sos_uaddr_t page_uaddr, + sos_ui32_t flags); + /** Flags for msync */ +#define SOS_MSYNC_SYNC (1 << 0) +#define SOS_MSYNC_ASYNC (0 << 0) +}; + + +/** + * The definition of a mapped resource. Typically, a mapped resource + * is a file or a device: in both cases, only part of the resource is + * mapped by each VR, this part is given by the offset_in_resource + * field of the VR, and the size field of the VR. + */ +struct sos_umem_vmm_mapped_resource +{ + /** Represent the maximum authrized SOS_VR_PROT_* for the VRs mapping + it */ + sos_ui32_t allowed_access_rights; + + /** Some flags associated with the resource. Currently only + SOS_MAPPED_RESOURCE_ANONYMOUS is supported */ + sos_ui32_t flags; + + /** List of VRs mapping this resource */ + struct sos_umem_vmm_vr * list_vr; + + /** + * MANDATORY Callback function called when a new VR is created, + * which maps the resource. This callback is allowed to change the + * following fields of the VR: + * - sos_umem_vmm_set_ops_of_vr() + */ + sos_ret_t (*mmap)(struct sos_umem_vmm_vr *); + + /** + * Custom data that the user is free to define: the umem_vmm + * subsystem won't ever look at it or change it. + */ + void *custom_data; +}; + + +/** Inidicate that this resource is not backed by any physical + storage. This means that the "offset_in_resource" field of the + VRs will be computed by sos_umem_vmm_map() */ +#define SOS_MAPPED_RESOURCE_ANONYMOUS (1 << 0) + + +/** + * Physical address of THE page (full of 0s) used for anonymous + * mappings. Anybody can map it provided it is ALWAYS in read-only + * mode + */ +extern sos_paddr_t sos_zero_physpage; + +/** + * "ZERO" page address mapped in kernel space + */ +extern sos_vaddr_t sos_zero_kernelpage; + + +/** + * Setup the umem_vmm subsystem. + */ +sos_ret_t sos_umem_vmm_subsystem_setup(void); + + +/** + * Get the current effective address space. This may be the "normal" + * address space of the current thread, but not necessarilly: this + * might be the address space of just another process ! This may + * happen if a kernel thread of process P wants to access the address + * space of another process P2. This might also be NULL (no access to + * user space needed). + */ +struct sos_umem_vmm_as * sos_umem_vmm_get_current_as(void); + + +/** + * Change the current effective address space, eventually + * reconfiguring the MMU. This will increase the owning process's + * reference count of the given AS, and decrease that of the previous + * AS (when different). + * + * @param as may be NULL (no need to access user space) + */ +sos_ret_t sos_umem_vmm_set_current_as(struct sos_umem_vmm_as * as); + + +/** + * Create a new, empty, address space + * + * @param owner The process that will own the new address space + * + * @note no need to call + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +struct sos_umem_vmm_as * +sos_umem_vmm_create_empty_as(struct sos_process *owner); + + +/** + * Create a new address space, copy of the model_as address + * space. All the translations belonging to private mappings are + * marked 'read-only' to activate the "copy-on-write" semantics. + * + * @param model_as The address space to copy + * @param for_owner The process that will hold the new address space + * + * @note automatically calls + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +struct sos_umem_vmm_as * +sos_umem_vmm_duplicate_as(struct sos_umem_vmm_as * model_as, + struct sos_process *for_owner); + + +/** + * Called at process deletion time, to remove all mappings present in + * the address space. This function not only delete all the VR data + * structures, it also calls the unmap()/unref() callbacks of these + * VRs. However, the physical pages mapped inside the address space + * won't be unmapped at this stage: they will be unmapped all in one + * go when the undelying mm_context will become unused. + * + * @note no need to call + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_delete_as(struct sos_umem_vmm_as * as); + + +/* + * Accessor functions for the address space + */ + +/** Retrieve the pointer (NOT a new reference !) to the process owning + the given address space. */ +struct sos_process * +sos_umem_vmm_get_process(struct sos_umem_vmm_as * as); + +/** Retrieve the pointer (NOT a new reference !) to the MMU + configuration for the given address space */ +struct sos_mm_context * +sos_umem_vmm_get_mm_context(struct sos_umem_vmm_as * as); + +/** Retrieve a pointer to the VR that covers the given virtual address + in the given address space */ +struct sos_umem_vmm_vr * +sos_umem_vmm_get_vr_at_address(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr); + + +/* + * Accessor functions for the virtual regions + */ + +/** Retrieve the address space owning the given VR */ +struct sos_umem_vmm_as * +sos_umem_vmm_get_as_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the set of callbacks of the given VR */ +struct sos_umem_vmm_vr_ops * +sos_umem_vmm_get_ops_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the current protection of the given VR */ +sos_ui32_t sos_umem_vmm_get_prot_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the flags of the given VR. One will especially be + interested in the SOS_VR_MAP_SHARED bit */ +sos_ui32_t sos_umem_vmm_get_flags_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the resource mapped by the VR */ +struct sos_umem_vmm_mapped_resource * +sos_umem_vmm_get_mapped_resource_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the start user address for the given mapping */ +sos_uaddr_t sos_umem_vmm_get_start_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the size (in user space) of the given mapping */ +sos_size_t sos_umem_vmm_get_size_of_vr(struct sos_umem_vmm_vr * vr); + +/** Retrieve the offset in the resource of the mapping */ +sos_luoffset_t +sos_umem_vmm_get_offset_in_resource(struct sos_umem_vmm_vr * vr); + + +/* + * Restricted accessor functions. May only be called from inside the + * map() callback of a VR + */ + +/** + * Function that is not called directly by the umem_subsystem: It MUST + * always be called by the mmap() callback of the resource being + * mapped (@see sos_umem_vmm_mapped_resource::mmap()). The mmap() + * method is called at VR creation time, automatically by + * sos_umem_vmm_map(). + * + * @note The VR MUST NOT already have a set of operations (fatal error) + */ +sos_ret_t sos_umem_vmm_set_ops_of_vr(struct sos_umem_vmm_vr * vr, + struct sos_umem_vmm_vr_ops * ops); + + +/* + * mmap API + */ + + +/** sos_umem_vmm_map() flag: the address given as parameter to + sos_umem_vmm_map() is not only a hint, it is where the VR is + expected to be mapped */ +#define SOS_VR_MAP_FIXED (1 << 31) + + +/** + * Add a new VR in the given address space, that maps the given + * resource. Its semantics follows that of the UNIX mmap() call + * (including SOS_VR_MAP_FIXED). Real mapping in physical memory will + * be delayed as much as possible (demand paging) and the physical + * pages will be shared among processes as much as possible (COW). + * + * @param *uaddr must be page-aligned, and can be NULL. It stores the + * address of the mapping, when successful + * + * @param size The size of the mapping in user space + * + * @param access_rights The allowed accesses to the mapped resource + * (@see SOS_VM_MAP_PROT_* flags in hwcore/paging.h) + * + * @param flags mainly: is it shared mapping (SOS_VR_MAP_SHARED) or not ? + * + * @param resource MUST be NON NULL, and its mmap() method must also + * be NON NULL + * + * @param offset_in_resource where inside the resource does the + * mapping start + * + * @return SOS_OK on success (address of the mapping stored in uaddr) + * + * @note no need to call + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_map(struct sos_umem_vmm_as * as, + sos_uaddr_t *uaddr, sos_size_t size, + sos_ui32_t access_rights, + sos_ui32_t flags, + struct sos_umem_vmm_mapped_resource * resource, + sos_luoffset_t offset_in_resource); + + +/** + * Unmap the given address interval. This might imply the partial or + * complete unmapping of 0, 1 or more VRs. Same semantics as unix + * munmap() + * + * @note automatically calls + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_unmap(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size); + + +/** + * Flush the given pages to backing store. Call the sync_page method + * for each of the dirty pages. The MMU is expected to be configured + * for the given AS ! + * + * @note MAKE SURE YOU CALL + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_sync(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size, + sos_ui32_t flags); + + +/** + * Change the access rights of the given address interval. This might + * concern 0, 1 or more VRs, and result in the splitting in 1 or 2 VRs + * if they are partially concerned by the change in protection.. Same + * semantics as unix mprotect() + * + * @param new_access_rights @see SOS_VM_MAP_PROT_* flags in hwcore/paging.h + * + * @note MAKE SURE YOU CALL + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_chprot(struct sos_umem_vmm_as * as, + sos_uaddr_t uaddr, sos_size_t size, + sos_ui32_t new_access_rights); + + + +/** + * Flag for sos_umem_vmm_resize() to indicate that the VR being + * resized can be moved elsewhere if there is not enough room to + * resize it in-place + */ +#define SOS_VR_REMAP_MAYMOVE (1 << 30) + +/** + * Lookup the region covering the old_uaddr/old_size interval, and + * resize it to match the *new_uaddr/new_size requirements. This is a + * variant of Unix's mremap() that allow to resize the VR by its + * low-addresses (mremap only allows to resize a VR by its + * top-address). + * + * @param old_uaddr Low address of the interval covered by the VR to resize + * + * @param old_size Size of the interval covered by the VR to resize + * + * @param new_uaddr MUST BE page-aligned ! Initially: the new start + * address of the VR, allowing to change the low-address. Once the + * function returns: the actual start address of the VR (which might + * be different, due to SOS_VR_REMAP_MAYMOVE flag, when set) + * + * @param new_size The size requested for the VR. Might be + * smaller/larger than the original VR size + * + * @param flags Essentially: 0 or SOS_VR_REMAP_MAYMOVE + * + * @note MAKE SURE YOU CALL + * sos_thread_prepare_user_space_access()/sos_thread_end_user_space_access() + */ +sos_ret_t +sos_umem_vmm_resize(struct sos_umem_vmm_as * as, + sos_uaddr_t old_uaddr, sos_size_t old_size, + sos_uaddr_t /* in/out */*new_uaddr, sos_size_t new_size, + sos_ui32_t flags); + + +/* + * Heap management API (ie libc's malloc support) + */ + +/** + * Change the top address of the heap. + * + * @param new_top_uaddr When NULL don't change anything. Otherwise: + * change the top address of the heap + * + * @return The top address of the heap after having been updated (if + * ever) + */ +sos_uaddr_t +sos_umem_vmm_brk(struct sos_umem_vmm_as * as, + sos_uaddr_t new_top_uaddr); + + +/* + * Reserved functions + */ + +/** + * Called by the main page fault handler when a physical page is not + * mapped for the given address of the current address space. This + * function is called only if: + * - The access (read / write) is allowed on this VR + * - no physical page is mapped yet + * This function first calls the sos_paging_try_resolve_COW() to + * resolve the COW if a COW access pattern is detected, and, if + * unsuccessful, the sos_umem_vmm_vr_ops::page_in() method of the VR. + * + * @param uaddr The address that was accessed, causing the fault. + * + * @param write_access Was it write access ? + * + * @param user_access Was it a user access ? Or a kernel access (by + * uaccess.h functions) ? + * + * @return SOS_OK when the fault could be solved, ie a page could be + * mapped for the given address. -SOS_EFAULT otherwise, meaning the + * faulting thread should be terminated or signalled (SIGSEGV) + * + * @note: The current mm_context MUST be that of the current thread + * (which caused the exception) ! + */ +sos_ret_t sos_umem_vmm_try_resolve_page_fault(sos_uaddr_t uaddr, + sos_bool_t write_access, + sos_bool_t user_access); + + + +/** + * Initialize the initial heap once the program code/data is mapped + * Called by the ELF32 program loader. + */ +sos_ret_t +sos_umem_vmm_init_heap(struct sos_umem_vmm_as * as, + sos_uaddr_t heap_start); + +#endif /* _SOS_UMEM_VMM_H_ */ diff --git a/support/build_image.sh b/support/build_image.sh new file mode 100755 index 0000000..85cf1bb --- /dev/null +++ b/support/build_image.sh @@ -0,0 +1,220 @@ +#!/bin/sh +# Copyright (C) 2003, David Decotigny + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +# USA. + +# 1) What does it do ? +# +# 1) Check where Grub is installed (lookup_grub) +# 2) Assign some local variables using the shell script arguments. +# a) Argument 1 : the destination (either a file or a drive, like a:) +# b) Argument 2 : the loader (i.e kernel) +# c) Argument 3 : options passed to the loader +# d) Argument 4 : the modules (that can be loaded optionally by Grub) +# 3) Test whether destination is a drive or a file +# 4) Create the directory structure inside the drive +# 5) Copy the loader in the drive +# 6) Generate the 'menu.txt' file used by Grub to generate the boot menu +# 7) Copy all modules +# 8) Copy the menu.txt file +# +# 2) Why is it so complex ? +# Because it must support various Grub/mtools installations and versions +# +# In fact, this shell script is used in the KOS (kos.enix.org) +# project. This operating system consists in a loader and many many +# modules that are linked together at boot time. It is much more +# complex that a simple monolithic kernel. +# +# For your simple monolithic kernel, you only need to give argument 1 +# and 2. + +print_usage () { + echo "Usage: $0 [X:|image] path/to/loader option path/to/modules..." + echo " where X: is a valid floppy drive on your computer" + echo " where image is any file name" + exit 1 +} + +grub_dirs_common="/usr/local/share/grub/i386-freebsd /usr/local/share/grub/i386-pc /usr/share/grub/i386-pc /usr/lib/grub/i386-pc /usr/local/grub /usr/share/grub/i386-redhat /usr/local/src/grub-0.5.94 $HOME/share/grub/i386-pc/" +sbin_grub_path="/usr/local/sbin /usr/sbin /sbin $HOME/sbin" + +PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin +export PATH + +MTOOLSRC=mtoolsrc +export MTOOLSRC + +# Redefined variables +FLOPPY_DRIVE=A: +IMG_FNAME=fd.img + +## +## Format disk image +## +init_image () { + echo "Initialize disk image $IMG_FILE..." + if [ ! -f $IMG_FNAME ] ; then + dd if=/dev/zero of=$IMG_FNAME bs=18k count=80 1>/dev/null 2>&1 + fi + + rm -f $MTOOLSRC + echo "drive u: file=\"$IMG_FNAME\" 1.44M filter" > $MTOOLSRC + + if mformat U: ; then : ; else + rm -f $MTOOLSRC + echo "drive u: file=\"$IMG_FNAME\" 1.44M" > $MTOOLSRC + if mformat U: ; then : ; else + rm -f $MTOOLSRC + echo "drive u: file=\"$IMG_FNAME\"" > $MTOOLSRC + mformat U: + fi + fi +} + + +## +## Format (real) floppy disk +## +init_floppy () { + echo "Formatting floppy..." + mformat $FLOPPY_DRIVE || exit 1 +} + + +lookup_grub () { + # Look for a correct GRUBDIR + for d in $grub_dirs_common ; do + if [ -d $d ] ; then + GRUBDIR=$d + break + fi + done + + # Try to guess with whereis (Credits to Karim Dridi) + if [ ! -d "$GRUBDIR" ] ; then + GRUBDIR=`whereis grub | awk '{ print "find "$3" -name stage2" }' | sh | xargs dirname 2>/dev/null` + fi + + # Try to guess with locate + if [ ! -d "$GRUBDIR" ] ; then + GRUBDIR=`locate stage2 | head -1 | xargs dirname 2>/dev/null` + fi + + # Look for a correct sbin/grub + for d in $sbin_grub_path ; do + if [ -x $d/grub ] ; then + SBIN_GRUB=$d/grub + break + fi + done + + if [ -d "$GRUBDIR" -a -x "$SBIN_GRUB" ] ; then + echo "Found correct grub installation in $GRUBDIR" + echo "Found correct /sbin/grub at $SBIN_GRUB" + else + echo "Couldn't find a correct grub installation." + exit 1 + fi +} + +## +## setup_disk [drive] +## => setup disk directory structure / copy files +## +setup_disk () { + echo "Setup destination disk..." + + mmd $1/boot + mmd $1/boot/grub + + if [ -d $GRUBDIR/stage1 ] ; then + mcopy $GRUBDIR/stage1/stage1 $1/boot/grub/ + mcopy $GRUBDIR/stage2/stage2 $1/boot/grub/ + else + mcopy $GRUBDIR/stage1 $1/boot/grub/ + mcopy $GRUBDIR/stage2 $1/boot/grub/ + fi + mmd $1/system + mmd $1/modules + + $SBIN_GRUB --batch --no-floppy </dev/null 2>/dev/null || exit 1 +device (fd0) $IMG_FNAME +install (fd0)/boot/grub/stage1 (fd0) (fd0)/boot/grub/stage2 p (fd0)/boot/grub/menu.txt +quit +EOT +} + + + +################################################# +## Real start +## +#[ "$#" -lt 3 ] && print_usage + +lookup_grub + +dest="$1" ; shift +loader_fname="$1" ; shift +options="$1" ; shift +modules="$*" + +# Init destination disk +case x$dest in + x*:) + drive=$dest + IMG_FNAME=$dest + FLOPPY_DRIVE=$dest + init_floppy + ;; + x*) + drive=U: + IMG_FNAME=$dest + init_image + ;; +esac + +# Create directory structure +setup_disk $drive + +# Copy the loader +mcopy -bo $loader_fname $drive/system/`basename $loader_fname` + +# Generate the menu.txt file +rm -f menu.txt +cat < menu.txt +timeout 0 +default 0 +title Simple OS +root (fd0) +kernel /system/`basename $loader_fname` $options +EOF + +# Copy the modules +for f in $modules ; do + if [ ! -f $f ] ; then + echo "ERROR: module $f not correctly compiled in." + exit 1 + fi + if ! mcopy -bo $f $drive/modules/`basename $f` ; then + echo "ERROR: module $f could not be transferred to floppy." + exit 1 + fi + echo module /modules/`basename $f` >> menu.txt +done + +# Transfers the menu.txt file to floppy +mcopy -bo menu.txt $drive/boot/grub/ diff --git a/support/sos.lds b/support/sos.lds new file mode 100644 index 0000000..b64fb19 --- /dev/null +++ b/support/sos.lds @@ -0,0 +1,113 @@ +/* Copyright (C) 2003, Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +/* We generate binary in the ELF format */ +OUTPUT_FORMAT("elf32-i386","elf32-i386","elf32-i386"); + +/* The entry point is _start (defined in boot.S) */ +ENTRY(_start) + +/* The architecture is i386 */ +OUTPUT_ARCH("i386") + +SECTIONS +{ + /* our kernel is loaded at 0x200000 */ + . = 0x200000; + __b_load = .; + + /* the multiboot header MUST come early enough in the output + object file */ + .multiboot : + { + /* The multiboot section (containing the multiboot header) + goes here */ + *(.multiboot); + + /* + * With the following line, we force this section to be + * allocated in the output file as soon as possible, no matter + * when the file containing the multiboot header (multiboot.S) + * is compiled. This is to conform to the multiboot spec, which + * says "The Multiboot header must be contained completely + * within the first 8192 bytes of the OS image, and must be + * longword (32-bit) aligned." + */ + LONG(0); + } + + /* Defines a symbol '__b_kernel to mark the start of the kernel + code/data */ + . = ALIGN(4096); + __b_kernel = .; + + /* Beginning of the text section */ + .text ALIGN(4096) : + { + /* This section includes the code */ + *(.text*) + /* Defines the 'etext' and '_etext' at the end */ + PROVIDE(etext = .); + PROVIDE(_etext = .); + } + + /* Beginning of the data section */ + .data . : + { *(.data*) + PROVIDE(edata = .); + PROVIDE(_edata = .); + } + + /* Beginning of the read-only data section */ + .rodata . : + { *(.rodata*) + *(.eh_frame*) + + /* For articles 7.5 and later, it is better if the program + "files" are located on a 4kB boundary: this allows + binfmt_elf32 to share program pages between kernel and + user, alleviating the need to allocate new pages to copy the + user code */ + . = ALIGN(4096); + *(.userprogs) + + PROVIDE(erodata = .); + PROVIDE(_erodata = .); + } + + /* We take note of the end of the data to load */ + __e_load = .; + + /* Beginning of the BSS section (global uninitialized data) */ + .bss SIZEOF(.rodata) + ADDR(.rodata) : + { *(.bss) + *(COMMON) + + /* We put the stack of the bootstrap thread on a page + boundary, because it can be un-allocated later */ + . = ALIGN(4096); + *(.init_stack) + + PROVIDE(ebss = .); + PROVIDE(_ebss = .); + } + + /* We take note of the end of the kernel: this is where the GPFM + will begin */ + __e_kernel = .; +} diff --git a/userland/Makefile b/userland/Makefile new file mode 100644 index 0000000..d75f87d --- /dev/null +++ b/userland/Makefile @@ -0,0 +1,100 @@ +## Copyright (C) 2005 David Decotigny +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation; either version 2 +## of the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +## USA. + +CC=gcc +AR=ar +CP=cp +STRIP=strip +OBJCOPY=objcopy +CFLAGS = -Wall -nostdinc -ffreestanding -I. -I.. -O +LIBGCC = $(shell $(CC) -print-libgcc-file-name) # To benefit from FP/64bits artihm. +LDFLAGS = -Wl,--warn-common -nostdlib -Wl,-Tldscript.lds + +# Main target +all: userprogs.kimg + +-include .mkvars + +PROGS := init myprog1 myprog2 myprog3 myprog4 myprog5 myprog6 \ + envtest myprog7 myprog8 myprog9 myprog10 myprog11 myprog12 \ + myprog13 myprog14 banner fstest fstestfat chartest blktest shell + +# Build dependencies of the programs +init: fstest_utils.o +fstest: fstest_utils.o +fstestfat: fstest_utils.o +chartest: fstest_utils.o +blktest: fstest_utils.o +shell: fstest_utils.o +$(PROGS) : % : %.o crt.o crt_asm.o libc.a + +PWD := $(shell pwd | sed 's/"/\\\"/g;s/\$$/\\\$$/g') + +# Programs generation +$(PROGS): + $(CC) -static $(LDFLAGS) -o $@ $^ $(LIBGCC) + +# Generation of the libC +libc.a: libc.o string.o stdarg.o debug.o + +# Create a program image to be integrated into the Kernel +userprogs.kimg: $(PROGS) + @echo "# Generating ELF images for inclusion into the kernel image: $@" + @echo "SECTIONS { .userprogs . : { " > .userprogs.lds + @i=0 ; \ + for f in $^ ; do \ + i=`expr $$i + 1` ; \ + echo "extern char _begin_userprog$$i, _end_userprog$$i;" \ + > .userprog$$i.c ; \ + echo "char *_userprog"$$i"_entry[]" >> .userprog$$i.c ; \ + echo " __attribute__((section(\".userprogs_table\")))" \ + >> .userprog$$i.c ; \ + echo " = { \"$$f\", &_begin_userprog$$i, &_end_userprog$$i };" \ + >> .userprog$$i.c ; \ + $(CC) $(CFLAGS) -c .userprog$$i.c -o .userprog$$i.o ; \ + $(CP) $$f $$f.strip && $(STRIP) -sx $$f.strip ; \ + $(OBJCOPY) --add-section .userprog$$i=$$f.strip .userprog$$i.o \ + .userprog$$i.kimg ; \ + echo " . = ALIGN(4096);" >> .userprogs.lds ; \ + echo " _begin_userprog$$i = .;" >> .userprogs.lds ; \ + echo " .userprog$$i.kimg(.userprog$$i);" >> .userprogs.lds ; \ + echo " _end_userprog$$i = .;" >> .userprogs.lds ; \ + echo " .userprog$$i.kimg(.rodata); .userprog$$i.kimg(.data);" \ + >> .userprogs.lds ; \ + done + @echo " _userprogs_table = .; *(.userprogs_table) ; LONG(0);" \ + >> .userprogs.lds + @echo "} /DISCARD/ : { *(.text) *(.data) *(.bss) } }" \ + >> .userprogs.lds + @$(LD) -r -o $@ -T.userprogs.lds + +# Create libraries from object files +%.a: + $(AR) rcv $@ $^ + +# Create objects from C source code +%.o: %.c + $(CC) "-I$(PWD)" -c "$<" $(CFLAGS) -o "$@" + +# Create objects from assembler (.S) source code +%.o: %.S + $(CC) "-I$(PWD)" -c "$<" $(CFLAGS) -DASM_SOURCE=1 -o "$@" + +# Clean directory +clean: + $(RM) *.o *.a *~ $(PROGS) *.kimg *.strip + $(RM) .userprog* diff --git a/userland/banner.c b/userland/banner.c new file mode 100644 index 0000000..e9b6695 --- /dev/null +++ b/userland/banner.c @@ -0,0 +1,150 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +/** + * @file banner.c + * + * Basic tests demonstrating how to create multithreaded processes + */ + +static char *str1 = +" _ _ _ _ ___ _ _ _ _ _ " +" | | | | ___ | | | | ___ |_ _| __ _ _ __ ___ | |_ | |__ _ __ ___ __ _ __| | / | | |" +" | |_| | / _ \\ | | | | / _ \\ | | / _` | | '_ ` _ \\ | __| | '_ \\ | '__| / _ \\ / _` | / _` | | | | |" +" | _ | | __/ | | | | | (_) | _ | | | (_| | | | | | | | | |_ | | | | | | | __/ | (_| | | (_| | | | |_|" +" |_| |_| \\___| |_| |_| \\___/ ( ) |___| \\__,_| |_| |_| |_| \\__| |_| |_| |_| \\___| \\__,_| \\__,_| |_| (_)" +" |/ "; + +static char *str2 = +" _ _ _ _ ___ _ _ _ ____ _ " +" | | | | ___ | | | | ___ |_ _| __ _ _ __ ___ | |_ | |__ _ __ ___ __ _ __| | |___ \\ | |" +" | |_| | / _ \\ | | | | / _ \\ | | / _` | | '_ ` _ \\ | __| | '_ \\ | '__| / _ \\ / _` | / _` | __) | | |" +" | _ | | __/ | | | | | (_) | _ | | | (_| | | | | | | | | |_ | | | | | | | __/ | (_| | | (_| | / __/ |_|" +" |_| |_| \\___| |_| |_| \\___/ ( ) |___| \\__,_| |_| |_| |_| \\__| |_| |_| |_| \\___| \\__,_| \\__,_| |_____| (_)" +" |/ "; + +/** + * Structure of a character in the x86 video memory mapping (text mode) + */ +static struct x86_videomem_char +{ + unsigned char character; + unsigned char attribute; +} * video; + + +static void print(int line, int col, char c, unsigned char attr) +{ + video[line*80 + col].character = c; + video[line*80 + col].attribute = attr; +} + + +/** + * Helper function that scrolls the given string on the screen + */ +static void print_banner(int top_line, const char * str, int nlines, + unsigned char attr, unsigned pause_ms, + int direction) +{ + int nbcols = strnlen(str, 16384) / nlines; + int base_char = 0; + + while (1) + { + int col; + + for (col = 0 ; col < 80 ; col ++) + { + int col_in_str = (col + base_char) % (nbcols + 20); + int line; + + for (line = 0 ; line < nlines ; line ++) + if (col_in_str < nbcols) + print(top_line + line, col, str[line*nbcols + col_in_str], attr); + else + print(top_line + line, col, ' ', attr); + } + + _sos_nanosleep(0, pause_ms * 1000000); + + if (direction > 0) + base_char ++; + else + base_char --; + + if (base_char < 0) + base_char = nbcols + 20 - 1; + } +} + + +struct thread_param +{ + int top_line; + const char * str; + int nlines; + unsigned char attr; + unsigned int pause_ms; + int direction; +}; + + +static void banner_thread(const struct thread_param *p) +{ + print_banner(p->top_line, p->str, p->nlines, p->attr, + p->pause_ms, p->direction); +} + +static struct thread_param p1, p2; +int main(void) +{ + int fd; + + /* Map the x86 text-mode framebuffer in user space */ + fd = open("/dev/mem", O_RDWR); + video = mmap(0, 4096, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 0xb8000); + close(fd); + + p1.top_line = 3; + p1.str = str1; + p1.nlines = 6; + p1.attr = 14 | (7 << 4); + p1.pause_ms = 10; + p1.direction = 1; + _sos_new_thread((sos_thread_func_t*)banner_thread, (void*) & p1, 8192); + + p2.top_line = 10; + p2.str = str2; + p2.nlines = 6; + p2.attr = (4 << 4) | (7 << 4); + p2.pause_ms = 20; + p2.direction = -1; + _sos_new_thread((sos_thread_func_t*)banner_thread, (void*) & p2, 8192); + + return 0; +} diff --git a/userland/blktest.c b/userland/blktest.c new file mode 100644 index 0000000..3253d51 --- /dev/null +++ b/userland/blktest.c @@ -0,0 +1,208 @@ +/* Copyright (C) 2005,2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include "fstest_utils.h" + +/** + * @file blktest.c + * + * Block devices tests + */ +static int test_device(char const* path, char magic) +{ + char wrbuff[32767]; + char rdbuff[32767]; + int fd, fdcheck, i; + char * mem; + + fd = open(path, O_RDWR); + if (fd < 0) + { + bochs_printf("Cannot open %s: error %d\n", path, fd); + return -1; + } + + bochs_printf("Testing %s...\n", path); + + /* Basic seek tests */ + TEST_EXPECT_CONDITION(lseek(fd, 2*512, SEEK_SET), RETVAL == 2*512); + TEST_EXPECT_CONDITION(write(fd, wrbuff, 1), RETVAL == 1); + TEST_EXPECT_CONDITION(lseek(fd, 4*512, SEEK_SET), RETVAL == 4*512); + TEST_EXPECT_CONDITION(read(fd, rdbuff, 1), RETVAL == 1); + + /* Prepare to write a large chunk */ + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + wrbuff[i] = magic + i; + memset(rdbuff, 0xaa, sizeof(wrbuff)); + TEST_EXPECT_CONDITION(lseek(fd, 3*512 + 43, SEEK_SET), RETVAL == 3*512 + 43); + TEST_EXPECT_CONDITION(write(fd, wrbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(write(fd, rdbuff, sizeof(rdbuff)), + RETVAL == sizeof(rdbuff)); + + memset(rdbuff, 0xbb, sizeof(rdbuff)); + TEST_EXPECT_CONDITION(lseek(fd, 3*512 + 47, SEEK_SET), RETVAL == 3*512 + 47); + TEST_EXPECT_CONDITION(read(fd, rdbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(memcmp(rdbuff, wrbuff+4, sizeof(wrbuff)-4), + RETVAL == 0); + + fdcheck = open(path, O_RDONLY); + +#define MAPOFFS (50*4096) + + /* Test mmap private mappings */ + TEST_EXPECT_CONDITION(mem = mmap(NULL, sizeof(wrbuff), PROT_READ, + MAP_PRIVATE, fd, MAPOFFS + 3), + (void*)RETVAL == NULL); + TEST_EXPECT_CONDITION(mem = mmap(NULL, sizeof(wrbuff), + PROT_READ | PROT_WRITE, MAP_PRIVATE, + fd, MAPOFFS), + (void*)RETVAL != NULL); + + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + wrbuff[i] = magic + i; + TEST_EXPECT_CONDITION(1, 1); /* Print a debug line */ + + memset(rdbuff, 0xaa, sizeof(wrbuff)); + TEST_EXPECT_CONDITION(lseek(fd, MAPOFFS, SEEK_SET), RETVAL == MAPOFFS); + TEST_EXPECT_CONDITION(write(fd, wrbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(memcmp(wrbuff, mem, sizeof(wrbuff)), + RETVAL == 0); + + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + mem[i] = wrbuff[i]*i; + TEST_EXPECT_CONDITION(1, 1); /* Print a debug line */ + + memset(rdbuff, 0xaa, sizeof(rdbuff)); + TEST_EXPECT_CONDITION(lseek(fdcheck, MAPOFFS, SEEK_SET), RETVAL == MAPOFFS); + TEST_EXPECT_CONDITION(read(fdcheck, rdbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(memcmp(wrbuff, rdbuff, sizeof(wrbuff)), RETVAL == 0); + + TEST_EXPECT_CONDITION(msync(mem, sizeof(wrbuff), MS_SYNC), RETVAL == 0); + TEST_EXPECT_CONDITION(munmap(mem, sizeof(wrbuff)), RETVAL == 0); + + /* Test mmap shared mappings */ + TEST_EXPECT_CONDITION(mem = mmap(mem, sizeof(wrbuff), PROT_READ, + MAP_SHARED, fd, MAPOFFS + 3), + (void*)RETVAL == NULL); + TEST_EXPECT_CONDITION(mem = mmap(mem, sizeof(wrbuff), + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, MAPOFFS), + (void*)RETVAL != NULL); + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + wrbuff[i] = magic + i; + memset(rdbuff, 0xaa, sizeof(rdbuff)); + TEST_EXPECT_CONDITION(lseek(fd, MAPOFFS, SEEK_SET), RETVAL == MAPOFFS); + TEST_EXPECT_CONDITION(write(fd, wrbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(memcmp(wrbuff, mem, sizeof(wrbuff)), RETVAL == 0); + + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + mem[i] = wrbuff[i]*i; + memset(rdbuff, 0xaa, sizeof(rdbuff)); + TEST_EXPECT_CONDITION(lseek(fdcheck, MAPOFFS, SEEK_SET), RETVAL == MAPOFFS); + TEST_EXPECT_CONDITION(read(fdcheck, rdbuff, sizeof(wrbuff)), + RETVAL == sizeof(wrbuff)); + TEST_EXPECT_CONDITION(memcmp(wrbuff, rdbuff, sizeof(wrbuff)), RETVAL != 0); + TEST_EXPECT_CONDITION(memcmp(mem, rdbuff, sizeof(wrbuff)), RETVAL == 0); + + TEST_EXPECT_CONDITION(msync(mem, sizeof(wrbuff), MS_SYNC), RETVAL == 0); + TEST_EXPECT_CONDITION(munmap(mem, sizeof(wrbuff)), RETVAL == 0); + +#define STARTOFFS (3*512 + 43 + sizeof(wrbuff) + sizeof(rdbuff)) + + TEST_EXPECT_CONDITION(lseek(fd, STARTOFFS, SEEK_SET), RETVAL == STARTOFFS); + TEST_EXPECT_CONDITION(lseek(fdcheck, STARTOFFS, SEEK_SET), + RETVAL == STARTOFFS); + int wrlen = 0; + while (1) + { + int sz; + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + wrbuff[i] = magic*magic*i + wrlen*wrlen + i; + + TEST_EXPECT_CONDITION(sz = write(fd, wrbuff, sizeof(wrbuff)), + (RETVAL >= 0) && (RETVAL <= sizeof(wrbuff))); + if (sz <= 0) + break; + wrlen += sz; + + memset(rdbuff, 0xcc, sizeof(wrbuff)); + TEST_EXPECT_CONDITION(read(fdcheck, rdbuff, sizeof(wrbuff)), RETVAL == sz); + TEST_EXPECT_CONDITION(memcmp(rdbuff, wrbuff, sz), RETVAL == 0); + } + + /* Check the data was correctly written */ + TEST_EXPECT_CONDITION(lseek(fd, STARTOFFS, SEEK_SET), RETVAL == STARTOFFS); + int rdlen = 0; + while (1) + { + int sz; + for (i = 0 ; i < sizeof(wrbuff) ; i ++) + wrbuff[i] = magic*magic*i + rdlen*rdlen + i; + + memset(rdbuff, 0xdd, sizeof(wrbuff)); + TEST_EXPECT_CONDITION(sz = read(fd, rdbuff, sizeof(wrbuff)), + (RETVAL >= 0) && (RETVAL <= sizeof(wrbuff))); + if (sz <= 0) + break; + rdlen += sz; + TEST_EXPECT_CONDITION(memcmp(rdbuff, wrbuff, sz), RETVAL == 0); + } + TEST_EXPECT_CONDITION(rdlen, wrlen == rdlen); + + bochs_printf("Synching %s...\n", path); + ioctl(fd, SOS_IOCTL_BLOCKDEV_SYNC, 0); + + close(fd); + bochs_printf("End of test for %s.\n", path); + + return 0; +} + + +int main(void) +{ + int i; + bochs_printf("Hi from blktest\n"); + + /* Test partitions access */ + for (i = 1 ; i <= 15 ; i ++) + { + char path[64]; + snprintf(path, sizeof(path), "/dev/hda%d", i); + if (fork() == 0) + { + test_device(path, i); + return 0; + } + } + + bochs_printf("Bye from blktest\n"); + return 0; +} diff --git a/userland/chartest.c b/userland/chartest.c new file mode 100644 index 0000000..66ce05e --- /dev/null +++ b/userland/chartest.c @@ -0,0 +1,248 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "fstest_utils.h" + +/** + * @file chartest.c + * + * Character devices tests + */ + +int main(void) +{ + int fd, len; + unsigned int sum, mem_size; + char buff[256]; + char *uaddr; + + bochs_printf("Hi from chartest\n"); + bochs_printf("WARNING: This test will eventually write 0 on kernel code !\n"); + bochs_printf("This WILL crash the kernel (as expected...) !\n"); + printf("WARNING: This test will eventually write 0 on kernel code !\n"); + printf("This WILL crash the kernel (as expected...) !\n"); + + /* Make things more complicated */ + fork(); + fork(); + + /* + * /dev/zero + */ + + TEST_EXPECT_CONDITION(fd = open("/dev/zero", O_RDWR), RETVAL == 3); + + /* Make things more complicated: spawn and stay in child */ + if (fork() > 0) + return 0; + + strzcpy(buff, "Garbage", sizeof(buff)); + TEST_EXPECT_CONDITION(len = read(fd, buff, sizeof(buff)), + RETVAL == sizeof(buff)); + + /* Make sure it is full with 0 */ + for (len = 0 ; len < sizeof(buff) ; len ++) + if (buff[len]) + break; + TEST_EXPECT_CONDITION(len, RETVAL == sizeof(buff)); + + TEST_EXPECT_CONDITION(len = write(fd, 0, 123456789), RETVAL == 123456789); + TEST_EXPECT_CONDITION(len = lseek(fd, 1234, SEEK_SET), RETVAL == 1234); + + /* Map it once */ + TEST_EXPECT_CONDITION(uaddr = mmap(NULL, 8192, + PROT_READ | PROT_WRITE, 0, + fd, 4096), RETVAL != (int)NULL); + + /* Make sure it is full with 0 */ + for (len = 0 ; len < 8192 ; len ++) + if (uaddr[len]) + break; + TEST_EXPECT_CONDITION(len, RETVAL == 8192); + + strzcpy(uaddr, "Hello /dev/zero", 8192); + TEST_EXPECT_CONDITION(strcmp(uaddr, "Hello /dev/zero"), RETVAL == 0); + + /* Map it again */ + TEST_EXPECT_CONDITION(uaddr = mmap(NULL, 8192, + PROT_READ | PROT_WRITE, 0, + fd, 4096), RETVAL != (int)NULL); + /* Make sure it is full with 0 */ + for (len = 0 ; len < 8192 ; len ++) + if (uaddr[len]) + break; + TEST_EXPECT_CONDITION(len, RETVAL == 8192); + + strzcpy(uaddr, "Hello /dev/zero", 8192); + TEST_EXPECT_CONDITION(strcmp(uaddr, "Hello /dev/zero"), RETVAL == 0); + + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + + /* + * /dev/null + */ + + TEST_EXPECT_CONDITION(fd = open("/dev/null", O_RDWR), RETVAL == 3); + + /* Make things more complicated: spawn and stay in child */ + if (fork() > 0) + return 0; + + strzcpy(buff, "Garbage", sizeof(buff)); + TEST_EXPECT_CONDITION(len = read(fd, buff, sizeof(buff)), + RETVAL == 0); + + /* Make sure buffer did not change */ + TEST_EXPECT_CONDITION(strcmp("Garbage", buff), RETVAL == 0); + + TEST_EXPECT_CONDITION(len = write(fd, 0, 123456789), RETVAL == 123456789); + TEST_EXPECT_CONDITION(len = lseek(fd, 1234, SEEK_SET), RETVAL == 1234); + + /* Map it once */ + TEST_EXPECT_CONDITION(uaddr = mmap(NULL, 8192, + PROT_READ | PROT_WRITE, 0, + fd, 4096), RETVAL != (int)NULL); + /* Make sure it is full with 0 */ + for (len = 0 ; len < 8192 ; len ++) + if (uaddr[len]) + break; + TEST_EXPECT_CONDITION(len, RETVAL == 8192); + + strzcpy(uaddr, "Hello /dev/null", 8192); + TEST_EXPECT_CONDITION(strcmp(uaddr, "Hello /dev/null"), RETVAL == 0); + + /* Map it again */ + TEST_EXPECT_CONDITION(uaddr = mmap(NULL, 8192, + PROT_READ | PROT_WRITE, 0, + fd, 4096), RETVAL != (int)NULL); + /* Make sure it is full with 0 */ + for (len = 0 ; len < 8192 ; len ++) + if (uaddr[len]) + break; + TEST_EXPECT_CONDITION(len, RETVAL == 8192); + + strzcpy(uaddr, "Hello /dev/null", 8192); + TEST_EXPECT_CONDITION(strcmp(uaddr, "Hello /dev/null"), RETVAL == 0); + + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* + * Invalid device instance of zero.c + */ + TEST_EXPECT_CONDITION(mknod("/dev/invalid_zero", S_IRUSR | S_IWUSR, + S_IFCHR, 1, 42), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(fd = open("/dev/invalid_zero", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("/dev/invalid_zero"), RETVAL == 0); + + + /* + * Invalid device class / device instance + */ + TEST_EXPECT_CONDITION(mknod("/dev/invalid_devclass", S_IRUSR | S_IWUSR, + S_IFCHR, 54654, 476576), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(fd = open("/dev/invalid_devclass", + O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("/dev/invalid_devclass"), RETVAL == 0); + + + /* + * Make a checksum of all physical memory (/dev/mem) + */ + TEST_EXPECT_CONDITION(fd = open("/dev/mem", O_RDONLY), RETVAL == 3); + + /* Make things more complicated: spawn and stay in child */ + if (fork() > 0) + return 0; + + for (mem_size = 0, sum = 0 ; + len = read(fd, buff, sizeof(buff)), len > 0 ; + mem_size += len) + { + char *c; + if (((mem_size / sizeof(buff)) % 4000) == 0) + printf("Read %d MB of physical memory\n", mem_size >> 20); + + for (c = buff ; c - buff < len ; c++) + sum += *c; + } + TEST_EXPECT_CONDITION(mem_size, RETVAL > 0); + printf("Checkum of RAM (%d MB) is %x\n", mem_size >> 20, sum); + TEST_EXPECT_CONDITION(len = lseek(fd, 0, SEEK_SET), len == 0); + TEST_EXPECT_CONDITION(len = lseek(fd, 0, SEEK_END), len == mem_size); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* + * Make a checksum of main kernel memory (/dev/kmem) + */ + TEST_EXPECT_CONDITION(fd = open("/dev/kmem", O_RDONLY), RETVAL == 3); + + /* Make things more complicated: spawn and stay in child */ + if (fork() > 0) + return 0; + + /* Seek to beginning of main .text section */ + TEST_EXPECT_CONDITION(lseek(fd, 0x201000, SEEK_SET), RETVAL == 0x201000); + for (mem_size = 0, sum = 0 ; + len = read(fd, buff, sizeof(buff)), len > 0 ; + mem_size += len) + { + char *c; + if (((mem_size / sizeof(buff)) % 400) == 0) + printf("Read %d kB of kernel memory\n", mem_size >> 10); + + for (c = buff ; c - buff < len ; c++) + sum += *c; + } + TEST_EXPECT_CONDITION(mem_size, RETVAL > 0); + printf("Checkum of main kernel area (%d kB) is %x\n", + mem_size >> 10, sum); + TEST_EXPECT_CONDITION(len = lseek(fd, 0, SEEK_SET), len == 0); + TEST_EXPECT_CONDITION(len = lseek(fd, 0, SEEK_END), len == 0x40000000); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + printf("Bye from chartest\n"); + ls("/", 1, 1); + + /* Crash the whole kernel */ + bochs_printf("NOW Crashing the whole thing !...\n"); + + { + int i; + for (i = 5 ; i >= 0 ; i --) + { + printf("Crashing the whole thing in %ds !... \r", i); + sleep(1); + } + } + printf("\nGO !\n"); + TEST_EXPECT_CONDITION(fd = open("/dev/mem", O_WRONLY), RETVAL == 3); + memset(buff, 0xcc, sizeof(buff)); + while (write(fd, buff, sizeof(buff))) + continue; + + return 0; +} diff --git a/userland/crt.c b/userland/crt.c new file mode 100644 index 0000000..92d5ef8 --- /dev/null +++ b/userland/crt.c @@ -0,0 +1,663 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2003 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + + +/** + * @file crt.c + * + * The C RunTime environment for the basic support of SOS C user + * programs + */ + +#include +#include + +#include "libc.h" /* putenv */ +#include "crt.h" + +/** + * Macro used to retrieve the 2 parameters passed to any new thread + * (architecture-dependent) + */ +#define GET_THREAD_PARAMETERS(param1, param2) \ + asm volatile ("movl %%eax, %0 ; movl %%ebx, %1" \ + : "=g"(param1), "=g"(param2) : : "%eax", "%ebx" ) + + +/** + * Helper function to retrieve the arg array from the parameter area + * sent by the parent. + * + * Format of the parent's args area: + * Number of arguments + * Offset of arg 0 (program's name) + * Offset of arg 1 + * ... + * Offset of arg N (last argument) + * NULL + * Offset of environnement variable 0 + * Offset of environnement variable 1 + * ... + * Offset of environnement variable N + * NULL + * Contents of arg 0 with terminal \0 + * Contents of arg 1 with terminal \0 + * ... + * Contents of arg N with terminal \0 + * Contents of env 0 with terminal \0 + * Contents of env 1 with terminal \0 + * ... + * Contents of env N with terminal \0 + * + * This function simply transforms the N offsets into the real + * addresses of the arg X contents (ie it simply adds the base address + * of the area). + */ +static void unmarshall_argv_envp(void * args, + int * argc, + const char ** * argv, + const char ** * envp) +{ + addr_t * offset = (addr_t*) args; + *argc = 0; + *argv = NULL; + + offset = args; + + /* Get argc */ + *argc = * (int*) offset; + offset ++; + + /* Get base of argv */ + *argv = (const char **) offset; + + /* Walk the offsets array and transform these offsets into memory + addresses */ + for ( ; 0 != *offset ; offset ++) + *offset += (addr_t) args; + + /* Skip the NULL separating argv from envp */ + offset ++; + + /* Get base of envp */ + *envp = (const char **) offset; + + /* Walk the offsets array and transform these offsets into memory + addresses */ + for ( ; 0 != *offset ; offset ++) + { + *offset += (addr_t) args; + + /* Register this environment variable */ + putenv((char*) *offset); + } +} + + +/** + * Starter function ! + */ +/** Function called by crt_asm.S:_start to start user program */ +void _cmain(const char* arg_env_area[]) __attribute__((noreturn)); +void _cmain(const char* arg_env_area[]) +{ + + /* Will hold the parameters that will be passed to main */ + const char** argv; + const char** envp; + int argc; + + + /* This starter function expects a main() function somewhere */ + extern int main(int argc, char const* argv[], + char const* envp[]); + + /* Setup the arguments list and the environment variables */ + unmarshall_argv_envp (arg_env_area, & argc, & argv, & envp); + + _sos_exit(main(argc, argv, envp)); +} + + +/* + * By convention, the USER SOS programs always pass 4 arguments to the + * kernel syscall handler: in eax/../edx. For less arguments, the + * unused registers are filled with 0s. For more arguments, the 4th + * syscall parameter gives the address of the array containing the + * remaining arguments. In any case, eax corresponds to the syscall + * IDentifier. + */ + + +inline +int _sos_syscall3(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3) +{ + int ret; + + asm volatile("movl %1,%%eax \n" + "movl %2,%%ebx \n" + "movl %3,%%ecx \n" + "movl %4,%%edx \n" + "int %5\n" + "movl %%eax, %0" + :"=g"(ret) + :"g"(id),"g"(arg1),"g"(arg2),"g"(arg3) + ,"i"(SOS_SWINTR_SOS_SYSCALL) + :"eax","ebx","ecx","edx","memory"); + + return ret; +} + + +int _sos_syscall0(int id) +{ + return _sos_syscall3(id, 0, 0, 0); +} + + +int _sos_syscall1(int id, + unsigned int arg1) +{ + return _sos_syscall3(id, arg1, 0, 0); +} + + +int _sos_syscall2(int id, + unsigned int arg1, + unsigned int arg2) +{ + return _sos_syscall3(id, arg1, arg2, 0); +} + + +int _sos_syscall4(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4) +{ + unsigned int args[] = { arg3, arg4 }; + return _sos_syscall3(id, arg1, arg2, (unsigned)args); +} + + +int _sos_syscall5(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5) +{ + unsigned int args[] = { arg3, arg4, arg5 }; + return _sos_syscall3(id, arg1, arg2, (unsigned)args); +} + + +int _sos_syscall6(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6) +{ + unsigned int args[] = { arg3, arg4, arg5, arg6 }; + return _sos_syscall3(id, arg1, arg2, (unsigned)args); +} + + +int _sos_syscall7(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6, + unsigned int arg7) +{ + unsigned int args[] = { arg3, arg4, arg5, arg6, arg7 }; + return _sos_syscall3(id, arg1, arg2, (unsigned)args); +} + + +int _sos_syscall8(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6, + unsigned int arg7, + unsigned int arg8) +{ + unsigned int args[] = { arg3, arg4, arg5, arg6, arg7, arg8 }; + return _sos_syscall3(id, arg1, arg2, (unsigned)args); +} + + +void _sos_exit(int status) +{ + _sos_syscall1(SOS_SYSCALL_ID_EXIT, (unsigned)status); + + /* Never reached ! */ + for ( ; ; ) + ; +} + + +int _sos_bochs_write(const char * str, unsigned length) +{ + return _sos_syscall2(SOS_SYSCALL_ID_BOCHS_WRITE, + (unsigned)str, + length); +} + + +int _sos_fork() +{ + return _sos_syscall0(SOS_SYSCALL_ID_FORK); +} + + +int _sos_getpid() +{ + return _sos_syscall0(SOS_SYSCALL_ID_GETPID); +} + + +int _sos_exec(const char * prog, + void const* args, + size_t arglen) +{ + return _sos_syscall4(SOS_SYSCALL_ID_EXEC, (unsigned int)prog, + (unsigned int)strlen(prog), + (unsigned int)args, + (unsigned int)arglen); +} + + +int _sos_munmap(void * start, size_t length) +{ + return _sos_syscall2(SOS_SYSCALL_ID_MUNMAP, + (unsigned int)start, + length); +} + + +int _sos_mprotect(const void *addr, size_t len, int prot) +{ + return _sos_syscall3(SOS_SYSCALL_ID_MPROTECT, + (unsigned int)addr, + len, + (unsigned int)prot); +} + + +int _sos_mresize(void * old_addr, size_t old_len, + void * *new_addr, size_t new_len, + unsigned long flags) +{ + return _sos_syscall5(SOS_SYSCALL_ID_MRESIZE, + (unsigned int)old_addr, + old_len, + (unsigned int)new_addr, + new_len, + flags); +} + + +int _sos_msync(void *start, size_t length, int flags) +{ + return _sos_syscall3(SOS_SYSCALL_ID_MSYNC, + (unsigned int)start, + length, + flags); +} + + +/** + * Helper function that represents the start routine of a new user + * thread created from user space (syscall new_thread). It takes 2 + * arguments that are passsed in the eax/ebx registers (@see + * cpu_ustate_init() in hwcore/cpu_context.c): the start function of + * the new thread (eax), the argument passed to it (ebx). + */ +static void thread_routine(void) +{ + sos_thread_func_t * func; + unsigned long int arg; + + GET_THREAD_PARAMETERS(func, arg); + + func(arg); + _sos_exit(0); +} + + +int _sos_new_thread(sos_thread_func_t *func, + void* arg, + size_t stack_size) +{ + return _sos_syscall4(SOS_SYSCALL_ID_NEW_THREAD, + (unsigned)thread_routine, + (unsigned)func, (unsigned)arg, + stack_size); +} + + +int _sos_nanosleep(unsigned long int sec, + unsigned long int nanosec) +{ + return _sos_syscall2(SOS_SYSCALL_ID_NANOSLEEP, + sec, nanosec); +} + + +void * _sos_brk(void * new_top_address) +{ + return (void*)_sos_syscall1(SOS_SYSCALL_ID_BRK, + (unsigned)new_top_address); +} + + +int _sos_mount(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *args) +{ + if (!target || !filesystemtype) + return -1; + + return _sos_syscall7(SOS_SYSCALL_ID_MOUNT, + (unsigned int)source, source?strlen(source):0, + (unsigned int)target, strlen(target), + (unsigned int)filesystemtype, + mountflags, + (unsigned int)args); +} + + +int _sos_umount(const char *target) +{ + if (!target) + return -1; + + return _sos_syscall2(SOS_SYSCALL_ID_UMOUNT, + (unsigned int)target, + strlen(target)); +} + + +void _sos_sync(void) +{ + _sos_syscall0(SOS_SYSCALL_ID_SYNC); +} + + +int _sos_statvfs(const char *path, struct statvfs *buf) +{ + if (! path) + return -1; + + return _sos_syscall3(SOS_SYSCALL_ID_VFSTAT64, + (unsigned)path, + strlen(path), + (unsigned)buf); +} + + +int _sos_open(const char * pathname, int flags, int mode) +{ + if (! pathname) + return -1; + + return _sos_syscall4(SOS_SYSCALL_ID_OPEN, + (unsigned)pathname, + strlen(pathname), + flags, + mode); +} + + +int _sos_close(int fd) +{ + return _sos_syscall1(SOS_SYSCALL_ID_CLOSE, fd); +} + + +int _sos_read(int fd, char * buf, size_t * len) +{ + return _sos_syscall3(SOS_SYSCALL_ID_READ, fd, + (unsigned int) buf, + (unsigned int) len); +} + + +int _sos_write(int fd, const char * buf, size_t * len) +{ + return _sos_syscall3(SOS_SYSCALL_ID_WRITE, fd, + (unsigned int) buf, + (unsigned int) len); +} + + +int _sos_seek64(int fd, loff_t * offset, int whence) +{ + return _sos_syscall3(SOS_SYSCALL_ID_SEEK64, fd, + (unsigned int)offset, + (unsigned int)whence); +} + + +int _sos_fmmap(void ** ptr_hint_addr, size_t len, int prot, int flags, + int fd, loff_t offset) +{ + return _sos_syscall7(SOS_SYSCALL_ID_FSMMAP, + (unsigned int)ptr_hint_addr, len, prot, flags, + (unsigned int)fd, + /* offs64_hi */(offset >> 32), + /* offs64_lo */(offset & 0xffffffff)); +} + + +int _sos_ftruncate64(int fd, loff_t length) +{ + return _sos_syscall2(SOS_SYSCALL_ID_FTRUNCATE64, fd, + (unsigned int)length); +} + + +int _sos_fcntl(int fd, int cmd, int arg) +{ + return _sos_syscall3(SOS_SYSCALL_ID_FCNTL, fd, + (unsigned int)cmd, + (unsigned int)arg); +} + + +int _sos_ioctl(int fd, int cmd, int arg) +{ + return _sos_syscall3(SOS_SYSCALL_ID_IOCTL, fd, + (unsigned int)cmd, + (unsigned int)arg); +} + + +int _sos_creat(const char *pathname, int mode) +{ + if (! pathname) + return -1; + + return _sos_syscall3(SOS_SYSCALL_ID_CREAT, + (unsigned int)pathname, + strlen(pathname), + mode); +} + + +int _sos_link (const char *oldpath, const char *newpath) +{ + if (!oldpath || !newpath) + return -1; + + return _sos_syscall4(SOS_SYSCALL_ID_LINK, + (unsigned int)oldpath, + strlen(oldpath), + (unsigned int)newpath, + strlen(newpath)); +} + + +int _sos_unlink(const char *pathname) +{ + if (! pathname) + return -1; + + return _sos_syscall2(SOS_SYSCALL_ID_UNLINK, + (unsigned int)pathname, + strlen(pathname)); +} + + +int _sos_rename (const char *oldpath, const char *newpath) +{ + if (!oldpath || !newpath) + return -1; + + return _sos_syscall4(SOS_SYSCALL_ID_RENAME, + (unsigned int)oldpath, + strlen(oldpath), + (unsigned int)newpath, + strlen(newpath)); +} + + +int _sos_symlink(const char *target, const char *path) +{ + if (!path || !target) + return -1; + + return _sos_syscall4(SOS_SYSCALL_ID_SYMLINK, + (unsigned int)path, + strlen(path), + (unsigned int)target, + strlen(target)); +} + + +int _sos_mknod(const char *pathname, mode_t mode, + int type, + unsigned int major, unsigned minor) +{ + if (!pathname) + return -1; + + return _sos_syscall6(SOS_SYSCALL_ID_MKNOD, + (unsigned int)pathname, + strlen(pathname), + type, mode, major, minor); +} + + +struct dirent; /* Forward declaration */ +int _sos_readdir(int fd, struct dirent * dirent) +{ + return _sos_syscall2(SOS_SYSCALL_ID_READDIR, + fd, + (unsigned int)dirent); +} + + +int _sos_mkdir(const char *pathname, mode_t mode) +{ + if (!pathname) + return -1; + + return _sos_syscall3(SOS_SYSCALL_ID_MKDIR, + (unsigned int)pathname, + strlen(pathname), + mode); +} + + +int _sos_rmdir(const char *pathname) +{ + if (!pathname) + return -1; + + return _sos_syscall2(SOS_SYSCALL_ID_RMDIR, + (unsigned int)pathname, + strlen(pathname)); +} + + +int _sos_chmod(const char *pathname, mode_t mode) +{ + if (!pathname) + return -1; + + return _sos_syscall3(SOS_SYSCALL_ID_CHMOD, + (unsigned int)pathname, + strlen(pathname), + mode); +} + + +int _sos_stat(const char *pathname, int nofollow, struct stat * st) +{ + if (!pathname || !st) + return -1; + + return _sos_syscall4(SOS_SYSCALL_ID_STAT64, + (unsigned int)pathname, + strlen(pathname), + nofollow, + (unsigned int)st); +} + + +int _sos_chroot(const char *dirname) +{ + if (!dirname) + return -1; + + return _sos_syscall2(SOS_SYSCALL_ID_CHROOT, + (unsigned int)dirname, + strlen(dirname)); +} + + +int _sos_chdir(const char *dirname) +{ + if (!dirname) + return -1; + + return _sos_syscall2(SOS_SYSCALL_ID_CHDIR, + (unsigned int)dirname, + strlen(dirname)); +} + + +int _sos_fchdir(int fd) +{ + return _sos_syscall1(SOS_SYSCALL_ID_FCHDIR, + (unsigned int)fd); +} diff --git a/userland/crt.h b/userland/crt.h new file mode 100644 index 0000000..c15001d --- /dev/null +++ b/userland/crt.h @@ -0,0 +1,233 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2003 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_USER_CRT_H_ +#define _SOS_USER_CRT_H_ + + +/** + * @file crt.h + * + * C runtime environment for the user side of the kernel/user + * interface + */ + +#include + +/** + * We include the syscall.h file from the kernel to retrieve the list + * of available syscall ID + */ +#include + + +/** + * The syscall wrappers hiding the details of the implementation + */ +int _sos_syscall0(int id); + +int _sos_syscall1(int id, + unsigned int arg1); + +int _sos_syscall2(int id, + unsigned int arg1, + unsigned int arg2); + +int _sos_syscall3(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3); + +int _sos_syscall4(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4); + +int _sos_syscall5(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5); + +int _sos_syscall6(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6); + +int _sos_syscall7(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6, + unsigned int arg7); + +int _sos_syscall8(int id, + unsigned int arg1, + unsigned int arg2, + unsigned int arg3, + unsigned int arg4, + unsigned int arg5, + unsigned int arg6, + unsigned int arg7, + unsigned int arg8); + + +/** + * The most important function of a C program ! ;) + */ +void _sos_exit (int status) __attribute__((noreturn)); + + +/** + * Non standard function to print something on bochs + */ +int _sos_bochs_write(const char *str, unsigned length); + + +/** + * Syscall to duplicate the current running process + */ +int _sos_fork(void); + + +/** + * Syscall to retrieved the current process's PID + */ +int _sos_getpid(void); + + +/** + * Syscall to re-initialize the address space of the current process + * with that of the program 'progname' + * + * The args argument is the address of an array that contains the + * arguments and environnement variables for the new process. + */ +int _sos_exec(const char * prog, + void const* args, + size_t arglen); + + +/** + * Syscall to unmap the given user address interval + */ +int _sos_munmap(void * start, size_t length); + + +/** + * Syscall to change the access permissions of the given user address + * interval + */ +int _sos_mprotect(const void *addr, size_t len, int prot); + + +/** + * Syscall to resize the given user mapped resource at the given + * old_addr address + */ +int _sos_mresize(void * old_addr, size_t old_len, + void * *new_addr, size_t new_len, + unsigned long flags); + + +/** + * Syscall to synchronize caches with the contents of the mapped memory + */ +int _sos_msync(void *start, size_t length, int flags); + + +/** + * Definition of a thread routine + */ +typedef void (sos_thread_func_t(unsigned long int arg)); + +/** + * Syscall to create a new thread inside the current process + */ +int _sos_new_thread(sos_thread_func_t *func, + void * arg, + size_t stack_size); + + +/** + * Syscall to put the calling thread asleep + */ +int _sos_nanosleep(unsigned long int sec, + unsigned long int nanosec); + + +/** + * Syscall to get/set heap top address + */ +void * _sos_brk(void * new_top_address); + + +int _sos_mount(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data); +int _sos_umount(const char *target); + +void _sos_sync(void); + +struct statvfs; /**< Forward declaration */ +int _sos_statvfs(const char *path, struct statvfs *buf); + +int _sos_open(const char * pathname, int flags, int mode); +int _sos_close(int fd); + +int _sos_read(int fd, char * buf, size_t * len); +int _sos_write(int fd, const char * buf, size_t * len); +int _sos_seek64(int fd, loff_t * offset, int whence); +int _sos_ftruncate64(int fd, loff_t length); +int _sos_fmmap(void ** ptr_hint_addr, size_t len, int prot, int flags, + int fd, loff_t offset); +int _sos_fcntl(int fd, int cmd, int arg); +int _sos_ioctl(int fd, int cmd, int arg); + +struct dirent; /* Forward declaration */ +int _sos_readdir(int fd, struct dirent * dirent); + +int _sos_creat(const char *pathname, int mode); +int _sos_link (const char *oldpath, const char *newpath); +int _sos_unlink(const char *pathname); +int _sos_rename (const char *oldpath, const char *newpath); +int _sos_symlink(const char *target, const char *path); +int _sos_mknod(const char *pathname, mode_t mode, + int type, + unsigned int major, unsigned minor); + +int _sos_mkdir(const char *pathname, mode_t mode); +int _sos_rmdir(const char *pathname); + +int _sos_chmod(const char *pathname, mode_t mode); + +struct stat; /**< forward declaration */ +int _sos_stat(const char *pathname, int nofollow, struct stat * st); + +int _sos_chroot(const char *dirname); +int _sos_chdir(const char *dirname); +int _sos_fchdir(int fd); + +#endif /* _SOS_USER_CRT_H_ */ diff --git a/userland/crt_asm.S b/userland/crt_asm.S new file mode 100644 index 0000000..e45a092 --- /dev/null +++ b/userland/crt_asm.S @@ -0,0 +1,36 @@ +/* Copyright (C) 2006 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#define ASM_SOURCE 1 + +.file "crt_asm.S" + +.text + +/* The function to call: "C" code start routine (defined in crt.c) */ +.extern _cmain + +/* User program entry point */ +.globl _start + .type _start, @function +_start: + pushl %esp + call _cmain /* Hello "C" world ! */ + addl $4, %esp + + /* We should never reach this line */ + lcall *0 /* Crash the process */ diff --git a/userland/debug.c b/userland/debug.c new file mode 100644 index 0000000..6442dd2 --- /dev/null +++ b/userland/debug.c @@ -0,0 +1,39 @@ +/* Copyright (C) 2004 David Decotigny (with INSA Rennes for vsnprintf) + Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include +#include + +#include "debug.h" + +int bochs_printf(const char *format, /* args */...) +{ + char buff[4096]; + int len; + va_list ap; + + va_start(ap, format); + len = vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + if (len < 0) + return len; + + return _sos_bochs_write(buff, len); +} diff --git a/userland/debug.h b/userland/debug.h new file mode 100644 index 0000000..7515e30 --- /dev/null +++ b/userland/debug.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2003 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_LIBC_DEBUG_H_ +#define _SOS_LIBC_DEBUG_H_ + +/** + * @file debug.h + * + * Non standard debugging features + */ + +/** + * Non standard function to print something on bochs + */ +int bochs_printf(const char *format, /* args */...) + __attribute__ ((format (printf, 1, 2))); + +#endif /* _SOS_USER_LIBC_H_ */ diff --git a/userland/envtest.c b/userland/envtest.c new file mode 100644 index 0000000..19e8027 --- /dev/null +++ b/userland/envtest.c @@ -0,0 +1,16 @@ +#include + +int main(int argc, char * argv[], char *envp[]) +{ + int i; + char **e; + + printf("Nargs: %d\n", argc); + for (i = 0 ; i < argc ; i ++) + printf(" arg[%d] = |%s|\n", i, argv[i]); + + for (e = envp ; e && *e ; e ++) + printf(" ENV |%s|\n", *e); + + return 0; +} diff --git a/userland/fstest.c b/userland/fstest.c new file mode 100644 index 0000000..a8b795c --- /dev/null +++ b/userland/fstest.c @@ -0,0 +1,847 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "fstest_utils.h" + +static const int OPEN_BASE_FD = 3; + +/** + * @file fstest.c + * + * File-system tests + */ + +int main(void) +{ + int fd, len; + char buff[256]; + + bochs_printf("Hi from fstest\n"); + + ls("/", 1, 1); + ls(".", 1, 1); + ls("", 1, 1); + ls(0, 1, 1); + + TEST_EXPECT_CONDITION(fd = open("", 0), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open(0, 0), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDWR), + RETVAL == OPEN_BASE_FD + 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDONLY), + RETVAL == OPEN_BASE_FD + 1); + + TEST_EXPECT_CONDITION(fd = open("/", O_WRONLY), + RETVAL == OPEN_BASE_FD + 2); + + TEST_EXPECT_CONDITION(close(OPEN_BASE_FD + 1), + RETVAL == 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_WRONLY), + RETVAL == OPEN_BASE_FD + 1); + + TEST_EXPECT_CONDITION(fd = open("//", O_WRONLY), + RETVAL == OPEN_BASE_FD + 3); + + TEST_EXPECT_CONDITION(fd = open("////////", O_WRONLY), + RETVAL == OPEN_BASE_FD + 4); + + TEST_EXPECT_CONDITION(fd = open("/does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist////", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/////", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist////", O_WRONLY), + RETVAL); + + TEST_EXPECT_CONDITION(fd = open("/does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDWR), + RETVAL == OPEN_BASE_FD + 5); + + TEST_EXPECT_CONDITION(fd = open("/tutu.txt", O_RDWR), + RETVAL < 0); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(fd = open("/tutu.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 6); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR | O_CREAT), + RETVAL == OPEN_BASE_FD + 7); + + /* O_EXCL with an already-existing file */ + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR | O_CREAT | O_EXCL), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/toto.txt", O_RDWR | O_CREAT | O_EXCL, + S_IRWXALL), + RETVAL == OPEN_BASE_FD + 8); + + /* O_EXCL with an already-existing file */ + TEST_EXPECT_CONDITION(fd = open("toto.txt", O_RDWR | O_CREAT | O_EXCL), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("toto.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 9); + + /* Trailing slash on non-dir entries */ + TEST_EXPECT_CONDITION(fd = open("toto.txt/", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("notdir/", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("notdir/", O_RDWR), RETVAL < 0); + + /* Substring match */ + TEST_EXPECT_CONDITION(fd = open("toto1.txt", O_RDWR), + RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto1.tx", O_RDWR | O_CREAT, S_IWUSR), + RETVAL == OPEN_BASE_FD + 10); + + /* Substring match */ + TEST_EXPECT_CONDITION(fd = open("toto.tx", O_RDWR), + RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto.tx", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 11); + + /* + * read/write/seek + */ + + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), + RETVAL == 0); + + strzcpy(buff, "Bonjour !", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL == 10); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), + RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 10); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strcmp("Bonjour !", buff), RETVAL ==0); + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_CUR), RETVAL == 10); + + /* + * truncate + */ + + TEST_EXPECT_CONDITION(ftruncate(fd, 3), RETVAL == 0); + + /* The current position should not have changed */ + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_CUR), RETVAL == 10); + + /* Make sure we cannot read anything because we get past the end of + the file */ + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Now get back at the begining of the file */ + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + /* Make sure that we can read something with the correct first 3 + characters */ + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 3); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strncmp("Bon", buff, len), RETVAL == 0); + + /* + * open mode + */ + + ls("/", 1, 1); + + + /* Open r/w, create read-only */ + + TEST_EXPECT_CONDITION(fd = open("toto2.txt", O_RDWR | O_CREAT, S_IRUSR), + RETVAL == OPEN_BASE_FD + 12); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Permission denied", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL < 0); /* Permission denied ! */ + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Open read-only, create r/w */ + + TEST_EXPECT_CONDITION(fd = open("toto3.txt", O_RDONLY | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 13); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Permission denied 2", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL < 0); /* Permission denied ! */ + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Create another process that chdirs in it */ + if (fork() == 0) + { + bochs_printf("Hello from child\n"); + TEST_EXPECT_CONDITION(fd = open("shrd.txt", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 14); + strzcpy(buff, "Hello from child !", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 19), + RETVAL == 19); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + bochs_printf("Bye from child\n"); + return 0; + } + + bochs_printf("Father sleeping\n"); + nanosleep(1, 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(fd = open("shrd.txt", O_RDONLY), + RETVAL == OPEN_BASE_FD + 14); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 19); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strncmp("Hello from child !", buff, len), + RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("shrd.txt"), RETVAL == 0); + ls("/", 1, 1); + + /* + * ioctl / fcntl + */ + + TEST_EXPECT_CONDITION(fcntl(fd, 2, 3), RETVAL < 0); /* Not supported + by virtfs */ + TEST_EXPECT_CONDITION(ioctl(fd, 2, 3), RETVAL < 0); /* Not supported + by virtfs */ + + ls("/", 1, 1); + + /* + * creat/link/unlink/symlink + */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + TEST_EXPECT_CONDITION(link("toto4.txt", "toto5.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(link("toto4.txt", "toto5.txt"), RETVAL < 0); /*EEXIST*/ + TEST_EXPECT_CONDITION(link("toto4.txt", "toto.txt"), RETVAL < 0); /*EEXIST*/ + ls("/", 1, 1); + TEST_EXPECT_CONDITION(chmod("toto5.txt", S_IRUSR | S_IWUSR), RETVAL == 0); + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(fd = open("toto5.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 14); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto5", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + TEST_EXPECT_CONDITION(fd = open("toto4.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 15); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(link("dangling", "toto42.txt"), RETVAL < 0); /*ENOENT*/ + TEST_EXPECT_CONDITION(unlink("toto4.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto42.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(unlink("toto4.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IWUSR | S_IRUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto5.txt/"), RETVAL < 0); /* EISDIR ? */ + TEST_EXPECT_CONDITION(rmdir("toto5.txt/"), RETVAL < 0); /* ENOTDIR ? */ + TEST_EXPECT_CONDITION(rmdir("toto5.txt"), RETVAL < 0); /* ENOTDIR ? */ + TEST_EXPECT_CONDITION(unlink("toto5.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto5.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + + ls("/", 1, 1); + + /* Change symlinks */ + TEST_EXPECT_CONDITION(symlink("toto4.txt", "toto5.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(symlink("toto4.txt", "toto5.txt"), RETVAL < 0); /*EEXIST*/ + TEST_EXPECT_CONDITION(symlink("toto4.txt", "toto.txt"), RETVAL < 0); /*EEXIST*/ + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(fd = open("toto5.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 16); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto5", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strcmp(buff, "Hello world from toto5"), RETVAL == 0); + + TEST_EXPECT_CONDITION(fd = open("toto4.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 17); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strcmp(buff, "Hello world from toto5"), RETVAL == 0); + + + TEST_EXPECT_CONDITION(symlink("dangling", "toto6.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(symlink("dangling", "toto6.txt"), RETVAL < 0); /*EEXIST*/ + + TEST_EXPECT_CONDITION(fd = open("toto6.txt", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto6.txt", O_RDWR | O_NOFOLLOW), + RETVAL == OPEN_BASE_FD + 18); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 8); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strncmp(buff, "dangling", len), RETVAL == 0); + + ls("/", 1, 1); + + /* mkdir/rmdir */ + + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(unlink("yo1"), RETVAL < 0); /*EISDIR*/ + TEST_EXPECT_CONDITION(unlink("yo1/"), RETVAL < 0); /*EISDIR*/ + TEST_EXPECT_CONDITION(rmdir("yo1"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("yo1"), RETVAL < 0); /*ENOENT*/ + TEST_EXPECT_CONDITION(rmdir("yoda"), RETVAL < 0); /*ENOENT*/ + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("yoplait1", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait1", "mouf1"), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("mouf1/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 19); + TEST_EXPECT_CONDITION(unlink("mouf1/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("mouf1"), RETVAL == 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait2", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait2", "mouf2"), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("mouf2/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 20); + TEST_EXPECT_CONDITION(rmdir("mouf2"), RETVAL < 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait3", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait3", "mouf3"), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("mouf3/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("mouf3/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("mouf3"), RETVAL == 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait4", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait4", "mouf4"), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("mouf4/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("mouf4"), RETVAL < 0); + + ls("/", 1, 1); + + /* Not supported by virtfs */ + TEST_EXPECT_CONDITION(mknod("dev1", S_IRWXALL, S_IFCHR, 420, 268), + RETVAL == 0); + /* Make sure the device cannot be opened (no device should be + associated to it */ + TEST_EXPECT_CONDITION(open("dev1", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("dev1"), RETVAL == 0); + + ls("/", 1, 1); + ls("..", 1, 1); + ls("../", 1, 1); + ls("/..", 1, 1); + + /* subdirs */ + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL < 0); + + ls("/", 1, 1); + ls("/yo1", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL == OPEN_BASE_FD + 21); + + ls("/", 1, 1); + ls("/yo1", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("yo2", S_IRUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo2/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo2/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL < 0); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("yo3", S_IWUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo3/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo3/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL == OPEN_BASE_FD + 22); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("yo4", S_IWUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo4/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo4/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL < 0); + + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(chdir("nowhere"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir("yo1"), RETVAL == 0); + + ls(".", 1, 1); + ls("/", 1, 1); + ls("..", 1, 1); + ls("../../../../", 1, 1); + ls("/../../../../", 1, 1); + + /* Test chroot */ + + TEST_EXPECT_CONDITION(chroot("nowhere"), RETVAL < 0); + TEST_EXPECT_CONDITION(chroot("."), RETVAL == 0); + ls(".", 1, 1); + ls("/", 1, 1); + ls("..", 1, 1); + ls("../../../../", 1, 1); + ls("/../../../../", 1, 1); + + /* mount */ + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", NULL, 0, NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", "yoplait", 0, NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", "virtfs", 0, NULL), RETVAL < 0); + + TEST_EXPECT_CONDITION(mkdir("mnt", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("mnt/subdir0", S_IRWXALL), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(mount(NULL, "mnt", "virtfs", 0, NULL), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(mkdir("mnt/subdir_mounted", S_IRWXALL), RETVAL == 0); + ls("/", 1, 1); + + /* Make sure we cannot umount if the FS is in use */ + TEST_EXPECT_CONDITION(fd = open("mnt/subdir_mounted", O_DIRECTORY), + RETVAL == OPEN_BASE_FD + 23); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* Make sure we cannot umount if the FS is in use */ + TEST_EXPECT_CONDITION(chdir("mnt"), RETVAL == 0); + TEST_EXPECT_CONDITION(umount("/mnt"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir(".."), RETVAL == 0); + ls(".", 1, 1); + + /* Create another process that chdirs in it */ + if (fork() == 0) + { + bochs_printf("Hello from child\n"); + TEST_EXPECT_CONDITION(chdir("mnt"), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("subdir_mounted", O_DIRECTORY), + RETVAL == OPEN_BASE_FD + 23); + bochs_printf("Child sleeping...\n"); + nanosleep(2, 0); + bochs_printf("Bye from child\n"); + return 0; + } + else + { + bochs_printf("Father sleeping\n"); + nanosleep(1, 0); + bochs_printf("Father trying to umount, should fail (a process chdir'ed in it)\n"); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + bochs_printf("Father Resuming normal operation in 3s...\n"); + nanosleep(3, 0); + } + + TEST_EXPECT_CONDITION(umount(NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(umount("nowhere"), RETVAL < 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + + /* + * Mountchain exploration + */ + TEST_EXPECT_CONDITION(mkdir("/mnt2", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("/mnt2/nothing-mounted", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mount(NULL, "mnt2", "virtfs", 0, NULL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("/mnt2/mountpoint-1", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mount(NULL, "mnt2", "virtfs", 0, NULL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("/mnt2/mountpoint-2", S_IRWXALL), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL < 0); + + /* + * Erasing files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 23); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(unlink("toto8.txt"), RETVAL == 0); + ls("/", 1, 1); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + /* + * rmdir on still used dirs + */ + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(chdir("plotch"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("../plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir(".."), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("plotch/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("plotch/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("plotch/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 24); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("plotch/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + ls("/", 1, 1); + + TEST_EXPECT_CONDITION(mkdir("this is ", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("this is / a long path", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("this is / a long path/tothe", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("this is / a long path/tothe/destination ", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("this is / a long path/tothe/destination / directory", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("this is / a long path/tothe/destination / directory/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 24); + TEST_EXPECT_CONDITION(rmdir("this is / a long path/tothe/destination / directory"), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("this is / a long path/tothe/destination / directory/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("this is / a long path/tothe/destination / directory"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("this is / a long path/tothe/destination / directory/"), RETVAL < 0); + TEST_EXPECT_CONDITION(rmdir("this is / a long path/tothe/destination "), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("this is / a long path/tothe/"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("this is / a long path"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("this is "), RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + ls("/", 1, 1); + + /* + * Unlink/link files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 24); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(link("toto8.txt", "toto9.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto8.txt"), RETVAL == 0); + ls("/", 1, 1); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto9.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 25); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(unlink("toto9.txt"), RETVAL == 0); + ls("/", 1, 1); + + /* + * Rename files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 26); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(rename("toto8.txt", "toto9.txt"), RETVAL == 0); + ls("/", 1, 1); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto9.txt", O_RDWR), + RETVAL == OPEN_BASE_FD + 26); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(unlink("toto9.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* Rename */ + ls("/", 1, 1); + TEST_EXPECT_CONDITION(rename("/mnt/subdir0", "subdir42"), RETVAL == 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(rename("titi1.txt", "subdir42"), RETVAL < 0); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(rename("titi1.txt", "subdir42/titi.txt"), RETVAL == 0); + + /* Rename a dir being used */ + TEST_EXPECT_CONDITION(chdir("subdir42"), RETVAL == 0); + ls(".", 1, 1); + ls("..", 1, 1); + ls("/", 1, 1); + TEST_EXPECT_CONDITION(rename("../subdir42", "../subdir000"), RETVAL == 0); + ls(".", 1, 1); + ls("/", 1, 1); + ls("..", 1, 1); + + /* + * test mmap + */ + + /* Common use: shared file suppressed as soon as possible */ + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 26); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello1 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello1 from the child (shared mapping) !"), + RETVAL == 0); + munmap(shrd, 8192); + } + ls("/", 1, 1); + TEST_EXPECT_CONDITION(unlink("mmap.txt"), RETVAL == 0); + ls("/", 1, 1); + + /* Common use: shared file suppressed as soon as possible */ + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 27); + TEST_EXPECT_CONDITION(unlink("mmap.txt"), RETVAL == 0); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello2 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello2 from the child (shared mapping) !"), + RETVAL == 0); + } + ls("/", 1, 1); + + /* Basic use */ + TEST_EXPECT_CONDITION(creat("mmap.txt", S_IRUSR | S_IWUSR), RETVAL == 0); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 28); + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello3 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 28); + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello3 from the child (shared mapping) !"), + RETVAL == 0); + } + ls("/", 1, 1); + + bochs_printf("Bye from fstest\n"); + ls("/", 1, 1); + return 0; +} diff --git a/userland/fstest_utils.c b/userland/fstest_utils.c new file mode 100644 index 0000000..73db4ca --- /dev/null +++ b/userland/fstest_utils.c @@ -0,0 +1,149 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include + +#include "fstest_utils.h" + + +/** Helper functions that dumps the contents of the current working + directory of the process */ +static void cwd_ls(int detailed, int recursive, int reclevel) +{ + char tab[256], *c; + int i; + struct dirent * dirent; + DIR * here; + + here = opendir("."); + if (! here) + return; + + /* Build initial tabulation */ + if (recursive) + { + for (c = tab, i = 0 ; (i < reclevel) && (i < sizeof(tab)/2) ; i++) + { + *c++ = ' '; + *c++ = ' '; + } + *c++ = '\0'; + } + else + *tab = '\0'; + + while ((dirent = readdir(here)) != NULL) + { + char entrychar; + char * entrysuffix; + + switch(dirent->type) + { + case S_IFREG: entrychar='-'; entrysuffix=""; break; + case S_IFDIR: entrychar='d'; entrysuffix="/"; break; + case S_IFLNK: entrychar='l'; entrysuffix="@"; break; + case S_IFCHR: entrychar='c'; entrysuffix=NULL; break; + case S_IFBLK: entrychar='b'; entrysuffix=NULL; break; + default: entrychar='?'; entrysuffix="?!?"; break; + } + + if (detailed) + { + struct stat stat; + char target_name[SOS_FS_DIRENT_NAME_MAXLEN]; + char majorminor[24]; + + if (lstat(dirent->name, & stat)) + continue; + + *target_name = '\0'; + if (stat.st_type == S_IFLNK) + { + int fd = open(dirent->name, O_RDONLY | O_NOFOLLOW); + if (fd >= 0) + { + int len = read(fd, target_name, sizeof(target_name) - 1); + if (len < 0) + *target_name='\0'; + else + target_name[len] = '\0'; + close(fd); + } + } + else if ( (stat.st_type == S_IFCHR) || (stat.st_type == S_IFBLK) ) + { + snprintf(majorminor, sizeof(majorminor), " %d,%d", + stat.st_rdev_major, stat.st_rdev_minor); + entrysuffix = majorminor; + } + + bochs_printf("%s%c%c%c%c %lld %s%s%s%s (location=0x%llx)\n", + tab, entrychar, + (stat.st_access_rights&S_IRUSR)?'r':'-', + (stat.st_access_rights&S_IWUSR)?'w':'-', + (stat.st_access_rights&S_IXUSR)?'x':'-', + stat.st_size, + dirent->name, + entrysuffix, + (stat.st_type==S_IFLNK)?" -> ":"", + target_name, + stat.st_storage_location); + } + else + bochs_printf("%s%s%s\n", + tab, dirent->name, entrysuffix); + + /* Next iteration */ + if (recursive && (dirent->type == S_IFDIR)) + { + int fd_here = dirfd(here); + if (chdir(dirent->name)) + continue; + cwd_ls(detailed, recursive, reclevel+1); + if(fchdir(fd_here)) + { + closedir(here); + return; + } + } + } + closedir(here); +} + + +void ls(const char * path, int detailed, int recursive) +{ + int fd_here = open(".", O_RDONLY | O_DIRECTORY); + if (fd_here < 0) + return; + + if (chdir(path)) + { + close(fd_here); + return; + } + + bochs_printf("----------- Contents of %s:\n", path); + cwd_ls(detailed, recursive, 1); + bochs_printf("---------------------------\n"); + + fchdir(fd_here); + close(fd_here); +} diff --git a/userland/fstest_utils.h b/userland/fstest_utils.h new file mode 100644 index 0000000..198506b --- /dev/null +++ b/userland/fstest_utils.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_FSTEST_UTILS_H_ +#define _SOS_FSTEST_UTILS_H_ + +/** + * @file fstest.h + * + * Macro and support functions for the fstest programs + */ + +#define TEST_EXPECT_CONDITION(code,condition) \ + ({ static char * _str_ok = "PASSED"; \ + static char * _str_failed = "FAILED"; \ + int RETVAL = (int)(code); \ + int _verdict = (int)(condition); \ + bochs_printf("L%d %s: %s %s (retval=%p aka %d)\n", \ + __LINE__, #code, #condition, \ + (_verdict)?_str_ok:_str_failed, (void*)RETVAL, RETVAL); \ + _verdict; }) + + +/** Dump the list of files to the bochs debugging output */ +void ls(const char * path, int detailed, int recursive); + +#endif /* _SOS_FSTEST_UTILS_H_ */ diff --git a/userland/fstestfat.c b/userland/fstestfat.c new file mode 100644 index 0000000..5f3bacd --- /dev/null +++ b/userland/fstestfat.c @@ -0,0 +1,768 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + +#include "fstest_utils.h" + +static const int OPEN_BASE_FD = 3; + +/** + * @file fstestfat.c + * + * File-system tests + */ + +int main(void) +{ + int fd, len; + char buff[256]; + + bochs_printf("Hi from fstest FAT\n"); + + ls("/", 1, 0); + ls(".", 1, 0); + ls("", 1, 0); + ls(0, 1, 0); + + TEST_EXPECT_CONDITION(fd = open("", 0), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open(0, 0), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDWR), + RETVAL == OPEN_BASE_FD + 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDONLY), + RETVAL == OPEN_BASE_FD + 1); + + TEST_EXPECT_CONDITION(fd = open("/", O_WRONLY), + RETVAL == OPEN_BASE_FD + 2); + + TEST_EXPECT_CONDITION(close(OPEN_BASE_FD + 1), + RETVAL == 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_WRONLY), + RETVAL == OPEN_BASE_FD + 1); + + TEST_EXPECT_CONDITION(fd = open("//", O_WRONLY), + RETVAL == OPEN_BASE_FD + 3); + + TEST_EXPECT_CONDITION(fd = open("////////", O_WRONLY), + RETVAL == OPEN_BASE_FD + 4); + + TEST_EXPECT_CONDITION(fd = open("/does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist////", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/////", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist/", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist////", O_WRONLY), + RETVAL); + + TEST_EXPECT_CONDITION(fd = open("/does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/does not exist////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("////does not exist/////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist/ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("does not exist////ab c d", O_WRONLY), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("/", O_RDWR), + RETVAL == OPEN_BASE_FD + 5); + + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR), + RETVAL < 0); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 6); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR | O_CREAT), + RETVAL == OPEN_BASE_FD + 7); + + /* O_EXCL with an already-existing file */ + TEST_EXPECT_CONDITION(fd = open("tutu.txt", O_RDWR | O_CREAT | O_EXCL), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("toto.txt", O_RDWR | O_CREAT | O_EXCL, + S_IRWXALL), + RETVAL == OPEN_BASE_FD + 8); + + /* O_EXCL with an already-existing file */ + TEST_EXPECT_CONDITION(fd = open("toto.txt", O_RDWR | O_CREAT | O_EXCL), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("toto.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 9); + + /* Trailing slash on non-dir entries */ + TEST_EXPECT_CONDITION(fd = open("toto.txt/", O_RDWR), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("notdir/", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL < 0); + + TEST_EXPECT_CONDITION(fd = open("notdir/", O_RDWR), RETVAL < 0); + + /* Substring match */ + TEST_EXPECT_CONDITION(fd = open("toto1.txt", O_RDWR), + RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto1.tx", O_RDWR | O_CREAT, S_IWUSR), + RETVAL == OPEN_BASE_FD + 10); + + /* Substring match */ + TEST_EXPECT_CONDITION(fd = open("toto.tx", O_RDWR), + RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("toto.tx", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 11); + + /* + * read/write/seek + */ + + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), + RETVAL == 0); + + strzcpy(buff, "Bonjour !", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL == 10); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), + RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 10); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strcmp("Bonjour !", buff), RETVAL ==0); + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_CUR), RETVAL == 10); + + /* + * truncate + */ + + TEST_EXPECT_CONDITION(ftruncate(fd, 3), RETVAL == 0); + + /* The current position should not have changed */ + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_CUR), RETVAL == 10); + + /* Make sure we cannot read anything because we get past the end of + the file */ + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Now get back at the begining of the file */ + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + /* Make sure that we can read something with the correct first 3 + characters */ + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 3); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strncmp("Bon", buff, len), RETVAL == 0); + + /* + * open mode + */ + + ls(".", 1, 0); +#if 1 + /* Open r/w, create read-only */ + + TEST_EXPECT_CONDITION(fd = open("toto2.txt", O_RDWR | O_CREAT, S_IRUSR), + RETVAL == OPEN_BASE_FD + 12); +#endif + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Permission denied", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL < 0); /* Permission denied ! */ + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Open read-only, create r/w */ + + TEST_EXPECT_CONDITION(fd = open("toto3.txt", O_RDONLY | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 13); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Permission denied 2", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 10), + RETVAL < 0); /* Permission denied ! */ + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + /* Create another process that chdirs in it */ + if (fork() == 0) + { + bochs_printf("Hello from child\n"); + TEST_EXPECT_CONDITION(fd = open("shrd.txt", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 14); + strzcpy(buff, "Hello from child !", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 19), + RETVAL == 19); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + bochs_printf("Bye from child\n"); + return 0; + } + + bochs_printf("Father sleeping\n"); + nanosleep(1, 0); + ls(".", 1, 0); + TEST_EXPECT_CONDITION(fd = open("shrd.txt", O_RDONLY), + RETVAL == OPEN_BASE_FD + 14); + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), + RETVAL == 19); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(strncmp("Hello from child !", buff, len), + RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("shrd.txt"), RETVAL == 0); + ls(".", 1, 0); + + /* + * ioctl / fcntl + */ + + TEST_EXPECT_CONDITION(fcntl(fd, 2, 3), RETVAL < 0); /* Not supported + by FAT */ + TEST_EXPECT_CONDITION(ioctl(fd, 2, 3), RETVAL < 0); /* Not supported + by FAT */ + + ls(".", 1, 0); + + /* + * creat/link/unlink/symlink + */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + /* Hard link not supported by FAT */ + TEST_EXPECT_CONDITION(link("toto4.txt", "toto5.txt"), RETVAL < 0); /*ENOSUP*/ + ls(".", 1, 0); + TEST_EXPECT_CONDITION(chmod("toto4.txt", S_IRUSR | S_IWUSR), RETVAL == 0); + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(link("dangling", "toto42.txt"), RETVAL < 0); /*ENOENT*/ + TEST_EXPECT_CONDITION(unlink("toto4.txt"), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto42.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(unlink("toto4.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IWUSR | S_IRUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("toto5.txt/"), RETVAL < 0); /* EISDIR ? */ + TEST_EXPECT_CONDITION(rmdir("toto5.txt/"), RETVAL < 0); /* ENOTDIR ? */ + TEST_EXPECT_CONDITION(rmdir("toto5.txt"), RETVAL < 0); /* ENOTDIR ? */ + TEST_EXPECT_CONDITION(unlink("toto5.txt"), RETVAL < 0); /* ENOENT */ + TEST_EXPECT_CONDITION(creat("toto4.txt", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + + ls(".", 1, 0); + + /* Change symlinks */ + /* Symlink not supported by FAT */ + TEST_EXPECT_CONDITION(symlink("toto4.txt", "toto5.txt"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(symlink("toto4.txt", "toto.txt"), RETVAL < 0); /*EEXIST*/ + + ls(".", 1, 0); + + /* mkdir/rmdir */ + + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRWXALL), RETVAL < 0); /*EEXIST*/ + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(unlink("yo1"), RETVAL < 0); /*EISDIR*/ + TEST_EXPECT_CONDITION(unlink("yo1/"), RETVAL < 0); /*EISDIR*/ + TEST_EXPECT_CONDITION(rmdir("yo1"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("yo1"), RETVAL < 0); /*ENOENT*/ + TEST_EXPECT_CONDITION(rmdir("yoda"), RETVAL < 0); /*ENOENT*/ + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait1", S_IRWXALL), RETVAL == 0); + /* FAT not supported rename because SOS' rename implementation use link + * and link is not by FAT FS */ + TEST_EXPECT_CONDITION(rename("yoplait1", "mouf1"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(fd = open("yoplait1/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 14); + TEST_EXPECT_CONDITION(unlink("yoplait1/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("yoplait1"), RETVAL == 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait2", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait2", "mouf2"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(fd = open("yoplait2/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 15); + TEST_EXPECT_CONDITION(rmdir("yoplait2"), RETVAL < 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait3", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait3", "mouf3"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(creat("yoplait3/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(unlink("yoplait3/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("yoplait3"), RETVAL == 0); + + TEST_EXPECT_CONDITION(mkdir("yoplait4", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rename("yoplait4", "mouf4"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(creat("yoplait4/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("yoplait4"), RETVAL < 0); + + ls(".", 1, 0); + + /* Not supported by FAT */ + TEST_EXPECT_CONDITION(mknod("dev1", S_IRWXALL, S_IFCHR, 420, 268), + RETVAL < 0); /*ENOSUP*/ + /* Make sure the device cannot be opened (no device should be + associated to it */ + TEST_EXPECT_CONDITION(open("dev1", O_RDONLY), RETVAL < 0); /*EEXIST*/ + TEST_EXPECT_CONDITION(unlink("dev1"), RETVAL < 0); /*EEXIST*/ + + ls(".", 1, 0); + ls("..", 1, 0); + ls("../", 1, 0); + ls("/..", 1, 0); + + /* subdirs */ + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL < 0); + + ls(".", 1, 0); + ls("yo1", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("yo1", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo1/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL == OPEN_BASE_FD + 16); + + ls(".", 1, 0); + ls("yo1", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("yo2", S_IRUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo2/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo2/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL < 0); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("yo3", S_IWUSR | S_IXUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo3/titi1.txt", O_RDONLY), RETVAL < 0); + TEST_EXPECT_CONDITION(fd = open("yo3/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL == OPEN_BASE_FD + 17); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("yo4", S_IWUSR), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("yo4/titi1.txt", O_RDONLY), RETVAL < 0); + /* FAT FS is always executable, then yo4 is S_IWUSR | S_IXUSR */ + TEST_EXPECT_CONDITION(fd = open("yo4/titi1.txt", O_RDONLY | O_CREAT, + S_IRUSR), RETVAL == OPEN_BASE_FD + 18); + + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(chdir("nowhere"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir("yo1"), RETVAL == 0); + + ls(".", 1, 0); + ls("/", 1, 0); + ls("..", 1, 0); + ls("../../../../", 1, 0); + ls("/../../../../", 1, 0); + + /* Test chroot */ + + TEST_EXPECT_CONDITION(chroot("nowhere"), RETVAL < 0); + TEST_EXPECT_CONDITION(chroot("."), RETVAL == 0); + ls(".", 1, 0); + ls("/", 1, 0); + ls("..", 1, 0); + ls("../../../../", 1, 0); + ls("/../../../../", 1, 0); + + /* mount */ + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", NULL, 0, NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", "yoplait", 0, NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(mount(NULL, "nowhere", "virtfs", 0, NULL), RETVAL < 0); + + TEST_EXPECT_CONDITION(mkdir("mnt", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("mnt/subdir0", S_IRWXALL), RETVAL == 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + TEST_EXPECT_CONDITION(mount(NULL, "mnt", "virtfs", 0, NULL), RETVAL == 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + TEST_EXPECT_CONDITION(mkdir("mnt/dir_mnt", S_IRWXALL), RETVAL == 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + + /* Make sure we cannot umount if the FS is in use */ + TEST_EXPECT_CONDITION(fd = open("mnt/dir_mnt", O_DIRECTORY), + RETVAL == OPEN_BASE_FD + 19); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* Make sure we cannot umount if the FS is in use */ + TEST_EXPECT_CONDITION(chdir("mnt"), RETVAL == 0); + TEST_EXPECT_CONDITION(umount("../mnt"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir(".."), RETVAL == 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + + /* Create another process that chdirs in it */ + if (fork() == 0) + { + bochs_printf("Hello from child\n"); + TEST_EXPECT_CONDITION(chdir("mnt"), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("dir_mnt", O_DIRECTORY), + RETVAL == OPEN_BASE_FD + 19); + bochs_printf("Child sleeping...\n"); + nanosleep(2, 0); + bochs_printf("Bye from child\n"); + return 0; + } + else + { + bochs_printf("Father sleeping\n"); + nanosleep(1, 0); + bochs_printf("Father trying to umount, should fail (a process chdir'ed in it)\n"); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + bochs_printf("Father Resuming normal operation in 3s...\n"); + nanosleep(3, 0); + } + + TEST_EXPECT_CONDITION(umount(NULL), RETVAL < 0); + TEST_EXPECT_CONDITION(umount("nowhere"), RETVAL < 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL == 0); + ls(".", 1, 0); + ls("mnt", 1, 0); + TEST_EXPECT_CONDITION(umount("mnt"), RETVAL < 0); + + /* + * Mountchain exploration + */ + TEST_EXPECT_CONDITION(mkdir("mnt2", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("mnt2/nothing", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mount(NULL, "mnt2", "virtfs", 0, NULL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("mnt2/mntpt-1", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mount(NULL, "mnt2", "virtfs", 0, NULL), RETVAL == 0); + TEST_EXPECT_CONDITION(mkdir("mnt2/mntpt-2", S_IRWXALL), RETVAL == 0); + ls(".", 1, 0); + ls("mnt2", 1, 0); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL == 0); + ls(".", 1, 0); + ls("mnt2", 1, 0); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL == 0); + ls(".", 1, 0); + ls("mnt2", 1, 0); + TEST_EXPECT_CONDITION(umount("mnt2"), RETVAL < 0); + + /* + * Erasing files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 19); + ls(".", 1, 0); + TEST_EXPECT_CONDITION(unlink("toto8.txt"), RETVAL == 0); + ls(".", 1, 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + /* + * rmdir on still used dirs + */ + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(chdir("plotch"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("../plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(chdir(".."), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(creat("plotch/a", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("plotch/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + ls(".", 1, 0); + + TEST_EXPECT_CONDITION(mkdir("plotch", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(fd = open("plotch/a", O_RDWR | O_CREAT, S_IRWXALL), + RETVAL == OPEN_BASE_FD + 20); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL < 0); + TEST_EXPECT_CONDITION(unlink("plotch/a"), RETVAL == 0); + TEST_EXPECT_CONDITION(rmdir("plotch"), RETVAL == 0); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + ls(".", 1, 0); + + /* Invalid char space in the name */ + TEST_EXPECT_CONDITION(mkdir("this is ", S_IRWXALL), RETVAL < 0); /*EINVAL*/ + /* Name too long */ + TEST_EXPECT_CONDITION(mkdir("nametoolongwithoutspacechar", S_IRWXALL), RETVAL < 0); /*ENAMETOOLONG*/ + /* Invalid char space in the name and name too long */ + TEST_EXPECT_CONDITION(mkdir("this is / a long path", S_IRWXALL), RETVAL < 0); /*ENAMETOOLONG*/ + /* Invalid char space in the name and name too long */ + TEST_EXPECT_CONDITION(rmdir("this is / a long path"), RETVAL < 0); /*EEXIST*/ + /* Name too long */ + TEST_EXPECT_CONDITION(rmdir("nametoolongwithoutspacechar"), RETVAL < 0); /*EEXIST*/ + /* Invalid char space in the name */ + TEST_EXPECT_CONDITION(rmdir("this is "), RETVAL < 0); /*EEXIST*/ + ls(".", 1, 0); + + /* + * Unlink/link files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 20); + ls(".", 1, 0); + TEST_EXPECT_CONDITION(link("toto8.txt", "toto9.txt"), RETVAL < 0); /*ENOSUP*/ + TEST_EXPECT_CONDITION(unlink("toto8.txt"), RETVAL == 0); + ls(".", 1, 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR), RETVAL < 0); + ls(".", 1, 0); + + /* + * Rename files while they are in use + */ + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 21); + ls(".", 1, 0); + TEST_EXPECT_CONDITION(rename("toto8.txt", "toto9.txt"), RETVAL < 0); /*ENOSUP*/ + ls(".", 1, 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 0); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Hello world from toto8", 256); + TEST_EXPECT_CONDITION(len = write(fd, buff, 24), RETVAL == 24); + + TEST_EXPECT_CONDITION(lseek(fd, 0, SEEK_SET), RETVAL == 0); + + strzcpy(buff, "Garbage garbage garbage", 256); + TEST_EXPECT_CONDITION(len = read(fd, buff, 256), RETVAL == 24); + bochs_printf("read s='%s'\n", buff); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + TEST_EXPECT_CONDITION(fd = open("toto8.txt", O_RDWR), RETVAL == OPEN_BASE_FD + 21); + TEST_EXPECT_CONDITION(close(fd), RETVAL == 0); + + /* Rename */ + ls(".", 1, 0); + TEST_EXPECT_CONDITION(rename("/mnt/subdir0", "subdir42"), RETVAL < 0); /*ENOSUP*/ + ls(".", 1, 0); + TEST_EXPECT_CONDITION(rename("titi1.txt", "subdir42"), RETVAL < 0); /*ENOSUP*/ + ls(".", 1, 0); + TEST_EXPECT_CONDITION(rename("titi1.txt", "subdir42/titi.txt"), RETVAL < 0); /*ENOSUP*/ + + /* + * test mmap + */ + + /* Common use: shared file suppressed as soon as possible */ + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 21); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello1 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello1 from the child (shared mapping) !"), + RETVAL == 0); + munmap(shrd, 8192); + } + ls(".", 1, 0); + TEST_EXPECT_CONDITION(unlink("mmap.txt"), RETVAL == 0); + ls(".", 1, 0); + + /* Common use: shared file suppressed as soon as possible */ + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 22); + TEST_EXPECT_CONDITION(unlink("mmap.txt"), RETVAL == 0); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello2 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello2 from the child (shared mapping) !"), + RETVAL == 0); + } + ls(".", 1, 0); + + /* Basic use */ + TEST_EXPECT_CONDITION(creat("mmap.txt", S_IRUSR | S_IWUSR), RETVAL == 0); + if (fork() == 0) + { + char *shrd; + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 23); + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + nanosleep(1, 0); + strzcpy(shrd, "Hello3 from the child (shared mapping) !", 4096); + return 0; + } + else + { + char *shrd; + TEST_EXPECT_CONDITION(fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR), + RETVAL == OPEN_BASE_FD + 23); + TEST_EXPECT_CONDITION(shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096), + shrd != NULL); + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + TEST_EXPECT_CONDITION(strcmp(shrd, "Hello3 from the child (shared mapping) !"), + RETVAL == 0); + } + ls(".", 1, 0); + + bochs_printf("Bye from fstest FAT\n"); + ls(".", 1, 0); + + return 0; +} diff --git a/userland/init.c b/userland/init.c new file mode 100644 index 0000000..37300b9 --- /dev/null +++ b/userland/init.c @@ -0,0 +1,100 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include "fstest_utils.h" + +/** + * @file init.c + * Test fork and exec() + */ + +int main(void) +{ + bochs_printf("init: Welcome in userland !\n"); + + + /* Creating initial device nodes */ + TEST_EXPECT_CONDITION(mkdir("/dev", S_IRWXALL), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/zero", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_ZERO_MAJOR, SOS_CHARDEV_ZERO_MINOR), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/null", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_ZERO_MAJOR, SOS_CHARDEV_NULL_MINOR), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/kmem", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_MEM_MAJOR, SOS_CHARDEV_KMEM_MINOR), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/mem", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_MEM_MAJOR, SOS_CHARDEV_PHYSMEM_MINOR), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/tty", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_TTY_MAJOR, SOS_CHARDEV_CONSOLE_MINOR), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/ttyS", S_IRUSR | S_IWUSR, + S_IFCHR, SOS_CHARDEV_TTY_MAJOR, SOS_CHARDEV_SERIAL_MINOR), RETVAL == 0); + + TEST_EXPECT_CONDITION(mknod("/dev/hda", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 0), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda1", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 1), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda2", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 2), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda3", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 3), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda4", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 4), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda5", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 5), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda6", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 6), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hda7", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 7), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hdb", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 16), RETVAL == 0); + TEST_EXPECT_CONDITION(mknod("/dev/hdb1", S_IRUSR | S_IWUSR, + S_IFBLK, SOS_BLOCKDEV_IDE_MAJOR, 17), RETVAL == 0); + + ls("/", 1, 1); + + /* Set up the shell on the console */ + TEST_EXPECT_CONDITION(open("/dev/tty", O_RDWR), RETVAL == 0); + TEST_EXPECT_CONDITION(open("/dev/tty", O_RDWR), RETVAL == 1); + TEST_EXPECT_CONDITION(open("/dev/tty", O_RDWR), RETVAL == 2); + + if (fork() == 0) + exec ("shell"); + + close (2); + close (1); + close (0); + + /* Set up the shell on the serial port */ + TEST_EXPECT_CONDITION(open("/dev/ttyS", O_RDWR), RETVAL == 0); + TEST_EXPECT_CONDITION(open("/dev/ttyS", O_RDWR), RETVAL == 1); + TEST_EXPECT_CONDITION(open("/dev/ttyS", O_RDWR), RETVAL == 2); + + if (fork() == 0) + exec ("shell"); + + close (2); + close (1); + close (0); + + + + bochs_printf("init: The end\n"); + return 0; +} diff --git a/userland/ldscript.lds b/userland/ldscript.lds new file mode 100644 index 0000000..e064671 --- /dev/null +++ b/userland/ldscript.lds @@ -0,0 +1,67 @@ +/* Copyright (C) 2005, David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +/* We generate binary in the ELF format */ +OUTPUT_FORMAT("elf32-i386","elf32-i386","elf32-i386"); + +/* The entry point of the program is _start (defined in crt.c) */ +ENTRY(_start) + +/* The architecture is i386 */ +OUTPUT_ARCH("i386") + +SECTIONS +{ + /* our program is loaded at 2G */ + . = 0x80000000; + + /* Beginning of the text section */ + .text : + { + /* This section includes the code */ + *(.text*) + /* Defines the 'etext' and '_etext' at the end */ + PROVIDE(etext = .); + PROVIDE(_etext = .); + } + + /* Beginning of the data section */ + .data . : + { *(.data*) + PROVIDE(edata = .); + PROVIDE(_edata = .); + } + + /* Beginning of the read-only data section */ + .rodata . : + { *(.rodata*) + *(.eh_frame*) + PROVIDE(erodata = .); + PROVIDE(_erodata = .); + } + + /* Beginning of the BSS section (global uninitialized data) */ + .bss ALIGN(SIZEOF(.rodata) + ADDR(.rodata), 4096) : + { PROVIDE(bbss = .); + PROVIDE(_bbss = .); + *(.bss) + *(COMMON) + PROVIDE(ebss = .); + PROVIDE(_ebss = .); + } +} diff --git a/userland/libc.c b/userland/libc.c new file mode 100644 index 0000000..e39fee9 --- /dev/null +++ b/userland/libc.c @@ -0,0 +1,808 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include "crt.h" +#include "string.h" +#include "stdarg.h" + +#include "libc.h" + +void exit (int status) +{ + _sos_exit(status); +} + + +pid_t fork(void) +{ + return _sos_fork(); +} + + +pid_t getpid() +{ + return _sos_getpid(); +} + + +/** + * Helper to transform the array of argv and envp into an area + * suitable for the crt.c:unmarshall_argv_envp() function. + * @see crt.c for the format of this array. + */ +static void const* marshall_execve_parameters(char* const argv[], + char* const envp[], + /*out*/size_t * sz) +{ + size_t i, i_offset, count_argv, count_envp, size; + char * str; + void * result; + addr_t * offset; + + *sz = 0; + + /* Count the number of argv parameters and the size to allocate */ + for (count_argv = 0, size = 0 ; argv && argv[count_argv] != NULL ; count_argv ++) + size += strlen(argv[count_argv]) + 1 /* trailing \0 */; + + /* Count the number of envp parameters and the size to allocate */ + for (count_envp = 0 ; envp && envp[count_envp] != NULL ; count_envp ++) + size += strlen(envp[count_envp]) + 1 /* trailing \0 */; + + size += (count_argv + count_envp + 3) * sizeof(addr_t); + result = malloc(size); + if (NULL == result) + return NULL; + + /* Build the array of offsets and the string contents */ + offset = (addr_t*)result; + str = (char*) & offset[count_argv + count_envp + 3]; + i_offset = 0; + + /* First element of the array is the number of argv entries */ + offset [i_offset++] = count_argv; + + /* Marshall the argv array */ + for (i = 0 ; i < count_argv ; i ++, i_offset ++) + { + char const* c; + + offset[i_offset] = (void*)str - result; + + for (c = argv[i] ; *c ; c ++, str ++) + *str = *c; + *str = '\0'; str ++; + } + + /* The NULL between argv and envp */ + offset [i_offset++] = 0; + + for (i = 0 ; i < count_envp ; i ++, i_offset ++) + { + char const* c; + + offset[i_offset] = (void*)str - result; + + for (c = envp[i] ; *c ; c ++, str ++) + *str = *c; + *str = '\0'; str ++; + } + + /* Final NULL */ + offset[count_argv + count_envp + 2] = 0; + *sz = size; + + return result; +} + + +int execve(const char *filename, + char *const argv [], + char *const envp[]) +{ + void const* args_area; + size_t args_sz; + int retval; + + args_area = marshall_execve_parameters(argv, envp, & args_sz); + + retval = _sos_exec(filename, + args_area, args_sz); + + if (NULL != args_area) + free((void*)args_area); + + return retval; +} + + +int execv(const char * filename, + char * const argv []) +{ + return execve(filename, argv, environ); +} + + +static int compute_nargs(va_list ap) +{ + int nargs = 0; + while (va_arg(ap, char*)) + nargs ++; + return nargs; +} + + +static int vexec(const char *path, va_list ap, int envp_in_list) +{ + int retval; + char ** vector, **entry; + char *str; + char ** envp; + + size_t nargs = compute_nargs(ap); + + /* Allocate the char* vector */ + vector = malloc((nargs + 1)*sizeof(char*)); + if (! vector) + return -1; + + /* Fill it */ + entry = vector; + while ((str = va_arg(ap, char*)) != NULL) + { + *entry = str; + entry ++; + } + *entry = NULL; + + if (envp_in_list) + envp = (char**)va_arg(ap, char*); + else + envp = environ; + + retval = execve(path, vector, envp); + free(vector); + return retval; +} + + +int execl(const char *path, ...) +{ + int retval; + va_list ap; + va_start(ap, path); + retval = vexec(path, ap, FALSE); + va_end(ap); + return retval; +} + + +int execle(const char *path, ...) +{ + int retval; + va_list ap; + va_start(ap, path); + retval = vexec(path, ap, TRUE); + va_end(ap); + return retval; +} + + +int exec(char * const filename) +{ + return execl(filename, filename, NULL); +} + + +int sleep(unsigned int seconds) +{ + return nanosleep(seconds, 0); +} + + +int nanosleep(unsigned long int sec, + unsigned long int nanosec) +{ + return _sos_nanosleep(sec, nanosec); +} + + +/** + * Max number of environment variables + */ +#define SOS_MAX_ENVVARS 1024 + +char **environ = NULL; + + +/** + * Compare the keys of two strings of the form "key=val" + */ +static int equal_key(const char *e1, + const char *e2) +{ + for ( ; e1 && (*e1) && e2 && (*e2) && (*e1 == *e2) ; e1++, e2++) + if (*e1 == '=') + return TRUE; + + return FALSE; +} + + +/** + * Helper function to register an environment variable + */ +static int registerenv(char * string, int can_overwrite) +{ + int i; + char ** overwritten; + + /* The first time: allocate the environ table */ + if (! environ) + { + environ = malloc(SOS_MAX_ENVVARS * sizeof(char*)); + if (! environ) + return 0; + + memset(environ, 0x0, SOS_MAX_ENVVARS * sizeof(char*)); + } + + /* Walk the environment variables */ + overwritten = NULL; + for (i = 0 ; i < SOS_MAX_ENVVARS ; i ++) + { + /* Reached end of list ? */ + if (! environ[i]) + break; + + /* Variable already present ? */ + if (equal_key(environ[i], string)) + overwritten = & environ[i]; + } + + if (overwritten) + { + /* Not allowed to overwrite it ! */ + if (! can_overwrite) + return -1; + + /* Overwrite allowed: do it now */ + free(*overwritten); + *overwritten = string; + return 0; + } + + /* Must add the variable */ + + /* No place left ? */ + if (i >= SOS_MAX_ENVVARS) + return -1; + + /* Ok, add it at the end */ + environ[i] = string; + return 0; +} + + +/** + * @return TRUE when the key of the "keyvalpair" pair is "key" + */ +static int key_is(char * keyvalpair, const char * key, + char ** /*out*/value) +{ + for ( ; keyvalpair && *keyvalpair && key && *key ; keyvalpair ++, key ++) + if (*key != *keyvalpair) + break; + + if (value) + *value = keyvalpair + 1; + + return (keyvalpair && (*keyvalpair == '=') && key && (*key == '\0')); +} + + +int putenv(char * string) +{ + char * str; + if (! string) + return -1; + + str = strdup(string); + if (! str) + return -1; + + return registerenv(string, TRUE); +} + + +int setenv(const char *name, const char *value, int overwrite) +{ + char * str, * c; + size_t sz_name, sz_value; + + if (!name || !value) + return -1; + + /* Allocate space for the "name=value" string */ + sz_name = strlen(name); + sz_value = strlen(value); + str = malloc(sz_name + 1 + sz_value + 1); + if (! str) + return -1; + + /* sprintf(str, "%s=%s", name, value); */ + c = str; + *c = '\0'; + strzcpy(c, name, sz_name+1); c += sz_name; + strzcpy(c, "=", 2); c += 1; + strzcpy(c, value, sz_value+1); + + return registerenv(str, overwrite); +} + + +char *getenv(const char *name) +{ + int i; + + if (! environ) + return NULL; + + for (i = 0 ; i < SOS_MAX_ENVVARS ; i ++) + { + char *value; + + /* Reached end of list ? */ + if (! environ[i]) + return NULL; + + /* Variable already present ? */ + if (key_is(environ[i], name, & value)) + return value; + } + + return NULL; +} + + +void unsetenv(const char *name) +{ + int i; + char ** entry, ** last_entry; + + if (! environ) + return; + + /* Walk the environment variables */ + entry = last_entry = NULL; + for (i = 0 ; i < SOS_MAX_ENVVARS ; i ++) + { + /* Reached end of list ? */ + if (! environ[i]) + break; + + /* Variable already present ? */ + if (key_is(environ[i], name, NULL)) + entry = & environ[i]; + else if (entry) + last_entry = & environ[i]; + } + + /* Found nothing ! */ + if (! entry) + return; + + /* Found it: erase it... */ + free(*entry); + *entry = NULL; + + /* ... and replace it with the last entry */ + if (last_entry) + { + *entry = *last_entry; + *last_entry = NULL; + } +} + + +int clearenv(void) +{ + int i; + + if (! environ) + return 0; + + for (i = 0 ; i < SOS_MAX_ENVVARS ; i ++) + { + if (! environ[i]) + break; + + free(environ[i]); + environ[i] = NULL; + } + + return 0; +} + + + +int munmap(void * start, size_t length) +{ + return _sos_munmap(start, length); +} + + +void * mremap(void * old_addr, size_t old_len, + size_t new_len, + unsigned long flags) +{ + void * new_uaddr = old_addr; + if (0 != _sos_mresize(old_addr, old_len, & new_uaddr, new_len, flags)) + return NULL; + + return new_uaddr; +} + + +int mprotect(const void *addr, size_t len, int prot) +{ + return _sos_mprotect(addr, len, prot); +} + + +int msync(void *start, size_t length, int flags) +{ + return _sos_msync(start, length, flags); +} + + +/** + * The presence of this global variable without any protected access + * to it explains why the "brk/sbrk" functions below are MT-unsafe ! + */ +static void * kernel_heap_top = NULL; +int brk(void *end_data_seg) +{ + if (! end_data_seg) + return -1; + + kernel_heap_top = _sos_brk(end_data_seg); + return 0; +} + + +void *sbrk(ptrdiff_t increment) +{ + if (! kernel_heap_top) + kernel_heap_top = _sos_brk(0); + + kernel_heap_top = _sos_brk(kernel_heap_top + increment); + return kernel_heap_top; +} + + +void * calloc (size_t nmemb, size_t size) +{ + return malloc(nmemb * size); +} + + +/** + * The presence of this global variable without any protected access + * to it explains why the "malloc/calloc" functions below are + * MT-unsafe ! + */ +static void * malloc_heap_top = NULL; +void * malloc (size_t size) +{ + void * retval; + + if (size <= 0) + return NULL; + + /* Align on a 4B boundary */ + size = ((size-1) & ~3) + 4; + + if (! kernel_heap_top) + kernel_heap_top = _sos_brk(0); + + if (! malloc_heap_top) + malloc_heap_top = kernel_heap_top; + + retval = malloc_heap_top; + malloc_heap_top += size; + + _sos_brk(malloc_heap_top); + return retval; +} + + +void free(void *ptr) +{ + // bochs_printf("Free ignored (not implemented yet)\n"); +} + + + +int mount(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data) +{ + return _sos_mount(source, target, filesystemtype, mountflags, data); +} + + +int umount(const char *target) +{ + return _sos_umount(target); +} + + +void sync(void) +{ + return _sos_sync(); +} + + +int statvfs(const char *path, struct statvfs *buf) +{ + return _sos_statvfs(path, buf); +} + + +int open(const char *pathname, int flags, /* mode_t mode */...) +{ + va_list ap; + unsigned int mode = 0; + + va_start(ap, flags); + if (flags & O_CREAT) + mode = va_arg(ap, unsigned int); + va_end(ap); + + return _sos_open(pathname, flags, mode); +} + + +int close(int fd) +{ + return _sos_close(fd); +} + + +int read(int fd, char * buf, size_t len) +{ + int retval = _sos_read(fd, buf, & len); + if (retval < 0) + return retval; + return len; +} + + +int write(int fd, const char * buf, size_t len) +{ + int retval = _sos_write(fd, buf, & len); + if (retval < 0) + return retval; + return len; +} + + +off_t lseek(int fd, off_t offset, int whence) +{ + loff_t result = offset; + int retval = _sos_seek64(fd, & result, whence); + if (retval < 0) + return retval; + return result; +} + + +loff_t lseek64(int fd, loff_t offset, int whence) +{ + loff_t result = offset; + int retval = _sos_seek64(fd, & result, whence); + if (retval < 0) + return retval; + return result; +} + + +void * mmap(void *start, size_t length, int prot , int flags, + int fd, loff_t offset) +{ + /* At kernel side, offset is considered positive */ + if (offset < 0) + return NULL; + + if (0 != _sos_fmmap(& start, length, prot, flags, fd, offset)) + return NULL; + + return start; +} + + +int ftruncate(int fd, off_t length) +{ + return _sos_ftruncate64(fd, length); +} + + +int ftruncate64(int fd, loff_t length) +{ + return _sos_ftruncate64(fd, length); +} + + +int fcntl(int fd, int cmd, int arg) +{ + return _sos_fcntl(fd, cmd, arg); +} + + +int ioctl(int fd, int cmd, int arg) +{ + return _sos_ioctl(fd, cmd, arg); +} + + +int creat(const char *pathname, mode_t mode) +{ + return _sos_creat(pathname, mode); +} + +int link (const char *oldpath, const char *newpath) +{ + return _sos_link(oldpath, newpath); +} + + +int unlink(const char *pathname) +{ + return _sos_unlink(pathname); +} + + +int rename(const char *oldpath, const char *newpath) +{ + return _sos_rename(oldpath, newpath); +} + + +int symlink(const char *target, const char *path) +{ + return _sos_symlink(target, path); +} + + +int mknod(const char *pathname, mode_t mode, + int type, + unsigned int major, unsigned minor) +{ + if (type == S_IFREG) + return creat(pathname, mode); + + return _sos_mknod(pathname, mode, type, + major, minor); +} + + +int mkdir(const char *pathname, mode_t mode) +{ + return _sos_mkdir(pathname, mode); +} + + +int rmdir(const char *pathname) +{ + return _sos_rmdir(pathname); +} + + +int chmod(const char *path, mode_t mode) +{ + return _sos_chmod(path, mode); +} + + +struct sos_DIR_struct +{ + int fd; + struct dirent dirent; +}; + + +DIR *opendir(const char *name) +{ + DIR * result = malloc(sizeof(DIR)); + if (! result) + return NULL; + + result->fd = _sos_open(name, O_DIRECTORY | O_RDONLY, 0); + return result; +} + + +int dirfd(const DIR * dir) +{ + if (dir) + return dir->fd; + return -1; +} + + +struct dirent *readdir(DIR *dir) +{ + int retval = _sos_readdir(dir->fd, & dir->dirent); + if (retval < 0) + return NULL; + return & dir->dirent; +} + + +int closedir(DIR *dir) +{ + close(dir->fd); + free(dir); + return 0; +} + + +int stat(const char *file_name, struct stat *buf) +{ + return _sos_stat(file_name, TRUE, buf); +} + + +int lstat(const char *file_name, struct stat *buf) +{ + return _sos_stat(file_name, FALSE, buf); +} + + +int chroot(const char *path) +{ + return _sos_chroot(path); +} + + +int chdir(const char *path) +{ + return _sos_chdir(path); +} + +int fchdir(int fd) +{ + return _sos_fchdir(fd); +} + + + +int printf (const char *format, ...) +{ + char buff[4096]; + va_list ap; + + va_start(ap, format); + vsnprintf(buff, sizeof(buff), format, ap); + va_end(ap); + + return write (1, buff, strlen(buff)); +} + + diff --git a/userland/libc.h b/userland/libc.h new file mode 100644 index 0000000..771161f --- /dev/null +++ b/userland/libc.h @@ -0,0 +1,287 @@ +/* Copyright (C) 2005 David Decotigny + Copyright (C) 2003 Thomas Petazzoni + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_USER_LIBC_H_ +#define _SOS_USER_LIBC_H_ + +#include + +/** + * @file libc.h + * + * The basic user C library for user programs + */ + +/** + * The most important function of a C program ! ;) + */ +void exit (int status); + + +/** + * Function to duplicate the current running process + */ +pid_t fork(void); + + +/** + * Retrieves the current process's PID + */ +pid_t getpid(void); + + +/** + * Function to re-initialize the address space of the current process + * with that of the program 'progname' + */ +int execve(const char *filename, + char *const argv [], + char *const envp[]); +int execv(const char * filename, + char * const argv []); +int execl(const char *path, ...); +int execle(const char *path, ...); + + +/** + * Equivalent to execl(filename, filename); + */ +int exec(char * const filename); + + +int nanosleep(unsigned long int sec, + unsigned long int nanosec); + + +int sleep(unsigned int seconds); + + +/** + * Environment variables management API + */ +extern char **environ; /** Needed by execl */ + +int putenv(char * string); +int setenv(const char *name, const char *value, int overwrite); +char *getenv(const char *name); +void unsetenv(const char *name); +int clearenv(void); + + +/** + * These flags (for mmap API) MUST be consistent with those defined in + * paging.h and umem_vmm.h + */ +#define PROT_NONE 0 +#define PROT_READ (1<<0) +#define PROT_WRITE (1<<1) +#define PROT_EXEC (1<<2) /* Not supported on IA32 */ + +#define MAP_PRIVATE (0 << 0) +#define MAP_SHARED (1 << 0) +#define MAP_FIXED (1 << 31) + + +/** + * Unmap the given user address interval + */ +int munmap(void * start, size_t length); + + +/** + * Change the access permissions of the given user address interval + */ +int mprotect(const void *addr, size_t len, int prot); + + +/** + * This flag tells mremap that the underlying VR may be moved, when necessary + */ +#define MREMAP_MAYMOVE (1 << 30) + +/** + * Grow/shrink the end of the given mapping + */ +void * mremap(void * old_addr, size_t old_len, + size_t new_len, + unsigned long flags); + + +/** Flags for msync */ +#define MS_SYNC (1 << 0) +#define MS_ASYNC (0 << 0) + +/** Synchronize cache contents with mapped memory */ +int msync(void *start, size_t length, int flags); + + +/** + * Standard heap management API + */ + +/** @note MT UNSAFE */ +int brk(void *end_data_seg); + +/** @note MT UNSAFE */ +void *sbrk(ptrdiff_t increment); + +/** @note MT UNSAFE */ +void * calloc (size_t nmemb, size_t size); + +/** @note MT UNSAFE */ +void * malloc (size_t size); + +/** @note Does nothing (not implemented yet) */ +void free(void *ptr); + + +/* + * Filesystem subsystem + */ + +int mount(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data); + + +int umount(const char *target); + + +void sync(void); + + +struct statvfs +{ + unsigned int f_rdev_major; + unsigned int f_rdev_minor; + size_t f_sz_total; /**< Total size */ + size_t f_sz_free; /**< Size left on device */ + unsigned int f_node_total;/**< Total allocatable FS nodes */ + unsigned int f_node_avail;/**< Number of available free FS nodes */ + unsigned int f_flags; +}; + +int statvfs(const char *path, struct statvfs *buf); + + +#define O_EXCL (1 << 0) +#define O_CREAT (1 << 1) +#define O_TRUNC (1 << 2) +#define O_NOFOLLOW (1 << 3) +#define O_DIRECTORY (1 << 4) /* Incompatible with CREAT/TRUNC */ +#define O_SYNC (1 << 5) + +#define O_RDONLY (1 << 16) +#define O_WRONLY (1 << 17) +#define O_RDWR (O_RDONLY | O_WRONLY) + +/** + * FS access rights. Same as in kernel fs.h + */ +#define S_IRUSR 00400 +#define S_IWUSR 00200 +#define S_IXUSR 00100 +#define S_IRWXALL 07777 /* For symlinks */ + +int open(const char *pathname, int flags, /* mode_t mode */...); + +int close(int fd); + + +int read(int fd, char * buf, size_t len); +int write(int fd, const char * buf, size_t len); + +/* Same as sos_seek_whence_t in kernel fs.h */ +#define SEEK_SET 42 +#define SEEK_CUR 24 +#define SEEK_END 84 +off_t lseek(int fd, off_t offset, int whence); +loff_t lseek64(int fd, loff_t offset, int whence); +void * mmap(void *start, size_t length, int prot , int flags, + int fd, loff_t offset); +int ftruncate(int fd, off_t length); +int ftruncate64(int fd, loff_t length); +int fcntl(int fd, int cmd, int arg); +int ioctl(int fd, int cmd, int arg); + +int creat(const char *pathname, mode_t mode); +int link (const char *oldpath, const char *newpath); +int unlink(const char *pathname); +int rename(const char *oldpath, const char *newpath); +int symlink(const char *target, const char *path); + +/* Same as sos_fs_node_type_t in kernel fs.h */ +#define S_IFREG 0x42 +#define S_IFDIR 0x24 +#define S_IFLNK 0x84 +#define S_IFCHR 0x48 +#define S_IFBLK 0x12 +int mknod(const char *pathname, mode_t mode, + int type, + unsigned int major, unsigned minor); + + +int mkdir(const char *pathname, mode_t mode); + + +int rmdir(const char *pathname); + + +int chmod(const char *path, mode_t mode); + + +struct dirent +{ + unsigned long long int storage_location; + unsigned long long int offset_in_dirfile; + unsigned int type; + unsigned short namelen; + +#define SOS_FS_DIRENT_NAME_MAXLEN 128 + char name[SOS_FS_DIRENT_NAME_MAXLEN]; +}; + +/* Forward declaration (defined in libc.c) */ +struct sos_DIR_struct; +#define DIR struct sos_DIR_struct + +DIR *opendir(const char *name); +int dirfd(const DIR * dir); +struct dirent *readdir(DIR *dir); +int closedir(DIR *dir); + +struct stat +{ + unsigned int st_rdev_major; + unsigned int st_rdev_minor; + int st_type; + unsigned long long int st_storage_location; + int st_access_rights; + unsigned int st_nlink; + signed long long int st_size; +}; + +int stat(const char *file_name, struct stat *buf); +int lstat(const char *file_name, struct stat *buf); +int chroot(const char *path); +int chdir(const char *path); +int fchdir(int fd); + +int printf(const char *, ...); + +#endif /* _SOS_USER_LIBC_H_ */ diff --git a/userland/myprog1.c b/userland/myprog1.c new file mode 100644 index 0000000..7bb5cda --- /dev/null +++ b/userland/myprog1.c @@ -0,0 +1,37 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog1.c + * Basic print/exit test + */ + +int main(void) +{ + int retval; + + bochs_printf("Prog1: Hello world 1 !\n"); + bochs_printf("Prog1: Hello world 2 !\n"); + bochs_printf("Prog1: Hello world 3 !\n"); + bochs_printf("Prog1: Hello world 4 !\n"); + retval = bochs_printf("Prog1: Hello world 5 !\n"); + + return retval; +} diff --git a/userland/myprog10.c b/userland/myprog10.c new file mode 100644 index 0000000..28198e3 --- /dev/null +++ b/userland/myprog10.c @@ -0,0 +1,82 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +/** + * @file myprog10.c + * + * mprotect tests + * + * We use the temporary syscall 4004 to dump the list of VRs in the + * thread's address space + */ + +int main(void) +{ + char * zoup; + int fd; + + fd = open("/dev/zero", O_RDWR); + zoup = mmap((void*)4096, 8*1024*1024, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 34); + close(fd); + + bochs_printf("mapped @%x\n", (unsigned)zoup); + + /* Do some forks to complicate things */ + fork(); + fork(); + + _sos_syscall1(4004, (unsigned)"Initial"); + mprotect(zoup, 10*4096, PROT_READ); + zoup += 10*4096; + _sos_syscall1(4004, (unsigned)"After mprotect Low"); + + mprotect(zoup-4096, 2*4096, PROT_READ); + zoup += 4096; + _sos_syscall1(4004, (unsigned)"After mprotect Before Low"); + + mprotect(zoup-4096, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Before low (bis)"); + + mprotect(zoup + 1024*1024, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Middle"); + + mprotect(zoup + 1024*1024, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Middle (bis)"); + + mprotect(zoup + 8*1024*1024 - 11*4096 - 4096, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect High"); + + mprotect(zoup + 8*1024*1024 - 11*4096 - 2*4096, 3*4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Past High"); + + mprotect((void*)0x40000000, 0x10000000, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Complete VR"); + + mprotect((void*)0x40000000, 0x10000000, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Complete VR (bis)"); + + return 0; +} diff --git a/userland/myprog11.c b/userland/myprog11.c new file mode 100644 index 0000000..1fe8545 --- /dev/null +++ b/userland/myprog11.c @@ -0,0 +1,109 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + + +/** + * @file myprog11.c + * + * mresize tests + * + * We use the temporary syscall 4004 to dump the list of VRs in the + * thread's address space + */ + + +int main(void) +{ + void * moved; + char * zoup; + int fd; + + fd = open("/dev/zero", O_RDWR); + zoup = mmap((void*)4096, 8*1024*1024, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 34); + close(fd); + + bochs_printf("mapped @%x\n", (unsigned)zoup); + + /* Do some forks to complicate things */ + fork(); + fork(); + + /* First, split the region in slices using a sequence of + mprotects */ + _sos_syscall1(4004, (unsigned)"Initial"); + mprotect(zoup, 10*4096, PROT_READ); + zoup += 10*4096; + _sos_syscall1(4004, (unsigned)"After mprotect Low"); + + mprotect(zoup-4096, 2*4096, PROT_READ); + zoup += 4096; + _sos_syscall1(4004, (unsigned)"After mprotect Before Low"); + + mprotect(zoup-4096, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Before low (bis)"); + + mprotect(zoup + 1024*1024, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Middle"); + + mprotect(zoup + 1024*1024, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Middle (bis)"); + + mprotect(zoup + 8*1024*1024 - 11*4096 - 4096, 4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect High"); + + mprotect(zoup + 8*1024*1024 - 11*4096 - 2*4096, 3*4096, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Past High"); + + mprotect((void*)0x40000000, 0x10000000, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Complete VR"); + + mprotect((void*)0x40000000, 0x10000000, PROT_READ); + _sos_syscall1(4004, (unsigned)"After mprotect Complete VR (bis)"); + + /* Now try to resize one of the VR */ + moved = zoup + 8*1024*1024 - 11*4096 - 10*4096; + _sos_mresize(zoup + 8*1024*1024 - 11*4096 - 2*4096, 3*4096, + & moved, 40*4096, 0); + _sos_syscall1(4004, (unsigned)"After mremap (not allowed)"); + bochs_printf("moved=%x\n", (unsigned)moved); + + _sos_mresize(zoup + 8*1024*1024 - 11*4096 - 2*4096, 4096, + & moved, 40*4096, 0); + _sos_syscall1(4004, (unsigned)"After mremap (not allowed - bis)"); + bochs_printf("moved=%x\n", (unsigned)moved); + + _sos_mresize(zoup + 8*1024*1024 - 11*4096 - 2*4096, 4096, + & moved, 40*4096, MREMAP_MAYMOVE); + _sos_syscall1(4004, (unsigned)"After mremap (DO move)"); + bochs_printf("moved=%x\n", (unsigned)moved); + + _sos_mresize(moved, 4096, + & moved, 100*4096, MREMAP_MAYMOVE); + _sos_syscall1(4004, (unsigned)"After mremap (DO move)"); + bochs_printf("moved=%x\n", (unsigned)moved); + + return 0; +} diff --git a/userland/myprog12.c b/userland/myprog12.c new file mode 100644 index 0000000..d4b6de4 --- /dev/null +++ b/userland/myprog12.c @@ -0,0 +1,104 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include + + +/** + * @file myprog12.c + * + * /dev/mem & /dev/kmem tests + */ + + +int main(void) +{ + char *zoup; + + /* Test /dev/mem */ + + /* Map the x86 text framebuffer into this process address space in + shared mode */ + int fd; + + fd = open("/dev/mem", O_RDWR); + zoup = mmap(0, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 0xb8000); + close(fd); + + bochs_printf("mapped mem @%x\n", (unsigned)zoup); + _sos_syscall1(4004, (unsigned)"Apres mmap video"); + + /* Do some forks to complicate things */ + fork(); + fork(); + + /* Write a string into it: it should be printed on screen */ + strzcpy(zoup + 80*5*2 + 10*2, + "H e l l o W o r l d f r o m S O S i n U s e r M o d e ", + 250); + + /* Map the x86 text framebuffer into this process address space in + PRIVATE mode */ + fd = open("/dev/mem", O_RDWR); + zoup = mmap(0, 4096, PROT_READ | PROT_WRITE, + 0 /* Private */, + fd, 0xb8000); + close(fd); + bochs_printf("mapped mem @%x\n", (unsigned)zoup); + _sos_syscall1(4004, (unsigned)"Apres mmap video"); + + /* Do some forks to complicate things */ + fork(); + fork(); + + /* Write a string into it: it should NOT be printed on screen since + the mapping is private */ + strzcpy(zoup + 80*5*2 + 10*2, + "Y o u c a n n o t S e e T h i s ! ", + 250); + + /* Test /dev/kmem */ + + /* remap some kernel pages in user space. We'd better map this in + "private" mode here, this way we can do whatever we like in this + area. If we'd mapped it read/write, this would overwrite kernel + data/code, causing a crash sooner or later. */ + fd = open("/dev/kmem", O_RDWR); + zoup = mmap(0, 100*4096, PROT_READ | PROT_WRITE, + 0 /* private */, + fd, 0x00201000); + close(fd); + bochs_printf("mapped kmem @%x\n", (unsigned)zoup); + _sos_syscall1(4004, (unsigned)"Apres mmap kernel"); + + /* Do some forks to complicate things */ + fork(); + fork(); + + /* Overwriting our private "copy" of kernel */ + memset(zoup, 0x0, 10*4096); + _sos_syscall1(4004, (unsigned)"Apres memset kernel"); + + return 0; +} diff --git a/userland/myprog13.c b/userland/myprog13.c new file mode 100644 index 0000000..b396d57 --- /dev/null +++ b/userland/myprog13.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + + +/** + * @file myprog13.c + * Test brk (and, hence: mremap) + */ + +int main(void) +{ + char * zoup; + + bochs_printf("myprog13: Hello world !\n"); + + /* Do some forks to complicate things */ + fork(); + fork(); + + zoup = _sos_brk(0); + bochs_printf("_sos_brk(0)=%x\n", (unsigned)_sos_brk(0)); + bochs_printf("_sos_brk(+0)=%x\n", (unsigned)_sos_brk(zoup)); + bochs_printf("_sos_brk(+1)=%x\n", (unsigned)_sos_brk(zoup + 1)); + zoup[3] = 42; + bochs_printf("z[] = %d\n", zoup[3]); + bochs_printf("_sos_brk(0)=%x\n", (unsigned)_sos_brk(0)); + bochs_printf("_sos_brk(orig)=%x\n", (unsigned)_sos_brk(zoup)); + bochs_printf("_sos_brk(0)=%x\n", (unsigned)_sos_brk(0)); + // zoup[3] = 42; + + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + bochs_printf("sbrk(1)=%x\n", (unsigned)sbrk(1)); + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + zoup = sbrk(0); + bochs_printf("brk(+1)=%x\n", (unsigned)brk(zoup + 1)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + + bochs_printf("sbrk(-4k)=%x\n", (unsigned)sbrk(-4096)); + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + zoup = sbrk(0); + bochs_printf("brk(-4k)=%x\n", (unsigned)brk(zoup - 4096)); + bochs_printf("brk(0)=%x\n", (unsigned)brk(0)); + bochs_printf("sbrk(0)=%x\n", (unsigned)sbrk(0)); + + bochs_printf("malloc(0)=%x\n", (unsigned)malloc(0)); + bochs_printf("malloc(1)=%x\n", (unsigned)malloc(1)); + bochs_printf("malloc(2)=%x\n", (unsigned)malloc(2)); + bochs_printf("malloc(3)=%x\n", (unsigned)malloc(3)); + bochs_printf("malloc(4)=%x\n", (unsigned)malloc(4)); + bochs_printf("malloc(5)=%x\n", (unsigned)malloc(5)); + bochs_printf("malloc(6)=%x\n", (unsigned)malloc(6)); + bochs_printf("malloc(7)=%x\n", (unsigned)malloc(7)); + bochs_printf("malloc(8)=%x\n", (unsigned)malloc(8)); + bochs_printf("malloc(9)=%x\n", (unsigned)malloc(9)); + bochs_printf("malloc(0x30000)=%x\n", (unsigned)malloc(0x30000)); + bochs_printf("malloc(1)=%x\n", (unsigned)malloc(1)); + + bochs_printf("myprog13: The end\n"); + return 0; +} diff --git a/userland/myprog14.c b/userland/myprog14.c new file mode 100644 index 0000000..c0f8289 --- /dev/null +++ b/userland/myprog14.c @@ -0,0 +1,70 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +/** + * @file myprog14.c + * + * uaccess tests. Demonstrate that a page fault in kernel during + * userspace access (with uaccess.c functions) is NEVER (or at least + * should not be) fatal: + * - licit page faults are resolved as usual (COW, page_in, ...) + * - illicit page faults are caught and the uaccess functions tolerate + * and report it + * + * We use the temporary syscall 4012 (hexdump) to force the kernel to + * call user_memcpy and do some page faults. + */ + +int main(void) +{ + char * zoup; + int fd; + + fd = open("/dev/zero", O_RDWR); + zoup = mmap(0, 8192, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 34); + close(fd); + + bochs_printf("mapped @%x\n", (unsigned)zoup); + + /* Do some forks to complicate things */ + fork(); + fork(); + + /* Force the first page of the mapping to be allocated */ + *zoup = 'a'; + + _sos_syscall2(4012, (unsigned)zoup, 16384); + /* + * all 3 cases are handled here: + * - [zoup, zoup + 4kB[: no page fault at all (page already mapped) + * - [zoup+4kB, zoup + 8kB[: page fault => /dev/zero page_in() called + * - [zoup+8kB, ...[: page fault => illegal user address => + * user_memcpy reports that only 8kB out of the 16kB could be + * hexdumped + */ + + return 0; +} diff --git a/userland/myprog2.c b/userland/myprog2.c new file mode 100644 index 0000000..e50cfd6 --- /dev/null +++ b/userland/myprog2.c @@ -0,0 +1,35 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog2.c + * Basic print/pause/exit test + */ + +int main(void) +{ + int i; + bochs_printf("Prog2: Hello world A !\n"); + for (i = 0 ; i < 10000000 ; i++) + continue; + bochs_printf("Prog2: Hello world B !\n"); + + return 0; +} diff --git a/userland/myprog3.c b/userland/myprog3.c new file mode 100644 index 0000000..be6f22a --- /dev/null +++ b/userland/myprog3.c @@ -0,0 +1,46 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog3.c + * Test unresolved page fault (write to kernel space) + */ + +int main(void) +{ + char *c; + int i; + bochs_printf("Prog3: Hello world A !\n"); + for (i = 0 ; i < 100000 ; i++) + continue; + bochs_printf("Prog3: Hello world B !\n"); + + /* Force a page fault */ + c = (char*) 0x201000; + /* Try to read somewhere in the kernel space */ + i = *c; + + bochs_printf("Prog3: Hello world C !\n"); + for ( ; i> 0 ; i--) + continue; + bochs_printf("Prog3: Hello world D !\n"); + + return 0; +} diff --git a/userland/myprog4.c b/userland/myprog4.c new file mode 100644 index 0000000..97c115c --- /dev/null +++ b/userland/myprog4.c @@ -0,0 +1,46 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog4.c + * Test unresolved page fault (write to unmapped page in user space) + */ + +int main(void) +{ + char *c; + int i; + bochs_printf("Prog4: Hello world A !\n"); + for (i = 0 ; i < 100000 ; i++) + continue; + bochs_printf("Prog4: Hello world B !\n"); + + /* Force a page fault */ + c = (char*) 0x60000000; + /* Try to read somewhere in user space where nothing is mapped */ + i = *c; + + bochs_printf("Prog4: Hello world C !\n"); + for ( ; i> 0 ; i--) + continue; + bochs_printf("Prog4: Hello world D !\n"); + + return 0; +} diff --git a/userland/myprog5.c b/userland/myprog5.c new file mode 100644 index 0000000..c60fd5a --- /dev/null +++ b/userland/myprog5.c @@ -0,0 +1,36 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog5.c + * Test inifite loop (preemptive scheduling of threads in user mode) + */ + +int main(void) +{ + int retval; + + bochs_printf("Prog5: Hello world 1 !\n"); + for (;;) + ; + bochs_printf("Prog5: Hello world 2 !\n"); + + return retval; +} diff --git a/userland/myprog6.c b/userland/myprog6.c new file mode 100644 index 0000000..9d7168b --- /dev/null +++ b/userland/myprog6.c @@ -0,0 +1,39 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +/** + * @file myprog6.c + * Test execution of (forbiden) supervisor instructions + */ + +int main(void) +{ + int i; + int retval = 0; + + bochs_printf("Prog6: Hello world 1 !\n"); + for (i = 0 ; i < 10000000 ; i++) + ; + bochs_printf("Prog6: Hello world 2 !\n"); + asm("cli\n"); /* Protection volation ! */ + bochs_printf("Prog6: Hello world 3 !\n"); + + return retval; +} diff --git a/userland/myprog7.c b/userland/myprog7.c new file mode 100644 index 0000000..f0c5656 --- /dev/null +++ b/userland/myprog7.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include + +static unsigned rien[16384]; + + +/** + * @file myprog7.c + * Tests forks and make sure that private mappings (eg the '.data' + * section of the program object file) are really private + */ + + +static void assign(unsigned * addr, const unsigned val) +{ + bochs_printf("[0x%x]=0x%x <- 0x%x\n", (unsigned)addr, *addr, val); + *addr = val; + bochs_printf(" ===> 0x%x\n", *addr); +} + + +int main(void) +{ + int i, level; + + bochs_printf("myprog7: Hello\n"); + + assign(& rien[0], 0x42); + assign(& rien[1000], 0x42); + + level = 0; + for (i = 0 ; i < 10 ; i++) + { + pid_t fork_status; + + fork_status = fork(); + if (fork_status == 0) + { + level ++; + bochs_printf("myprog7: I am the child %d %d\n", level, i); + + assign(& rien[0], 0x0f<<24 | (level << 8) | i); + assign(& rien[1000], 0x0a<<24 | (level << 8) | i); + + if ((i % 2) == 0) + { + char strprog[20]; + snprintf(strprog, sizeof(strprog), "myprog%d", i/2 + 1); + exec(strprog); + } + } + else if (fork_status > 0) + { + bochs_printf("myprog7: I am the father %d %d\n", level, i); + + assign(& rien[0], 0x8f<<24 | (level << 8) | i); + assign(& rien[1000], 0x8a<<24 | (level << 8) | i); + } + else + bochs_printf("myprog7: fork error %d !\n", fork_status); + } + + bochs_printf("myprog7: The end %d %d\n", level, i); + assign(& rien[0], 0xff<<24 | (level << 8) | i); + assign(& rien[1000], 0xfa<<24 | (level << 8) | i); + + return 0; +} diff --git a/userland/myprog8.c b/userland/myprog8.c new file mode 100644 index 0000000..dded077 --- /dev/null +++ b/userland/myprog8.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include + + +/** + * @file myprog8.c + * Make sure that shared mappings (here: anonymous shared mapping) are + * really shared between father and child + */ + +int main(void) +{ + int i; + char *zoup; + int fd; + + fd = open("/dev/zero", O_RDWR); + zoup = mmap((void*)4096, 8*1024*1024, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 0x123456789012345ULL); + close(fd); + + bochs_printf("mapped @%x\n", (unsigned)zoup); + + switch (fork()) + { + case 0: + bochs_printf("CHILD 1: %d %d\n", zoup[42], zoup[43]); + for (i = 0 ; i < 1000000 ; i++) + zoup[43] = i / 19; + bochs_printf("CHILD 2: %d %d\n", zoup[42], zoup[43]); + for (i = 0 ; i < 1000000 ; i++) + zoup[43] = i / 19; + bochs_printf("CHILD 3: %d %d\n", zoup[42], zoup[43]); + break; + + default: + bochs_printf("FATHER 1: %d %d\n", zoup[42], zoup[43]); + for (i = 0 ; i < 1000000 ; i++) + zoup[42] = i / 17; + bochs_printf("FATHER 2: %d %d\n", zoup[42], zoup[43]); + break; + } + + return 0; +} diff --git a/userland/myprog9.c b/userland/myprog9.c new file mode 100644 index 0000000..a45ba20 --- /dev/null +++ b/userland/myprog9.c @@ -0,0 +1,82 @@ +/* Copyright (C) 2005 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include + +/** + * @file myprog9.c + * + * mmap/munmap tests + * + * We use the temporary syscall 4004 to dump the list of VRs in the + * thread's address space + */ + +int main(void) +{ + char *zoup; + int fd; + + fd = open("/dev/zero", O_RDWR); + zoup = mmap((void*)4096, 8*1024*1024, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, 34); + close(fd); + + bochs_printf("mapped @%x\n", (unsigned)zoup); + + /* Do a fork() here to complicate things */ + fork(); + fork(); + + _sos_syscall1(4004, (unsigned)"Initial"); + munmap(zoup, 10*4096); + zoup += 10*4096; + _sos_syscall1(4004, (unsigned)"After munmap Low"); + + munmap(zoup-4096, 2*4096); + zoup += 4096; + _sos_syscall1(4004, (unsigned)"After munmap Before Low"); + + munmap(zoup-4096, 4096); + _sos_syscall1(4004, (unsigned)"After munmap Before Low (bis)"); + + munmap(zoup + 1024*1024, 4096); + _sos_syscall1(4004, (unsigned)"After munmap middle"); + + munmap(zoup + 1024*1024, 4096); + _sos_syscall1(4004, (unsigned)"After munmap middle (bis)"); + + munmap(zoup + 8*1024*1024 - 11*4096 - 4096, 4096); + _sos_syscall1(4004, (unsigned)"After munmap High"); + + munmap(zoup + 8*1024*1024 - 11*4096 - 2*4096, 3*4096); + _sos_syscall1(4004, (unsigned)"After munmap Past High"); + + munmap((void*)0x40000000, 0x10000000); + _sos_syscall1(4004, (unsigned)"After munmap Over everything"); + + munmap((void*)0x40000000, 0x10000000); + _sos_syscall1(4004, (unsigned)"After munmap Over everything (bis)"); + + return 0; +} diff --git a/userland/shell.c b/userland/shell.c new file mode 100644 index 0000000..0cd0b38 --- /dev/null +++ b/userland/shell.c @@ -0,0 +1,1001 @@ +/* Copyright (C) 2005 Thomas Petazzoni, David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include +#include +#include +#include +#include +#include + +#include "fstest_utils.h" + +/** + * @file shell.c + * + * Small shell. + */ + +static int shell_uname (int argc, char *argv[]) +{ + printf ("SOS article 10\n"); + return 0; +} + +static void shell_ls_internal(int detailed, int recursive, int reclevel) +{ + char tab[256], *c; + int i; + struct dirent * dirent; + DIR * here; + + here = opendir("."); + if (! here) + return; + + /* Build initial tabulation */ + if (recursive) + { + for (c = tab, i = 0 ; (i < reclevel) && (i < sizeof(tab)/2) ; i++) + { + *c++ = ' '; + *c++ = ' '; + } + *c++ = '\0'; + } + else + *tab = '\0'; + + while ((dirent = readdir(here)) != NULL) + { + char entrychar; + char * entrysuffix; + + switch(dirent->type) + { + case S_IFREG: entrychar='-'; entrysuffix=""; break; + case S_IFDIR: entrychar='d'; entrysuffix="/"; break; + case S_IFLNK: entrychar='l'; entrysuffix="@"; break; + case S_IFCHR: entrychar='c'; entrysuffix=NULL; break; + case S_IFBLK: entrychar='b'; entrysuffix=NULL; break; + default: entrychar='?'; entrysuffix="?!?"; break; + } + + if (detailed) + { + struct stat stat; + char target_name[SOS_FS_DIRENT_NAME_MAXLEN]; + char majorminor[24]; + + if (lstat(dirent->name, & stat)) + continue; + + *target_name = '\0'; + if (stat.st_type == S_IFLNK) + { + int fd = open(dirent->name, O_RDONLY | O_NOFOLLOW); + if (fd >= 0) + { + int len = read(fd, target_name, sizeof(target_name) - 1); + if (len < 0) + *target_name='\0'; + else + target_name[len] = '\0'; + close(fd); + } + } + else if ( (stat.st_type == S_IFCHR) || (stat.st_type == S_IFBLK) ) + { + snprintf(majorminor, sizeof(majorminor), " %d,%d", + stat.st_rdev_major, stat.st_rdev_minor); + entrysuffix = majorminor; + } + + printf("%s%c%c%c%c %lld %s%s%s%s (location=0x%llx)\n", + tab, entrychar, + (stat.st_access_rights&S_IRUSR)?'r':'-', + (stat.st_access_rights&S_IWUSR)?'w':'-', + (stat.st_access_rights&S_IXUSR)?'x':'-', + stat.st_size, + dirent->name, + entrysuffix, + (stat.st_type==S_IFLNK)?" -> ":"", + target_name, + stat.st_storage_location); + } + else + printf("%s%s%s\n", + tab, dirent->name, entrysuffix); + + /* Next iteration */ + if (recursive) + { + int fd_here = dirfd(here); + if (chdir(dirent->name)) + continue; + shell_ls_internal(detailed, recursive, reclevel+1); + if(fchdir(fd_here)) + { + closedir(here); + return; + } + } + } + closedir(here); +} + +static void shell_ls(const char * path, int detailed, int recursive) +{ + int fd_here = open(".", O_RDONLY | O_DIRECTORY); + if (fd_here < 0) + return; + + if (chdir(path)) + { + close(fd_here); + return; + } + + shell_ls_internal(detailed, recursive, 1); + + fchdir(fd_here); + close(fd_here); +} + +static int shell_chdir(int argc, char *argv[]) +{ + int ret; + + ret = chdir(argv[1]); + + if (ret < 0) + printf("Cannot chdir '%s': %d\n", argv[1], ret); + + return 0; +} + +static int shell_touch (int argc, char *argv[]) +{ + return creat (argv[1], S_IRUSR | S_IWUSR); +} + +static int shell_mkdir (int argc, char *argv[]) +{ + int retval; + + retval = mkdir (argv[1], S_IRUSR | S_IWUSR | S_IXUSR); + + if (retval != 0) + printf("Cannot mkdir %s (%d)\n", argv[1], retval); + return retval; +} + +static int shell_rmdir (int argc, char *argv[]) +{ + int ret; + + ret = rmdir (argv[1]); + + if (ret < 0) + printf("Cannot rmdir '%s': %d\n", argv[1], ret); + + return 0; +} + +static int shell_cat (int argc, char *argv[]) +{ + int fd; + char buf[4096]; + int len; + + fd = open (argv[1], O_RDONLY); + if (fd < 0) { + printf ("Cannot open '%s'\n", argv[1]); + return -1; + } + + while (1) + { + len = read (fd, buf, sizeof(buf)-1); + if (len <= 0) + break; + buf[len] = '\0'; + + printf ("%s", buf); + } + + close (fd); + return 0; +} + +static int shell_edit (int argc, char *argv[]) +{ + int fd; + char buf[16]; + int len; + + fd = open (argv[1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + printf ("Cannot create '%s': %d\n", argv[1], fd); + return -1; + } + + /* Activate echo and activate again canonical mode */ + ioctl (0, SOS_IOCTL_TTY_SETPARAM, SOS_IOCTLPARAM_TTY_ECHO); + ioctl (0, SOS_IOCTL_TTY_SETPARAM, SOS_IOCTLPARAM_TTY_CANON); + + while (1) + { + len = read (0, buf, sizeof(buf)); + if (len <= 1) + break; + + bochs_printf ("Writing %d bytes\n", len); + len = write (fd, buf, len); + if (len <= 0) { + printf ("Cannot write to '%s': %d\n", argv[1], len); + break; + } + } + + /* Desactivate echo and remove canonical mode */ + ioctl (0, SOS_IOCTL_TTY_RESETPARAM, SOS_IOCTLPARAM_TTY_ECHO); + ioctl (0, SOS_IOCTL_TTY_RESETPARAM, SOS_IOCTLPARAM_TTY_CANON); + + close (fd); + return 0; +} + +static int shell_hexdump (int argc, char *argv[]) +{ + int fd; + char buf[16]; + int count = 0; + int len; + + fd = open (argv[1], O_RDONLY); + if (fd < 0) { + printf ("Cannot open '%s': %d\n", argv[1], fd); + return -1; + } + + while (1) + { + int i; + + len = read (fd, buf, sizeof(buf)); + if (len <= 0) + break; + + if (count < 0x10) + printf ("00%x ", count); + else if (count < 0x100) + printf ("0%x ", count); + else if (count < 0x1000) + printf ("%x ", count); + + for (i = 0; i < len; i++) + { + if (buf[i] < 0x10) + printf ("0%x ", buf[i]); + else + printf ("%x ", buf[i]); + } + + printf ("\n"); + + count += len; + } + + close (fd); + return 0; +} + +static int shell_test (int argc, char *argv[]) +{ + int i; + for (i = 0; i < argc; i++) + { + printf ("argv[%d] = %s\n", i, argv[i]); + } + return 0; +} + + +static int shell_spawn (int argc, char *argv[]) +{ + int retval; + if (argc < 2) + { + printf("Usage: spawn prog_name\n"); + return -1; + } + retval = fork(); + if (retval > 0) + return 0; /* father returns */ + if (retval < 0) + { + printf("Could not fork (%d)\n", retval); + return -1; + } + printf("Created new process %d\n", getpid()); + retval = execv(argv[1], & argv[1]); + printf("Could not exec %s: error %d\n", argv[1], retval); + exit(0); + return 0; +} + + +static int shell_rm (int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + int ret; + + ret = unlink (argv[i]); + if (ret < 0) + printf ("Cannot remove '%s', %d\n", argv[i], ret); + } + + return 0; +} + + +static int shell_dd (int argc, char *argv[]) +{ + char *infile = NULL; + char *outfile = NULL; + long bs = 1; + long count = -1; + int i; + int infd, outfd; + char *buffer; + int ret; + + for (i = 1; i < argc; i++) + { + if (strncmp (argv[i], "if=", 3) == 0) + infile = argv[i] + 3; + else if (strncmp (argv[i], "of=", 3) == 0) + outfile = argv[i] + 3; + else if (strncmp (argv[i], "bs=", 3) == 0) + { + char *tmp = argv[i] + 3; + char *multiplier_char; + int multiplier = 1; + int j; + + multiplier_char = & tmp[strlen(tmp)-1]; + if (*multiplier_char == 'k' || + *multiplier_char == 'K') + { + multiplier = 1024; + *multiplier_char = '\0'; + } + else if (*multiplier_char == 'm' || + *multiplier_char == 'M') + { + multiplier = 1024 * 1024; + *multiplier_char = '\0'; + } + + for (j = 0; j < strlen(tmp); j++) + if (! isdigit(tmp[j])) + { + printf ("Syntax error in block size\n"); + return -1; + } + + bs = atol(tmp) * multiplier; + } + else if (strncmp (argv[i], "count=", 6) == 0) + { + char *tmp = argv[i] + 6; + int j; + + for (j = 0; j < strlen(tmp); j++) + if (! isdigit(tmp[j])) + { + printf ("Syntax error in block count\n"); + return -1; + } + + count = atol(tmp); + } + else + { + printf ("Syntax error\n"); + return -1; + } + } + + infd = open(infile, O_RDONLY); + if (infd < 0) + { + printf ("Cannot open '%s', %d\n", infile, infd); + return infd; + } + + outfd = open(outfile, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (outfd < 0) + { + printf ("Cannot open '%s', %d\n", outfile, outfd); + return outfd; + } + + buffer = malloc (bs); + if (! buffer) + return -1; + + for (i = 0; (count < 0) || (i < count) ; i++) + { + int len; + + ret = read (infd, buffer, bs); + if (ret < 0) + { + printf ("Input error from '%s' at %d\n", infile, i); + return -1; + } + if (ret == 0) + break; + + len = ret; + + ret = write (outfd, buffer, len); + if (ret != len) + { + printf ("Output error to '%s' at %d (%d)\n", outfile, i, ret); + return -1; + } + } + + return 0; +} + + +static int shell_partdump (int argc, char *argv[]) +{ + /** + * This structure defines the structure of a partition entry + * (determined by the PC architecture) + */ + struct partition_entry + { + unsigned char active; + unsigned char start_dl; + unsigned short start_cylinder; + unsigned char type; + unsigned char end_dl; + unsigned short end_cylinder; + unsigned long lba; + unsigned long size; + }; + + /** + * Offset of the partition table inside the 512-byte sector. + */ +#define PART_TABLE_OFFSET 446 + + /** + * The most common partition types. For a complete list, you can for + * example refer to + * http://www.win.tue.nl/~aeb/partitions/partition_types-1.html + */ +#define PART_TYPE_EXTENDED 0x5 +#define PART_TYPE_FAT16_1 0xe +#define PART_TYPE_FAT16_2 0x6 +#define PART_TYPE_FAT32_1 0xb +#define PART_TYPE_FAT32_2 0xc +#define PART_TYPE_LINUX_SWAP 0x82 +#define PART_TYPE_LINUX 0x83 + + /** + * Converts a partition type to a string + */ + const char * + sos_part_type_str (unsigned int type) + { + switch (type) + { + case PART_TYPE_EXTENDED: + return "Extended"; + case PART_TYPE_FAT16_1: + case PART_TYPE_FAT16_2: + return "FAT16"; + case PART_TYPE_FAT32_1: + case PART_TYPE_FAT32_2: + return "FAT32"; + case PART_TYPE_LINUX_SWAP: + return "Linux Swap"; + case PART_TYPE_LINUX: + return "Linux"; + default: + return "Unknown"; + } + } + + int fd; + /* We assume the block size is 512 bytes. The clean way would be to + have a BLKGETSIZE ioctl() on block devices */ +#define BLKSIZE 512 + char buffer[BLKSIZE]; + int ret; + struct partition_entry *part_entry; + int partnum; + unsigned int extstart = 0, extsup = 0; + + if (argc != 2) + { + printf ("Usage: partdump /dev/device\n"); + return -1; + } + + fd = open (argv[1], O_RDONLY); + if (fd < 0) + { + printf ("Cannot open '%s', %d\n", argv[1], fd); + return -1; + } + + ret = read (fd, buffer, sizeof(buffer)); + if (ret != sizeof(buffer)) + { + printf ("Read error in '%s', %d\n", argv[1], fd); + return -1; + } + + part_entry = (struct partition_entry *) (buffer + PART_TABLE_OFFSET); + + printf ("Partitions in %s:\n", argv[1]); + + /* Handle primary partitions */ + for (partnum = 0; partnum < 4; partnum++) + { + if (part_entry [partnum].size == 0) + continue; + + if (part_entry [partnum].type == PART_TYPE_EXTENDED) + { + if (extstart != 0) + printf ("Warning: two extended partitions detected\n"); + extstart = part_entry [partnum].lba; + continue; + } + + /* When size is >= 1 Mb, display size in Mb, otherwise display + size in Kb */ + if (part_entry[partnum].size * BLKSIZE >= (1024*1024)) + printf (" - %s%d (primary, %lu Mb, %s)\n", argv[1], partnum+1, + (part_entry[partnum].size * BLKSIZE >> 20), + sos_part_type_str(part_entry[partnum].type)); + else + printf (" - %s%d (primary, %lu Kb, %s)\n", argv[1], partnum+1, + (part_entry[partnum].size * BLKSIZE >> 10), + sos_part_type_str(part_entry[partnum].type)); + } + + while (extstart != 0) + { + ret = lseek (fd, (extstart + extsup) * BLKSIZE, SEEK_SET); + if (ret != (extstart + extsup) * BLKSIZE) + { + printf ("Cannot seek at position %d in '%s', %d\n", + (extstart + extsup) * BLKSIZE, argv[1], ret); + return -1; + } + + ret = read (fd, buffer, BLKSIZE); + if (ret != BLKSIZE) + { + printf ("Read error in '%s', %d\n", argv[1], ret); + break; + } + + /* When size is >= 1 Mb, display size in Mb, otherwise display + size in Kb */ + if (part_entry[0].size * BLKSIZE >= (1024*1024)) + printf (" - %s%d (logical volume, %lu Mb, %s)\n", argv[1], partnum+1, + (part_entry[0].size * BLKSIZE >> 20), + sos_part_type_str(part_entry[0].type)); + else + printf (" - %s%d (logical volume, %lu Kb, %s)\n", argv[1], partnum+1, + (part_entry[0].size * BLKSIZE >> 10), + sos_part_type_str(part_entry[0].type)); + + extsup = part_entry[1].lba; + partnum ++; + if (extsup == 0) + break; + } + + return 0; +} + +static int shell_mount (int argc, char *argv[]) +{ + /*char * end = NULL;*/ + int retval = 0; + int flags = 0; + + if (strncmp(argv[1], "-t", 2)) { + /*flags = strtol(argv[1], &end, 10);*/ + if (argc == 8) { + retval = mount(argv[4], argv[5], argv[3], flags, argv[7]); + } else { + if (argc == 6) { + retval = mount(argv[4], argv[5], argv[3], flags, NULL); + } else { + printf("mount [FLAGS] -t TYPE DEVICE MOUNT_DIR [-o ARGS]\n"); + } + } + } else { + if (argc == 7) { + retval = mount(argv[3], argv[4], argv[2], 0, argv[6]); + } else { + if (argc == 5) { + retval = mount(argv[3], argv[4], argv[2], 0, NULL); + } else { + printf("mount [FLAGS] -t TYPE DEVICE MOUNT_DIR [-o ARGS]\n"); + } + } + } + + if (retval != 0) + printf("Cannot mount %s (%d)\n", argv[2], retval); + return retval; +} + +static int shell_umount (int argc, char *argv[]) +{ + int retval = 0; + + if (argc == 2) + retval = umount(argv[1]); + else + printf("umount DIR\n"); + + if (retval != 0) + printf("Cannot umount %s (%d)\n", argv[1], retval); + return retval; +} + +static int shell_mmap (int argc, char *argv[]) +{ + /* + * test mmap + */ + int fd, ret; + + /* Common use: shared file suppressed as soon as possible */ + fd = open("mmap.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + + if (fork() == 0) + { + char *shrd; + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd children error\n"); + + nanosleep(1, 0); + strzcpy(shrd, "Hello1 from the child (shared mapping) !", 4096); + exit(0); + } + else + { + char *shrd; + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd father error\n"); + + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + if (strcmp(shrd, "Hello1 from the child (shared mapping) !") == 0) + bochs_printf("TEST1 OK\n"); + munmap(shrd, 8192); + } + ret = close(fd); + if (ret == 0) + bochs_printf("close OK\n"); + ret = unlink("mmap.txt"); + if (ret == 0) + bochs_printf("delete mmap.txt OK\n"); + + shell_ls_internal(1, 0, 1); + + /* Common use: shared file suppressed as soon as possible */ + fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR); + ret = unlink("mmap.txt"); + if (ret == 0) + bochs_printf("delete mmap.txt OK 2\n"); + if (fork() == 0) + { + char *shrd; + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd children error\n"); + + nanosleep(1, 0); + strzcpy(shrd, "Hello2 from the child (shared mapping) !", 4096); + exit(0); + } + else + { + char *shrd; + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd father error\n"); + + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + if (strcmp(shrd, "Hello2 from the child (shared mapping) !") == 0) + bochs_printf("Test 2 OK\n"); + /*msync(shrd, 4096, MAP_SHARED);*/ + munmap(shrd, 8192); + } + ret = close(fd); + if (ret == 0) + bochs_printf("close OK\n"); + + shell_ls_internal(1, 0, 1); + + /* Basic use */ + ret = creat("mmap.txt", S_IRUSR | S_IWUSR); + if (ret == 0) + bochs_printf("creat OK\n"); + if (fork() == 0) + { + char *shrd; + fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR); + + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd children error\n"); + nanosleep(1, 0); + strzcpy(shrd, "Hello3 from the child (shared mapping) !", 4096); + exit(0); + } + else + { + char *shrd; + fd = open("mmap.txt", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR); + + shrd = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 4096); + if (shrd == NULL) + bochs_printf("shrd father error\n"); + + strzcpy(shrd, "Garbage garbage garbage", 256); + nanosleep(2, 0); + bochs_printf("Father woken up\n"); + bochs_printf("Read string from child: '%s'\n", shrd); + if (strcmp(shrd, "Hello3 from the child (shared mapping) !") == 0) + bochs_printf("TEST3 OK\n"); + + munmap(shrd, 8192); + ret = close(fd); + if (ret == 0) + bochs_printf("close OK\n"); + } + shell_ls_internal(1, 0, 1); + + return 0; +} + +void command_exec (char * cmd) +{ + char *c; + int argc = 1, i; + char **argv; + + for (c = cmd; *c != '\0'; c++) + { + if (*c == ' ') + { + /* Skip all spaces */ + while (*c && *c == ' ') + c++; + + /* Reached the end of the command line ? */ + if (! *c) + break; + + argc++; + } + } + + argv = malloc ((argc+1) * sizeof(char*)); + argv[argc] = NULL; /* To be compatible with execv(e) */ + if (argv == NULL) + return; + + for (i = 0, c = cmd; i < argc; i++) + { + argv[i] = c; + while (*c != ' ' && *c != '\0') + c++; + *c = '\0'; + c++; + /* Skip spaces */ + while (*c && *c == ' ') + c++; + } + + if (! strcmp (argv[0], "uname")) + shell_uname(argc, argv); + + else if (! strcmp (argv[0], "ls")) + { + if (argv[1]) + shell_ls (argv[1], 1, 0); + else + shell_ls (".", 1, 0); + } + + else if (! strcmp (argv[0], "cd")) + { + shell_chdir(argc, argv); + } + + else if (! strcmp (argv[0], "touch")) + { + shell_touch (argc, argv); + } + + else if (! strcmp (argv[0], "mkdir")) + { + shell_mkdir (argc, argv); + } + + else if (! strcmp (argv[0], "rmdir")) + { + shell_rmdir (argc, argv); + } + + else if (! strcmp (argv[0], "cat")) + { + shell_cat (argc, argv); + } + + else if (! strcmp (argv[0], "edit")) + { + shell_edit (argc, argv); + } + + else if (! strcmp (argv[0], "hexdump")) + { + shell_hexdump (argc, argv); + } + else if (! strcmp (argv[0], "test")) + { + shell_test (argc, argv); + } + else if (! strcmp (argv[0], "spawn")) + { + shell_spawn (argc, argv); + } + else if (! strcmp (argv[0], "dd")) + { + shell_dd (argc, argv); + } + else if (! strcmp (argv[0], "rm")) + { + shell_rm (argc, argv); + } + else if (! strcmp (argv[0], "partdump")) + { + shell_partdump (argc, argv); + } + else if (! strcmp (argv[0], "mount")) + { + shell_mount (argc, argv); + } + else if (! strcmp (argv[0], "umount")) + { + shell_umount (argc, argv); + } + else if (! strcmp (argv[0], "mmap")) + { + shell_mmap (argc, argv); + } + else if (! strcmp(argv[0], "getenv")) + { + if (argc > 1) + printf("%s\n", getenv(argv[1])); + else + printf("Variable name missing\n"); + } + else if (! strcmp(argv[0], "setenv")) + { + if (argc > 2) + printf("retval=%d\n", + setenv(argv[1], argv[2], TRUE)); + else + printf("Variable name/value missing\n"); + } + else if (! strcmp(argv[0], "unsetenv")) + { + if (argc > 1) + unsetenv(argv[1]); + else + printf("Variable name missing\n"); + } + else + printf ("Command not found\n"); + + free (argv); +} + +int main(void) +{ + char buffer[256]; + int i; + char chr; + + ioctl (0, SOS_IOCTL_TTY_RESETPARAM, SOS_IOCTLPARAM_TTY_CANON); + + while (1) + { + memset (buffer, 0, sizeof(buffer)); + i = 0; + printf ("$ "); + + while (1) + { + read (0, & chr, 1); + if (chr == '\n') + { + buffer[i] = '\0'; + printf ("\n"); + if (i != 0) + command_exec (buffer); + break; + } + else if (chr == '\b') + { + if (i > 0) + { + i--; + printf ("\b"); + } + } + + else if (i < 256) + { + buffer[i++] = chr; + printf ("%c", chr); + } + else + printf ("%c", chr); + } + } + + return 0; +} diff --git a/userland/stdarg.c b/userland/stdarg.c new file mode 100644 index 0000000..a122586 --- /dev/null +++ b/userland/stdarg.c @@ -0,0 +1,244 @@ +/* Copyright (C) 2004 David Decotigny (with INSA Rennes for vsnprintf) + Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include + +#include "stdarg.h" + + +/* I (d2) borrowed and rewrote this for Nachos/INSA Rennes. Thanks to + them for having kindly allowed me to do so. */ +int vsnprintf(char *buff, size_t len, const char * format, va_list ap) +{ + int i, result; + int fmt_modifiers = 0; + int prefix_long = 0; + int prefix_long_long = 0; + + if (!buff || !format || (len < 0)) + return -1; + +#define PUTCHAR(thechar) \ + do { \ + if (result < len-1) \ + *buff++ = (thechar); \ + result++; \ + } while (0) + + result = 0; + for(i=0 ; format[i] != '\0' ; i++) + { + if (!fmt_modifiers && (format[i] != '%')) + { + PUTCHAR(format[i]); + continue; + } + + switch (format[i]) + { + case '%': + if (fmt_modifiers) + { + PUTCHAR('%'); + fmt_modifiers = 0; + break; + } + + fmt_modifiers = 1; + prefix_long = 0; + prefix_long_long = 0; + break; + + case 'l': + if (prefix_long) + prefix_long_long = 1; + else + prefix_long = 1; + break; + + case 'u': + { + if (! prefix_long_long) + { + unsigned int integer = va_arg(ap,unsigned int); + int cpt2 = 0; + char buff_int[16]; + + do { + int m10 = integer%10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + else + { + unsigned long long int integer + = va_arg(ap,unsigned long long int); + int cpt2 = 0; + char buff_int[32]; + + do { + int m10 = integer%10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + } + fmt_modifiers = 0; + break; + + case 'i': + case 'd': + { + if (! prefix_long_long) + { + int integer = va_arg(ap,int); + int cpt2 = 0; + char buff_int[16]; + + if (integer<0) + PUTCHAR('-'); + /* Ne fait pas integer = -integer ici parce que INT_MIN + n'a pas d'equivalent positif (int = [-2^31, 2^31-1]) */ + + do { + int m10 = integer%10; + m10 = (m10 < 0)? -m10:m10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + else + { + long long int integer = va_arg(ap,long long int); + int cpt2 = 0; + char buff_int[32]; + + if (integer<0) + PUTCHAR('-'); + /* Ne fait pas integer = -integer ici parce que INT_MIN + n'a pas d'equivalent positif (int = [-2^63, 2^63-1]) */ + + do { + int m10 = integer%10; + m10 = (m10 < 0)? -m10:m10; + buff_int[cpt2++]=(char)('0'+ m10); + integer=integer/10; + } while(integer!=0); + + for(cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) + PUTCHAR(buff_int[cpt2]); + } + } + fmt_modifiers = 0; + break; + + case 'c': + { + int value = va_arg(ap,int); + PUTCHAR((char)value); + fmt_modifiers = 0; + break; + } + + case 's': + { + char *string = va_arg(ap,char *); + if (! string) + string = "(null)"; + for( ; *string != '\0' ; string++) + PUTCHAR(*string); + fmt_modifiers = 0; + break; + } + + case 'p': + PUTCHAR('0'); + PUTCHAR('x'); + case 'x': + { + unsigned long long int hexa; + unsigned long long int nb; + int i, had_nonzero = 0; + + if (prefix_long_long) + hexa = va_arg(ap,unsigned long long int); + else + hexa = va_arg(ap,unsigned int); + + for(i=0 ; i < 16 ; i++) + { + nb = (unsigned long long int)(hexa << (i*4)); + nb = (nb >> 60) & 0xf; + // Skip the leading zeros + if (nb == 0) + { + if (had_nonzero) + PUTCHAR('0'); + } + else + { + had_nonzero = 1; + if (nb < 10) + PUTCHAR('0'+nb); + else + PUTCHAR('a'+(nb-10)); + } + } + if (! had_nonzero) + PUTCHAR('0'); + } + fmt_modifiers = 0; + break; + + default: + PUTCHAR('%'); + if (prefix_long) + PUTCHAR('l'); + if (prefix_long_long) + PUTCHAR('l'); + PUTCHAR(format[i]); + fmt_modifiers = 0; + } + } + + *buff = '\0'; + return result; +} + + +int snprintf(char * buff, size_t len, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + len = vsnprintf(buff, len, format, ap); + va_end(ap); + + return len; +} diff --git a/userland/stdarg.h b/userland/stdarg.h new file mode 100644 index 0000000..c24234f --- /dev/null +++ b/userland/stdarg.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_LIBC_STDARG_H_ +#define _SOS_LIBC_STDARG_H_ + +#include + +/** + * @file stdarg.h + */ + +/* Borrowed from GCC */ +#define __GNUC_VA_LIST +typedef void *__gnuc_va_list; +typedef __gnuc_va_list va_list; +#define __va_rounded_size(TYPE) \ + (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int)) +#define va_start(AP, LASTARG) \ + (AP = ((__gnuc_va_list) __builtin_next_arg (LASTARG))) +#define va_end(AP) \ + ((void)0) +#define va_arg(AP, TYPE) \ + (AP = (__gnuc_va_list) ((char *) (AP) + __va_rounded_size (TYPE)), \ + *((TYPE *) (void *) ((char *) (AP) - __va_rounded_size (TYPE)))) +#define __va_copy(dest, src) \ + (dest) = (src) + +/* stdarg.h functions. There might be a non-standard behavior: there + will always be a trailing '\0' in the resulting string */ +int vsnprintf(char *, size_t, const char *, va_list); +int snprintf(char *, size_t, const char *, /*args*/ ...) + __attribute__ ((format (printf, 3, 4))); + +#endif /* _SOS_LIBC_STDARG_H_ */ diff --git a/userland/string.c b/userland/string.c new file mode 100644 index 0000000..92ae418 --- /dev/null +++ b/userland/string.c @@ -0,0 +1,239 @@ +/* Copyright (C) 2004 David Decotigny (with INSA Rennes for vsnprintf) + Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include "string.h" +#include "libc.h" + +/* For an optimized version, see BSD sources ;) */ +void *memcpy(void *dst0, const void *src0, register unsigned int size) +{ + char *dst; + const char *src; + for (dst = (char*)dst0, src = (const char*)src0 ; + size > 0 ; + dst++, src++, size--) + *dst = *src; + return dst0; +} + +/* ditto */ +void *memset(void *dst0, register int c, register unsigned int length) +{ + char *dst; + for (dst = (char*) dst0 ; + length > 0 ; + dst++, length --) + *dst = (char)c; + return dst0; +} + +int memcmp(const void *s1, const void *s2, size_t len) +{ + const unsigned char *c1, *c2; + unsigned int i; + + for (i = 0, c1 = s1, c2 = s2; i < len; i++, c1++, c2++) + { + if(*c1 != *c2) + return *c1 - *c2; + } + + return 0; +} + + +unsigned int strlen(register const char *str) +{ + unsigned int retval = 0; + + while (*str++) + retval++; + + return retval; +} + + +unsigned int strnlen(const char * s, size_t count) +{ + const char *sc; + + for (sc = s; count-- && *sc != '\0'; ++sc) + /* nothing */continue; + + return sc - s; +} + + +char *strzcpy(register char *dst, register const char *src, register int len) +{ + int i; + + if (len <= 0) + return dst; + + for (i = 0; i < len; i++) + { + dst[i] = src[i]; + if(src[i] == '\0') + return dst; + } + + dst[len-1] = '\0'; + return dst; +} + + +char *strzcat (char *dest, const char *src, size_t n) +{ + char *res = dest; + + for ( ; *dest ; dest++); + + for ( ; *src ; src++, dest++) { + *dest = *src; + n--; + if (n <= 0) + break; + } + + *dest = '\0'; + return res; +} + + +char *strndup(const char *s, size_t n) +{ + char * result; + n = strnlen(s, n); + result = malloc(n+1); + if (! result) + return NULL; + + strzcpy(result, s, n+1); + return result; +} + + +char *strdup(const char *s) +{ + char * result; + size_t n = strlen(s); + + result = malloc(n+1); + if (! result) + return NULL; + + strzcpy(result, s, n+1); + return result; +} + + +int strcmp(register const char *s1, register const char *s2) +{ + while (*s1 == *s2++) + if (*s1++ == 0) + return (0); + + return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1)); +} + + +int strncmp(register const char *s1, register const char *s2, register int len) +{ + char c1 = '\0', c2 = '\0'; + + while (len > 0) + { + c1 = (unsigned char) *s1++; + c2 = (unsigned char) *s2++; + if (c1 == '\0' || c1 != c2) + return c1 - c2; + len--; + } + + return c1 - c2; +} + + +long long int strtoll(const char *nptr, char **endptr, int base) +{ + long long int result; + +#define RETURN(res) ({ if (endptr) *endptr = (char*)nptr; return res; }) + + if (base > 36) + { + RETURN(0); + } + + /* Skip leading spaces */ + for ( ; *nptr != '\0' && isspace(*nptr) ; nptr++) + continue; + + if (nptr[0] == '0') + { + if (nptr[1] == 'x') + { + base = 16; + nptr += 2; + } + else + { + base = 8; + nptr ++; + } + } + + result = 0; + for ( ; *nptr != '\0' ; nptr ++) + { + int digit; + if (*nptr >= '0' && *nptr <= '9') + digit = *nptr - '0'; + else if (*nptr >= 'a' && *nptr <= 'z') + digit = 10 + *nptr - 'a'; + else if (*nptr >= 'A' && *nptr <= 'Z') + digit = 10 + *nptr - 'A'; + else + RETURN(result); + if (digit >= base) + { + RETURN(result); + } + result *= base; + result += digit; + } + + RETURN(result); +} + +long int strtol(const char *nptr, char **endptr, int base) +{ + return strtoll(nptr, endptr, base); +} + +long long atoll(const char *nptr) +{ + return strtoll(nptr, NULL, 10); +} + +long atol(const char *nptr) +{ + return strtol(nptr, NULL, 10); +} diff --git a/userland/string.h b/userland/string.h new file mode 100644 index 0000000..fbac5af --- /dev/null +++ b/userland/string.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_LIBC_STRING_H_ +#define _SOS_LIBC_STRING_H_ + +#include + +/** + * @file string.h + * + * Most of the prototypes of these functions are borrowed from + * FreeBSD, but their implementation (in klibc.c) come either from Kos + * (GPL v2) or from David Decotigny (SOS). + */ + +void *memcpy(void *dst, const void *src, register unsigned int size ) ; +void *memset(void *dst, register int c, register unsigned int length ) ; +int memcmp(const void *s1, const void *s2, size_t n); + +unsigned int strlen( register const char *str) ; +unsigned int strnlen(const char * s, size_t maxlen); + +/** + * @note Same as strncpy(), with a slightly different semantic. + * Actually, strncpy(3C) says " The result will not be null-terminated + * if the length of 'from' is n or more.". Here, 'dst' is ALWAYS + * null-terminated. And its total len will ALWAYS be <= len, with + * null-terminating-char included. + */ +char *strzcpy( register char *dst, register const char *src, + register int len ) ; + +/** + * @note Same as strncat(), with the same semantic : 'dst' is ALWAYS + * null-terminated. And its total len will ALWAYS be <= len, with + * null-terminating-char included. + */ +char *strzcat (char *dest, const char *src, + const size_t len); + +char *strndup(const char *s, size_t n); +char *strdup(const char *s); + +int strcmp(register const char *s1, register const char *s2 ); +int strncmp(register const char *s1, register const char *s2, + register int len ); + +#define islower(c) (('a' <= (c)) && ((c) <= 'z')) +#define isupper(c) (('A' <= (c)) && ((c) <= 'Z')) +#define isdigit(c) (('0' <= (c)) && ((c) <= '9')) +#define isspace(c) (((c) == ' ') || ((c) == '\t') || \ + ((c) == '\f') || ((c) == '\n') || \ + ((c) == '\r') || ((c) == '\v')) +#define isprint(c) ((' ' <= (c)) && ((c) <= '~')) + +long long int strtoll(const char *nptr, char **endptr, int base); +long int strtol(const char *nptr, char **endptr, int base); +long long atoll(const char *nptr); +long atol(const char *nptr); + +#endif /* _SOS_LIBC_STRING_H_ */ diff --git a/userland/types.h b/userland/types.h new file mode 100644 index 0000000..374ec07 --- /dev/null +++ b/userland/types.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2003 The KOS Team + Copyright (C) 1999 Free Software Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#ifndef _SOS_LIBC_TYPES_H_ +#define _SOS_LIBC_TYPES_H_ + +/** + * @file types.h + */ + +typedef unsigned int size_t; + +typedef unsigned int addr_t; +typedef int ptrdiff_t; +typedef int pid_t; +typedef signed long int off_t; +typedef signed long long int loff_t; + +typedef unsigned int mode_t; +typedef unsigned long long int dev_t; + +#ifndef NULL +# define NULL ((void*)0) +#endif + +#ifndef TRUE +# define TRUE (!0) +#endif + +#ifndef FALSE +# define FALSE (!TRUE) +#endif + +#endif /* _SOS_LIBC_TYPES_H_ */